GitHunt
JA

jannikbecher/lambda_modbus

LambdaModbus

Elixir implementation of the Lambda Wärmepumpen (Heat Pump) Modbus TCP & RTU communication protocol using the modbux library.

Based on the official Lambda Modbus protocol document dated 13.02.2025.

Supported Modules

Module Index Max Instances Data Points
General (Ambient) 0 Fixed (sub 0) Outdoor temperature, averages
General (E-Manager) 0 Fixed (sub 1) PV surplus, power consumption
Heat Pump 1 3 Temps, COP, compressor, requests, energy stats
Boiler (DHW) 2 5 Tank temperatures, max temp setting
Buffer 3 5 Tank temperatures, heating requests
Solar 4 2 Collector/buffer temps, settings
Heating Circuit 5 12 Flow/return/room temps, operating mode, setpoints

Communication

Modbus TCP

  • Port: 502, Unit ID: 1
  • Up to 16 concurrent master connections
  • Keep connection alive — do NOT reconnect per request

Modbus RTU

  • RS485, 9600 baud, 8N1
  • 120Ω termination resistors on bus ends

Function Codes

  • Read: 0x03 (read multiple holding registers)
  • Write: 0x10 (write multiple holding registers)

Write Timeout Rules

  • Data points 00–49: Must be refreshed within 5 minutes or default value is used
  • Data points 50+: Written once, stored permanently

Installation

def deps do
  [{:lambda_modbus, path: "path/to/lambda_modbus"}]
end

Quick Start

TCP Connection

{:ok, conn} = LambdaModbus.Connection.start_link(
  transport: :tcp,
  ip: {192, 168, 0, 47},
  port: 502
)

RTU Connection

{:ok, conn} = LambdaModbus.Connection.start_link(
  transport: :rtu,
  tty: "/dev/ttyUSB0",
  slave_id: 1
)

Reading Data

# Ambient temperature
{:ok, temp} = LambdaModbus.Connection.read_ambient_temp(conn)

# Heat pump snapshot (all operational data in one read)
{:ok, snapshot} = LambdaModbus.Connection.read_hp_snapshot(conn)
# => %{flow_temp_c: 35.2, return_temp_c: 30.1, cop: 4.52, ...}

# Heat pump individual values
{:ok, cop} = LambdaModbus.Connection.read_hp_cop(conn)
{:ok, state} = LambdaModbus.Connection.read_hp_operating_state(conn)
{:ok, power} = LambdaModbus.Connection.read_hp_fi_power(conn)

# Energy statistics (INT32, Wh)
{:ok, consumed_wh} = LambdaModbus.Connection.read_hp_energy_consumed(conn)
{:ok, output_wh} = LambdaModbus.Connection.read_hp_energy_output(conn)

# Boiler
{:ok, boiler_state} = LambdaModbus.Connection.read_boiler_state(conn)
{:ok, boiler_temp} = LambdaModbus.Connection.read_boiler_high_temp(conn)

# Heating circuit
{:ok, circuit_state} = LambdaModbus.Connection.read_circuit_state(conn, 0)
{:ok, flow_temp} = LambdaModbus.Connection.read_circuit_flow_temp(conn, 0)
{:ok, mode} = LambdaModbus.Connection.read_circuit_mode(conn, 0)

# Second heat pump (instance 1)
{:ok, hp2_state} = LambdaModbus.Connection.read_hp_operating_state(conn, 1)

# E-Manager
{:ok, pv_power} = LambdaModbus.Connection.read_emanager_power(conn)

Writing Data

# Write PV surplus to E-Manager (must refresh within 5 min)
:ok = LambdaModbus.Connection.write_emanager_power(conn, 3500)

# Write ambient temperature (when using Modbus as source)
:ok = LambdaModbus.Connection.write_ambient_temp(conn, 12.5)

# Write room temperature for heating circuit 0
:ok = LambdaModbus.Connection.write_circuit_room_temp(conn, 21.5, 0)

# Set heating circuit operating mode
:ok = LambdaModbus.Connection.write_circuit_mode(conn, :automatik, 0)

# Set permanent settings (number >= 50)
:ok = LambdaModbus.Connection.set_boiler_max_temp(conn, 55.0)
:ok = LambdaModbus.Connection.set_circuit_room_heating_temp(conn, 22.0, 0)
:ok = LambdaModbus.Connection.set_circuit_room_cooling_temp(conn, 24.0, 0)
:ok = LambdaModbus.Connection.set_circuit_flow_offset(conn, 2.0, 0)

# Heat pump request (requires password unlock first)
:ok = LambdaModbus.Connection.write_hp_request_password(conn, 12345)
:ok = LambdaModbus.Connection.write_hp_request(conn, 0,
  type: :central_heating,
  flow_temp: 40.0,
  return_temp: 35.0,
  temp_diff: 5.0
)

# Raw register access
{:ok, [val]} = LambdaModbus.Connection.read_registers(conn, 1004, 1)
:ok = LambdaModbus.Connection.write_registers(conn, 5006, [2])

Register Address Scheme

Address = Index × 1000 + Subindex × 100 + Number

Examples:
  General Ambient temp    = 0×1000 + 0×100 + 02 = 2
  E-Manager power         = 0×1000 + 1×100 + 02 = 102
  Heat Pump 1 flow temp   = 1×1000 + 0×100 + 04 = 1004
  Heat Pump 2 flow temp   = 1×1000 + 1×100 + 04 = 1104
  Boiler 1 high temp      = 2×1000 + 0×100 + 02 = 2002
  Buffer 2 state          = 3×1000 + 1×100 + 01 = 3101
  Circuit 3 room temp     = 5×1000 + 2×100 + 04 = 5204

Use LambdaModbus.Address.build/3 and LambdaModbus.Address.parse/1 for programmatic access.

Module Overview

Module Purpose
LambdaModbus.Connection GenServer for TCP/RTU, high-level read/write API
LambdaModbus.Address Register address builder (Index/Subindex/Number)
LambdaModbus.Registers.General Ambient + E-Manager registers
LambdaModbus.Registers.HeatPump Heat pump registers (data + requests + stats)
LambdaModbus.Registers.Boiler Boiler (DHW) registers
LambdaModbus.Registers.Buffer Buffer storage registers
LambdaModbus.Registers.Solar Solar thermal registers
LambdaModbus.Registers.HeatingCircuit Heating/cooling circuit registers
LambdaModbus.DataTypes INT16/UINT16/INT32 conversions, temperature scaling
LambdaModbus.Status State/mode/error decoders for all module types

License

MIT

jannikbecher/lambda_modbus | GitHunt