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"}]
endQuick 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