[modbus.lambda] Initial contribution (#19378)

* New Crowdin updates (#19375)

Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com>
Signed-off-by: Johannes Koch <github@lockhead.net>
This commit is contained in:
Christian Koch
2025-11-23 11:07:57 +01:00
committed by GitHub
parent a7bbdfe3d8
commit e393064c19
53 changed files with 6110 additions and 0 deletions
+1
View File
@@ -246,6 +246,7 @@
/bundles/org.openhab.binding.modbus.e3dc/ @weymann
/bundles/org.openhab.binding.modbus.foxinverter/ @holgerfriedrich
/bundles/org.openhab.binding.modbus.helioseasycontrols/ @bern77
/bundles/org.openhab.binding.modbus.lambda/ @chilobo
/bundles/org.openhab.binding.modbus.kermi/ @KaaNee
/bundles/org.openhab.binding.modbus.sbc/ @fwolter
/bundles/org.openhab.binding.modbus.stiebeleltron/ @pail23
+5
View File
@@ -1221,6 +1221,11 @@
<artifactId>org.openhab.binding.modbus.kermi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.lambda</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus.sbc</artifactId>
@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
@@ -0,0 +1,314 @@
# Lambda Heat Pump
This extension adds support for the Lambda Heat Pump modbus protocol as provided by
<https://lambda-wp.at/wp-content/uploads/2025/02/Modbus-Beschreibung-und-Protokoll.pdf>
A Lambda Heat Pump has to be reachable within your network.
If you plan to use the E-Manager part to hand over your PV excess to the heat pump ask Lambda support to configure it to
E-Meter Kommunikationsart: ModBus Client
E-Meter Messpunkt: E-Eintrag
Other configurations of the E-Manager are not supported (yet).
## Supported Things
This bundle adds the following thing types to the Modbus binding.
Note, that the things will show up under the Modbus binding.
| Thing | ThingTypeID | Description |
| --------------- | --------------------| --------------------------------------- |
| General | general | General sections Ambient and E-Manager |
| Heat Pump | heat-pump | Heat Pump section |
| Boiler | boiler | Boiler section |
| Buffer | buffer | Buffer section |
| Solar | solar | Solar section |
| Heating Circuit | heating-circuit | Heating Circuit section |
A Modbus Bridge has to be installed before installing the above mentioned things.
The binding supports installations with more than one Heat Pump, Boiler, Buffer, Heating Circuit.
For each of these parts you have to provide the Subindex of your thing in the configurations section, usually using the User Interface.
So if you have two Heating Circuits use 0 for the first and 1 for the second Heating Circuit.
Handling of General System Settings (Base Adress 200) is not supported (yet). Solar functions (Base Address 4000) are now supported.
Some of the registers noted RW in the manual are read only in the binding.
## Discovery
This extension does not support autodiscovery. The things need to be added manually.
## Thing Configuration
You first need to set up a TCP Modbus bridge according to the Modbus documentation.
A typical modbus bridge configuration would look like this:
```java
Bridge modbus:tcp:bridge [ host="10.0.0.2", port=502, id=1 ]
```
You then add the things of the Lambda Heat Pump system as part of the modbus binding.
Things in this extension will use the selected bridge to connect to the device.
The following parameters are valid for all things:
| Parameter | Type | Required | Default if omitted | Description |
| --------- | ------- | -------- | ------------------ | -------------------------------------------------------------------------- |
| refresh | integer | no | 30 | Poll interval in seconds. Increase this if you encounter connection errors |
| maxTries | integer | no | 3 | Number of retries when before giving up reading from this thing. |
Heat Pump, Boiler, Buffer and Heating Circuit things use another parameter Subindex to add one or more things of this type:
| Parameter | Type | Required | Default if omitted | Description |
| --------- | ------- | -------- | ------------------ | -------------------------------------------------------------------------- |
| Subindex | integer | yes | 0 | Subindex for things of the same thing type, starting with 0 |
| Thing Type | Range |
| ---------------- | ------- |
| Heatpump | 0..2 |
| Boiler | 0..4 |
| Buffer | 0..4 |
| Solar | 0..2 |
| Heating Circuit | 0..11 |
## Channels
Channels within the things are grouped into channel groups.
### General:
### Ambient Group
| Channel ID | Item Type | Read only | Description |
| -------------------------------- | ------------------ | --------- | ------------------------------------------------------------------------ |
| ambient-error-number | Number | true | Ambient Error Number (0 = No error) |
| ambient-operating-state | Number | true | Ambient Operating State (0 = OFF, 1 = AUTOMATIC, 2 = MANUAL, 3 = ERROR) |
| actual-ambient-temperature | Number:Temperature | false | Actual Ambient Temperature |
| average-ambient-temperature | Number:Temperature | true | Arithmetic average temperature of the last 60 minutes |
| calculated-ambient-temperature | Number:Temperature | true | Temperature for calculations in heat distribution modules |
### E-Manager Group
This group contains parameters signaling the PV excess to the heat pump.
| Channel ID | Item Type | Read only | Description |
| -------------------------------- | ------------------ | --------- | -------------------------------------------------------------------------------------- |
| emanager-error-number | Number | true | E-Manager Error Number (0 = No error) |
| emanager-operating-state | Number | true | E-Manager Operating State (0 = OFF, 1 = AUTOMATIC, 2 = MANUAL, 3 = ERROR 4 = OFFLINE |
| actual-power | Number:Power | false | Actual excess power 0 W .. 65535 W |
| actual-power-signed | Number:Power | false | Actual excess power -32768 W .. 32767 W |
| actual-power-consumption | Number:Power | true | Power consumption of heat-pump (only valid when Betriebsart: Automatik, 0 W otherwise) |
| power-consumption-setpoint | Number:Power | false | Power consumption setpoint for heat pump 1 |
### Heat Pump Group
This group contains general operational information about the heat pump itself.
| Channel ID | Item Type | Read only | Description |
| -------------------------------| --------------------------| --------- | ----------------------------------------------------------------------- |
| heat-pump-error-state | Number | true | Error state (0 = NONE, 1 = MESSAGE, 2 = WARNING, 3 = ALARM, 4 = FAULT) |
| heat-pump-error-number | Number | true | Error number: scrolling through all active error numbers (1..99) |
| heat-pump-state | Number | true | State: See Modbus description manual, link above |
| heat-pump-operating-state | Number | true | Operating State: See Modbus description manual, link above |
| heat-pump-t-flow | Number:Temperature | true | Flow line termperature |
| heat-pump-t-return | Number:Temperature | true | Return line temperature |
| heat-pump-vol-sink | Number:VolumetricFlowRate | true | Volume flow heat sink |
| heat-pump-t-eqin | Number:Temperature | true | Energy source inlet temperature |
| heat-pump-t-eqout | Number:Temperature | true | Energy source outlet temperature |
| heat-pump-vol-source | Number:VolumetricFlowRate | true | Volume flow energy source |
| heat-pump-compressor-rating | Number | true | Compressor unit rating |
| heat-pump-qp-heating | Number:Power | true | Actual heating capacity |
| heat-pump-fi-power-consumption | Number:Power | true | Frequency inverter actual power consumption |
| heat-pump-cop | Number | true | Coefficient of performance |
| heat-pump-request-password | Number | false | Password register to release modbus request registers |
| heat-pump-request-type | Number: | false | Request Type |
| heat-pump-request-t-flow | Number:Temperature | false | Requested flow line termperature |
| heat-pump-request-t-return | Number:Temperature | false | Requested return line temperature |
| heat-pump-request-heat-sink | Number:Temperature | false | Requested temperature difference between flow and return line |
| heat-pump-relais-state | Number:Temperature | true | Heatpump Relais State for 2nd heating stage |
| heat-pump-vdae | Number:Energy | true | Accumulated electrical energy consumption of compressor unit |
| heat-pump-vdaq | Number:Energy | true | Accumulated thermal energy output of compressor unit |
### Boiler Group
This group contains information about the boiler for the water for domestic use / tap water / washwater.
| Channel ID | Item Type | Read only | Description |
| ------------------------------------- | ------------------ | --------- | ------------------------------------------------------------------- |
| boiler-error-number | Number | true | Boiler Error Number (0 = No error) |
| boiler-operating-state | Number | true | Boiler Operating State: See Modbus description manual, link above |
| boiler-actual-high-temperature | Number:Temperature | true | Actual temperature boiler high sensor |
| boiler-actual-low-temperature | Number:Temperature | true | Actual temperature boiler low sensor |
| maximum-boiler-temperature | Number:Temperature | false | Setting for maximum boiler temperature (min = 25.0°C; max = 65.0°C) |
### Buffer Group
This group contains information about the buffer for the heating circuit.
| Channel ID | Item Type | Read only | Description |
| ------------------------------------------------| ------------------ | --------- | ---------------------------------------------------------------------|
| buffer-error-number | Number | true | Buffer Error Number (0 = No error) |
| buffer-operating-state | Number | true | Buffer Operating State: See Modbus description manual, link above |
| buffer-actual-high-temperature | Number:Temperature | true | Actual temperature buffer high sensor |
| buffer-actual-low-temperature | Number:Temperature | true | Actual temperature buffer low sensor |
| buffer-actual-modbus-temperature | Number:Temperature | false | Actual temperature set via modbus |
| buffer-request-type | Number | false | Request Type: See Modbus description manual, link above |
| buffer-request-flow-line-temperature | Number:Temperature | false | Requested flow line temperature |
| buffer-request-return-line-temperature | Number:Temperature | false | Requested return line temperature |
| buffer-request-heat-sink-temperature-difference | Number:Temperature | false | Requested temperature difference between flow line and return line |
| buffer-request-heating-capacity | Number:Power | false | Requested capacity |
| maximum-buffer-temperature | Number:Temperature | false | Setting for maximum buffer temperature ) |
### Solar Group
This group contains information about the solar thermic component.
| Channel ID | Item Type | Read only | Description |
| ------------------------------ | ------------------ | --------- | ------------------------------------------------------------------- |
| solar-error-number | Number | true | Solar Error Number (0 = No error) |
| solar-operating-state | Number | true | Solar Operating State: See Modbus description manual, link above |
| solar-collector-temperature | Number:Temperature | true | Temperature of the solar collector |
| solar-storage-temperature | Number:Temperature | true | Temperature of the solar storage |
| solar-pump-speed | Number | true | Speed of the solar pump |
| solar-heat-quantity | Number:Energy | true | Heat quantity produced by solar |
| solar-power-output | Number:Power | true | Current power output of solar |
| solar-operating-hours | Number | true | Operating hours of solar component |
### Heating Circuit Group
This group contains general operational information about the heating circuit.
| Channel ID | Item Type | Read only | Description |
| ---------------------------------------------- | -------------------| --------- | ----------------------------------------------------------- |
| heating-circuit-error-number | Number | true | Error Number (0 = No error) |
| heating-circuit-operating-state | Number | true | Operating State: See Modbus description manual, link above |
| heating-circuit-flow-line-temperature | Number:Temperature | true | Actual temperature flow line sensor |
| heating-circuit-return-line-temperature | Number:Temperature | true | Actual temperature return line sensor |
| heating-circuit-room-device-temperature | Number:Temperature | false | Actual temperature room device sensor |
| heating-circuit-setpoint-flow-line-temperature | Number:Temperature | false | Setpoint temperature flow line |
| heating-circuit-operating-mode | Number | false | Operating Mode: See Modbus description manual, link above |
| heating-circuit-target-temperature-flow-line | Number:Temperature | false | Setpoin temperature flow line (min = 15.0°C; max = 65.0°C) |
| heating-circuit-offset-flow-line-temperature | Number:Temperature | false | Setting for flow line temperature setpoint offset |
| heating-circuit-room-heating-temperature | Number:Temperature | false | Setting for heating mode room setpoint temperature |
| heating-circuit-room-cooling-temperature | Number:Temperature | false | Setting for cooling mode room setpoint temperature |
## Full Example
### `demo.things` Example
```java
Bridge modbus:tcp:Bridge "Lambda Modbus TCP Bridge" [ host="192.168.223.83", port=502, id=1, enableDiscovery=false ] {
Thing general lambdageneral "Lambda General" (modbus:tcp:Bridge) [ refresh=60, subindex=0 ]
Thing heat-pump lambdaheat-pump "Lambda Heatpump" (modbus:tcp:Bridge) [ refresh=60, subindex=0 ]
Thing boiler lambdaboiler "Lambda Boiler" (modbus:tcp:Bridge) [ refresh=60, subindex=0 ]
Thing buffer lambdabuffer "Lambda Buffer" (modbus:tcp:Bridge) [ refresh=60, subindex=0 ]
Thing solar lambdasolar "Lambda Solar" (modbus:tcp:Bridge) [ refresh=60, subindex=0 ]
}
```
### Items Lambda Boiler
```java
Number lambdaboiler_errornumber "Boiler Error Number" (lambdaboiler) { channel="modbus:boiler:Lambda_Bridge:lambdaboiler:boiler-group#boiler-error-number" }
Number lambdaboiler_operatingstate "Boiler Operating State" (lambdaboiler) { channel="modbus:boiler:Lambda_Bridge:lambdaboiler:boiler-group#boiler-operating-state" }
Number:Temperature lambdaboiler_actualhightemperature "Boiler Actual High Temperature" (lambdaboiler) { channel="modbus:boiler:Lambda_Bridge:lambdaboiler:boiler-group#boiler-actual-high-temperature" }
Number:Temperature lambdaboiler_actuallowtemperature "Boiler Actual Low Temperature" (lambdaboiler) { channel="modbus:boiler:Lambda_Bridge:lambdaboiler:boiler-group#boiler-actual-low-temperature" }
Number:Temperature lambdaboiler_maximumboilertemperature "Maximum Boiler Temperature" (lambdaboiler) { channel="modbus:boiler:Lambda_Bridge:lambdaboiler:boiler-group#maximum-boiler-temperature" }
```
### Items Lambda Buffer
```java
Number lambdabuffer_errornumber "Buffer Error Number" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-error-number" }
Number lambdabuffer_operatingstate "Buffer Operating State" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-operating-state" }
Number:Temperature lambdabuffer_actualhightemperature "Buffer Actual High Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-actual-high-temperature" }
Number:Temperature lambdabuffer_actuallowtemperature "Buffer Actual Low Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-actual-low-temperature" }
Number:Temperature lambdabuffer_actualmodbustemperature "Actual Modbus Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-actual-modbus-temperature" }
Number lambdabuffer_requesttype "Request Type" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-request-type" }
Number:Temperature lambdabuffer_requestflowlinetemperature "Request Flow Line Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-request-flow-line-temperature" }
Number:Temperature lambdabuffer_requestreturnlinetemperature "Request Return Line Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-request-return-line-temperature" }
Number:Temperature lambdabuffer_requestheatsinktemperature "Requested Heat Sink Temperature Difference" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-request-heat-sink-temperature" }
Number:Power lambdabuffer_requestheatingcapacity "Requested Heating Capacity" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#buffer-request-heating-capacity" }
Number:Temperature lambdabuffer_maximumbuffertemperature "Maximum Buffer Temperature" (lambdabuffer) { channel="modbus:buffer:Lambda_Bridge:lambdabuffer:buffer-group#maximum-buffer-temperature" }
```
### Items Lambda General
```java
Number lambdaambient_operatingstate "Ambient Operating State" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:ambient-group#ambient-operating-state" }
Number lambdaambient_errornumber "Ambient Error Number" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:ambient-group#ambient-error-number" }
Number:Temperature lambdaambient_actualambienttemperature "Ambient Actual Temperature" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:ambient-group#actual-ambient-temperature" }
Number:Temperature lambdaambient_averageambienttemperature "Ambient Average Temperature" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:ambient-group#average-ambient-temperature" }
Number:Temperature lambdaambient_calculatedambienttemperature "Ambient Calculated Temperature" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:ambient-group#calculated-ambient-temperature" }
Number lambdaemanager_operatingstate "EManager Operating State" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:emanager-group#emanager-operating-state" }
Number lambdaemanager_errornumber "EManager Error Number" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:emanager-group#emanager-error-number" }
Number:Power lambdaemanager_actualpower "EManager Actual Power" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:emanager-group#actual-power" }
Number:Power lambdaemanager_actualpowerconsumption "EManager Actual Power Consumption" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:emanager-group#actual-power-consumption" }
Number:Power lambdaemanager_powerconsumptionsetpoint "EManager Power Consumption Setpoint" (lambdageneral) { channel="modbus:general:Lambda_Bridge:lambdageneral:emanager-group#power-consumption-setpoint" }
```
### Items Heatingcircuit
```java
Number lambdaheatingcircuit_errornumber "Heating Circuit Error Number" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-error-number" }
Number lambdaheatingcircuit_operatingstate "Heating Circuit Operating State" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-operating-state" }
Number:Temperature lambdaheatingcircuit_flowlinetemperature "Heating Circuit Flow Line Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-flow-line-temperature" }
Number:Temperature lambdaheatingcircuit_returnlinetemperature "Heating Circuit Return Line Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-return-line-temperature" }
Number:Temperature lambdaheatingcircuit_roomdevicetemperature "Heating Circuit Room Device Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-room-device-temperature" }
Number:Temperature lambdaheatingcircuit_setpointflowlinetemperature "Heating Circuit Setpoint Flow Line Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-setpoint-flow-line-temperature" }
Number lambdaheatingcircuit_operatingmode "Heating Circuit Operating Mode" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-operating-mode" }
Number:Temperature lambdaheatingcircuit_offsetflowlinetemperature "Heating Circuit Offset Flow Line Temperature"(lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-offset-flow-line-temperature" }
Number:Temperature lambdaheatingcircuit_roomheatingtemperature "Heating Circuit Room Heating Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-room-heating-temperature" }
Number:Temperature lambdaheatingcircuit_roomcoolingtemperature "Heating Circuit Room Cooling Temperature" (lambdaheatingcircuit) { channel="modbus:heating-circuit:Lambda_Bridge:lambdaheatingcircuit:heating-circuit-group#heating-circuit-room-cooling-temperature" }
```
### Items Lambda Solar
```java
Number lambdasolar_errornumber "Solar Error Number" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-error-number" }
Number lambdasolar_operatingstate "Solar Operating State" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-operating-state" }
Number:Temperature lambdasolar_collectortemperature "Solar Collector Temperature" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-collector-temperature" }
Number:Temperature lambdasolar_storagetemperature "Solar Storage Temperature" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-storage-temperature" }
Number lambdasolar_pumpspeed "Solar Pump Speed" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-pump-speed" }
Number:Energy lambdasolar_heatquantity "Solar Heat Quantity" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-heat-quantity" }
Number:Power lambdasolar_poweroutput "Solar Power Output" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-power-output" }
Number lambdasolar_operatinghours "Solar Operating Hours" (lambdasolar) { channel="modbus:solar:Lambda_Bridge:lambdasolar:solar-group#solar-operating-hours" }
```
### Items Lambda Heatpump
```java
Number lambdaheatpump_errorstate "Heatpump Error State" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-error-state" }
Number lambdaheatpump_errornumber "Heatpump Error Number" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-error-number" }
Number lambdaheatpump_state "Heatpump State" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-state" }
Number lambdaheatpump_operatingstate "Heatpump Operating State" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-operating-state" }
Number:Temperature lambdaheatpump_tflow "Heatpump Flow Line Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-t-flow" }
Number:Temperature lambdaheatpump_treturn "Heatpump Return Line Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-t-return" }
Number:VolumetricFlowRate lambdaheatpump_volsink "Heatpump Volume Flow Heat Sink" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-vol-sink" }
Number:Temperature lambdaheatpump_teqin "Heatpump Energy Source Inlet Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-t-eqin" }
Number:Temperature lambdaheatpump_teqout "Heatpump Energy Source Outlet Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-t-eqout" }
Number:VolumetricFlowRate lambdaheatpump_volsource "Heatpump Volume Flow Energy Source" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-vol-source" }
Number lambdaheatpump_compressorrating "Heatpump Compressor Rating" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-compressor-rating" }
Number:Power lambdaheatpump_qpheating "Heatpump Actual Heating Capacity" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-qp-heating" }
Number:Power lambdaheatpump_fipowerconsumption "Heatpump Frequency inverter Actual Power Consumption" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-fi-power-consumption" }
Number lambdaheatpump_cop "Heatpump COP" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-cop" }
Number lambdaheatpump_requestpassword "Heatpump Request Password" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-request-password" }
Number lambdaheatpump_requesttype "Heatpump Request Type" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-request-type" }
Number lambdaheatpump_requesttflow "Heatpump Requested Flow Line Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-request-t-flow" }
Number lambdaheatpump_requesttreturn "Heatpump Requested Return Line Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-request-t-return" }
Number lambdaheatpump_requestheatsink "Heatpump Requested Heat Sink Temperature" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-request-heat-sink" }
Number lambdaheatpump_relaisstate "Heatpump Relais State" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-relais-state" }
Number:Energy lambdaheatpump_vdae "Heatpump Accumulated Electrical Energy consumption" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-vdae" }
Number:Energy lambdaheatpump_vdaq "Heatpump Accumulated Thermical Energy consumption" (lambdaheatpump) { channel="modbus:heat-pump:Lambda_Bridge:lambdaheatpumpheat-pump-group#heat-pump-vdaq" }
```
### Example: (DSL) Send Power value the E-Manager of the Lambda Heat Pump
'''
// Sending Value to Heatpump
// Script has to send a value about every 30 seconds, for example with cron settings.
// Calculate power_to_heat-pump using your data provided by the PV system.
// var int power_to_heat-pump = ((lambdaemanager_actualpowerconsumption.state as Number) - (PW_Battery.state as Number) - (PW_Grid.state as Number)).intValue
var int power_to_heat-pump = 1000
lambdaemanager_actualpower.sendCommand(power_to_heat-pump)
'''
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.modbus.lambda</artifactId>
<name>openHAB Add-ons :: Bundles :: Lambda Binding</name>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link BoilerConfiguration} class contains fields mapping
* thing configuration parameters.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class BoilerConfiguration extends GeneralConfiguration {
/**
* Subindex to calculate the base adress of the modbus registers
*/
private int subindex = 0;
public int getSubindex() {
return subindex;
}
public void setSubindex(int subindex) {
this.subindex = subindex;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link BufferConfiguration} class contains fields mapping
* thing configuration parameters.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class BufferConfiguration extends GeneralConfiguration {
/**
* Subindex to calculate the base adress of the modbus registers
*/
private int subindex = 0;
public int getSubindex() {
return subindex;
}
public void setSubindex(int subindex) {
this.subindex = subindex;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link GeneralConfiguration} class contains fields mapping
* thing configuration parameters.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class GeneralConfiguration {
/**
* Refresh interval in seconds
*/
private int refresh = 30;
private int maxTries = 3;// backwards compatibility and tests
/**
* Gets refresh period in milliseconds
*/
public long getRefreshMillis() {
return refresh * 1000;
}
public int getMaxTries() {
return maxTries;
}
public void setMaxTries(int maxTries) {
this.maxTries = maxTries;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HeatingCircuitConfiguration} class contains fields mapping
* thing configuration parameters.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class HeatingCircuitConfiguration extends GeneralConfiguration {
/**
* Subindex to calculate the base adress of the modbus registers
*/
private int subindex = 0;
public int getSubindex() {
return subindex;
}
public void setSubindex(int subindex) {
this.subindex = subindex;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HeatpumpConfiguration} class contains fields mapping
* thing configuration parameters.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class HeatpumpConfiguration extends GeneralConfiguration {
/**
* Subindex to calculate the base adress of the modbus registers
*/
private int subindex = 0;
public int getSubindex() {
return subindex;
}
public void setSubindex(int subindex) {
this.subindex = subindex;
}
}
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.ModbusBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link LambdaBindingConstants} class defines common
* constants, which are used across the whole binding.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class LambdaBindingConstants {
private static final String BINDING_ID = ModbusBindingConstants.BINDING_ID;
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_GENERAL = new ThingTypeUID(BINDING_ID, "general");
public static final ThingTypeUID THING_TYPE_BOILER = new ThingTypeUID(BINDING_ID, "boiler");
public static final ThingTypeUID THING_TYPE_BUFFER = new ThingTypeUID(BINDING_ID, "buffer");
public static final ThingTypeUID THING_TYPE_HEAT_PUMP = new ThingTypeUID(BINDING_ID, "heat-pump");
public static final ThingTypeUID THING_TYPE_SOLAR = new ThingTypeUID(BINDING_ID, "solar");
public static final ThingTypeUID THING_TYPE_HEATING_CIRCUIT = new ThingTypeUID(BINDING_ID, "heating-circuit");
// Channel group ids
public static final String GROUP_GENERAL_AMBIENT = "ambient-group";
public static final String GROUP_GENERAL_EMANAGER = "emanager-group";
public static final String GROUP_HEAT_PUMP = "heat-pump-group";
public static final String GROUP_BOILER = "boiler-group";
public static final String GROUP_BOILER_REG50 = "boiler-reg50-group";
public static final String GROUP_BUFFER = "buffer-group";
public static final String GROUP_BUFFER_REG50 = "buffer-reg50-group";
public static final String GROUP_SOLAR = "solar-group";
public static final String GROUP_SOLAR_REG50 = "solar-reg50-group";
public static final String GROUP_HEATING_CIRCUIT = "heating-circuit-group";
public static final String GROUP_HEATING_CIRCUIT_REG50 = "heating-circuit-reg50-group";
// List of all Channel ids in device information group
// General Ambient
public static final String CHANNEL_AMBIENT_ERROR_NUMBER = "ambient-error-number";
public static final String CHANNEL_AMBIENT_OPERATING_STATE = "ambient-operating-state";
public static final String CHANNEL_ACTUAL_AMBIENT_TEMPERATURE = "actual-ambient-temperature";
public static final String CHANNEL_AVERAGE_AMBIENT_TEMPERATURE = "average-ambient-temperature";
public static final String CHANNEL_CALCULATED_AMBIENT_TEMPERATURE = "calculated-ambient-temperature";
// General E-manager
public static final String CHANNEL_EMANAGER_ERROR_NUMBER = "emanager-error-number";
public static final String CHANNEL_EMANAGER_OPERATING_STATE = "emanager-operating-state";
public static final String CHANNEL_EMANAGER_ACTUAL_POWER = "emanager-actual-power";
public static final String CHANNEL_EMANAGER_ACTUAL_POWER_SIGNED = "emanager-actual-power-signed";
public static final String CHANNEL_EMANAGER_ACTUAL_POWER_CONSUMPTION = "emanager-actual-power-consumption";
public static final String CHANNEL_POWER_CONSUMPTION_SETPOINT = "emanager-power-consumption-setpoint";
// Heatpump
public static final String CHANNEL_HEAT_PUMP_ERROR_STATE = "heat-pump-error-state";
public static final String CHANNEL_HEAT_PUMP_ERROR_NUMBER = "heat-pump-error-number";
public static final String CHANNEL_HEAT_PUMP_STATE = "heat-pump-state";
public static final String CHANNEL_HEAT_PUMP_OPERATING_STATE = "heat-pump-operating-state";
public static final String CHANNEL_HEAT_PUMP_T_FLOW = "heat-pump-t-flow";
public static final String CHANNEL_HEAT_PUMP_T_RETURN = "heat-pump-t-return";
public static final String CHANNEL_HEAT_PUMP_VOL_SINK = "heat-pump-vol-sink";
public static final String CHANNEL_HEAT_PUMP_T_EQIN = "heat-pump-t-eqin";
public static final String CHANNEL_HEAT_PUMP_T_EQOUT = "heat-pump-t-eqout";
public static final String CHANNEL_HEAT_PUMP_VOL_SOURCE = "heat-pump-vol-source";
public static final String CHANNEL_HEAT_PUMP_COMPRESSOR_RATING = "heat-pump-compressor-rating";
public static final String CHANNEL_HEAT_PUMP_QP_HEATING = "heat-pump-qp-heating";
public static final String CHANNEL_HEAT_PUMP_FI_POWER_CONSUMPTION = "heat-pump-fi-power-consumption";
public static final String CHANNEL_HEAT_PUMP_COP = "heat-pump-cop";
public static final String CHANNEL_HEAT_PUMP_REQUEST_PASSWORD = "heat-pump-request-password";
public static final String CHANNEL_HEAT_PUMP_REQUEST_TYPE = "heat-pump-request-type";
public static final String CHANNEL_HEAT_PUMP_REQUEST_T_FLOW = "heat-pump-request-t-flow";
public static final String CHANNEL_HEAT_PUMP_REQUEST_T_RETURN = "heat-pump-request-t-return";
public static final String CHANNEL_HEAT_PUMP_REQUEST_HEAT_SINK = "heat-pump-request-heat-sink";
public static final String CHANNEL_HEAT_PUMP_RELAIS_STATE = "heat-pump-relais-state";
public static final String CHANNEL_HEAT_PUMP_VDAE = "heat-pump-vdae";
public static final String CHANNEL_HEAT_PUMP_VDAQ = "heat-pump-vdaq";
// Boiler
public static final String CHANNEL_BOILER_ERROR_NUMBER = "boiler-error-number";
public static final String CHANNEL_BOILER_OPERATING_STATE = "boiler-operating-state";
public static final String CHANNEL_BOILER_ACTUAL_HIGH_TEMPERATURE = "boiler-actual-high-temperature";
public static final String CHANNEL_BOILER_ACTUAL_LOW_TEMPERATURE = "boiler-actual-low-temperature";
public static final String CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE = "maximum-boiler-temperature";
// Buffer
public static final String CHANNEL_BUFFER_ERROR_NUMBER = "buffer-error-number";
public static final String CHANNEL_BUFFER_OPERATING_STATE = "buffer-operating-state";
public static final String CHANNEL_BUFFER_ACTUAL_HIGH_TEMPERATURE = "buffer-actual-high-temperature";
public static final String CHANNEL_BUFFER_ACTUAL_LOW_TEMPERATURE = "buffer-actual-low-temperature";
public static final String CHANNEL_BUFFER_ACTUAL_MODBUS_TEMPERATURE = "buffer-actual-modbus-temperature";
public static final String CHANNEL_BUFFER_REQUEST_TYPE = "buffer-request-type";
public static final String CHANNEL_BUFFER_REQUEST_FLOW_LINE_TEMPERATURE = "buffer-request-flow-line-temperature";
public static final String CHANNEL_BUFFER_REQUEST_RETURN_LINE_TEMPERATURE = "buffer-request-return-line-temperature";
public static final String CHANNEL_BUFFER_REQUEST_HEAT_SINK_TEMPERATURE = "buffer-request-heat-sink-temperature-difference";
public static final String CHANNEL_BUFFER_REQUEST_HEATING_CAPACITY = "buffer-request-heating-capacity";
public static final String CHANNEL_BUFFER_MAXIMUM_BUFFER_TEMPERATURE = "maximum-buffer-temperature";
// Solar
public static final String CHANNEL_SOLAR_ERROR_NUMBER = "solar-error-number";
public static final String CHANNEL_SOLAR_OPERATING_STATE = "solar-operating-state";
public static final String CHANNEL_SOLAR_COLLECTOR_TEMPERATURE = "solar-collector-temperature";
public static final String CHANNEL_SOLAR_BUFFER1_TEMPERATURE = "solar-buffer1-temperature";
public static final String CHANNEL_SOLAR_BUFFER2_TEMPERATURE = "solar-buffer2-temperature";
public static final String CHANNEL_SOLAR_MAXIMUM_BUFFER_TEMPERATURE = "solar-maximum-buffer-temperature";
public static final String CHANNEL_SOLAR_BUFFER_CHANGEOVER_TEMPERATURE = "solar-buffer-changeover-temperature";
// Heating Circuit
public static final String CHANNEL_HEATING_CIRCUIT_ERROR_NUMBER = "heating-circuit-error-number";
public static final String CHANNEL_HEATING_CIRCUIT_OPERATING_STATE = "heating-circuit-operating-state";
public static final String CHANNEL_HEATING_CIRCUIT_FLOW_LINE_TEMPERATURE = "heating-circuit-flow-line-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_RETURN_LINE_TEMPERATURE = "heating-circuit-return-line-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_ROOM_DEVICE_TEMPERATURE = "heating-circuit-room-device-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_SETPOINT_FLOW_LINE_TEMPERATURE = "heating-circuit-setpoint-flow-line-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_OPERATING_MODE = "heating-circuit-operating-mode";
public static final String CHANNEL_HEATING_CIRCUIT_TARGET_TEMPERATURE_FLOW_LINE = "heating-circuit-target-temperature-flow-line";
public static final String CHANNEL_HEATING_CIRCUIT_OFFSET_FLOW_LINE_TEMPERATURE = "heating-circuit-offset-flow-line-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_ROOM_HEATING_TEMPERATURE = "heating-circuit-room-heating-temperature";
public static final String CHANNEL_HEATING_CIRCUIT_ROOM_COOLING_TEMPERATURE = "heating-circuit-room-cooling-temperature";
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.lambda.internal.handler.BoilerHandler;
import org.openhab.binding.modbus.lambda.internal.handler.BufferHandler;
import org.openhab.binding.modbus.lambda.internal.handler.GeneralHandler;
import org.openhab.binding.modbus.lambda.internal.handler.HeatingCircuitHandler;
import org.openhab.binding.modbus.lambda.internal.handler.HeatpumpHandler;
import org.openhab.binding.modbus.lambda.internal.handler.SolarHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LambdaHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
@Component(configurationPid = "binding.lambda", service = ThingHandlerFactory.class)
public class LambdaHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(LambdaHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GENERAL, THING_TYPE_HEAT_PUMP,
THING_TYPE_BOILER, THING_TYPE_BUFFER, THING_TYPE_HEATING_CIRCUIT, THING_TYPE_SOLAR);
private static final Map<ThingTypeUID, Function<Thing, ThingHandler>> HANDLER_FACTORY_MAP = Map.of(
THING_TYPE_HEAT_PUMP, HeatpumpHandler::new, THING_TYPE_GENERAL, GeneralHandler::new, THING_TYPE_BUFFER,
BufferHandler::new, THING_TYPE_BOILER, BoilerHandler::new, THING_TYPE_HEATING_CIRCUIT,
HeatingCircuitHandler::new, THING_TYPE_SOLAR, SolarHandler::new);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
logger.debug("LambdaHandlerFactory thingTypeUID: {}", thingTypeUID);
Function<Thing, ThingHandler> factory = HANDLER_FACTORY_MAP.get(thingTypeUID);
return factory != null ? factory.apply(thing) : null;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SolarConfiguration} class contains fields mapping
* thing configuration parameters for the solar handler.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class SolarConfiguration extends GeneralConfiguration {
/**
* Subindex for multiple solar components
*/
private int subindex = 0;
public int getSubindex() {
return subindex;
}
public void setSubindex(int subindex) {
this.subindex = subindex;
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the Ambient Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class AmbientBlock {
public int ambientErrorNumber;
public int ambientOperatingState;
public int actualAmbientTemperature;
public int averageAmbientTemperature;
public int calculatedAmbientTemperature;
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the Boiler Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class BoilerBlock {
public int boilerErrorNumber;
public int boilerOperatingState;
public int boilerActualHighTemperature;
public int boilerActualLowTemperature;
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the BoilerReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class BoilerReg50Block {
public int boilerMaximumBoilerTemperature;
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the Buffer Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class BufferBlock {
public int bufferErrorNumber;
public int bufferOperatingState;
public int bufferActualHighTemperature;
public int bufferActualLowTemperature;
public int bufferActualModbusTemperature;
public int bufferRequestType;
public int bufferrequestFlowLineTemperature;
public int bufferrequestReturnLineTemperature;
public int bufferrequestHeatSinkTemperature;
public int bufferrequestHeatingCapacity;
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the BufferReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class BufferReg50Block {
public int bufferMaximumBufferTemperature;
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the EManager Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class EManagerBlock {
public int emanagerErrorNumber;
public int emanagerOperatingState;
public int emanagerActualPower;
public int emanagerActualPowerSigned;
public int emanagerActualPowerConsumption;
public int emanagerPowerConsumptionSetpoint;
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the HeatingCircuit Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class HeatingCircuitBlock {
public int heatingcircuitErrorNumber;
public int heatingcircuitOperatingState;
public int heatingcircuitFlowLineTemperature;
public int heatingcircuitReturnLineTemperature;
public int heatingcircuitRoomDeviceTemperature;
public int heatingcircuitSetpointFlowLineTemperature;
public int heatingcircuitOperatingMode;
public int heatingcircuitTargetTemperatureFlowLine;
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the HeatingCircuitReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class HeatingCircuitReg50Block {
public int heatingcircuitOffsetFlowLineTemperature;
public int heatingcircuitRoomHeatingTemperature;
public int heatingcircuitRoomCoolingTemperature;
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the Heatpump Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class HeatpumpBlock {
public int heatpumpErrorState;
public int heatpumpErrorNumber;
public int heatpumpState;
public int heatpumpOperatingState;
public int heatpumpTFlow;
public int heatpumpTReturn;
public int heatpumpVolSink;
public int heatpumpTEQin;
public int heatpumpTEQout;
public int heatpumpVolSource;
public int heatpumpCompressorRating;
public int heatpumpQpHeating;
public int heatpumpFIPowerConsumption;
public int heatpumpCOP;
public int heatpumpRequestPassword;
public int heatpumpRequestType;
public int heatpumpRequestTFlow;
public int heatpumpRequestTReturn;
public int heatpumpRequestHeatSink;
public int heatpumpRelaisState;
public long heatpumpVdAE;
public long heatpumpVdAQ;
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Data transfer object for solar thermic component data
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus to add handler
*/
public class SolarBlock {
public int solarErrorNumber;
public int solarOperatingState;
public int solarCollectorTemperature;
public int solarBuffer1Temperature;
public int solarBuffer2Temperature;
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.dto;
/**
* Dto class for the SolarReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
public class SolarReg50Block {
public int solarMaximumBufferTemperature;
public int solarBufferChangeoverTemperature;
}
@@ -0,0 +1,531 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.*;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.BoilerConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.BoilerBlock;
import org.openhab.binding.modbus.lambda.internal.dto.BoilerReg50Block;
import org.openhab.binding.modbus.lambda.internal.parser.BoilerBlockParser;
import org.openhab.binding.modbus.lambda.internal.parser.BoilerReg50BlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BoilerHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class BoilerHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private final Logger logger = LoggerFactory.getLogger(BoilerHandler.class);
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = BoilerHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Register poll task This is where we set up our regular poller
*/
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
logger.debug("Setting up regular polling Address: {}", address);
ModbusCommunicationInterface mycomms = BoilerHandler.this.comms;
BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("Boiler: registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, BoilerHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = BoilerHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(BoilerHandler.class);
/**
* Configuration instance
*/
protected @Nullable BoilerConfiguration config = null;
/**
* Parsers used to convert incoming raw messages into state blocks
*/
private final BoilerBlockParser boilerBlockParser = new BoilerBlockParser();
private final BoilerReg50BlockParser boilerReg50BlockParser = new BoilerReg50BlockParser();
/**
* These are the tasks used to poll the device
*/
private volatile @Nullable AbstractBasePoller boilerPoller = null;
private volatile @Nullable AbstractBasePoller boilerReg50Poller = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
*/
public BoilerHandler(Thing thing) {
super(thing);
}
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config);
ModbusCommunicationInterface mycomms = BoilerHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
BoilerHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
BoilerHandler.this.handleWriteError(failure);
});
}
private short getScaledInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
if (c != null) {
return (short) (c.doubleValue() * 10);
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return (short) (c.doubleValue() * 10);
}
throw new LambdaException("Unsupported command type");
}
/**
* Handle incoming commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_BOILER:
poller = boilerPoller;
break;
case GROUP_BOILER_REG50:
poller = boilerReg50Poller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("Boiler: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_BOILER_REG50.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE:
writeInt16(reg50baseadress, getScaledInt16Value(command));
break;
}
}
}
catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
/**
* Initialization: Load the config object of the block Connect to the slave
* bridge Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(BoilerConfiguration.class);
startUp();
}
/**
* Adresses for the polling registers, used for reading and writing
*/
private int baseadress;
private int reg50baseadress;
/**
* This method starts the operation of this handler Connect to the slave bridge
* Start the periodic polling
*/
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
logger.debug("Boiler: Error setting up SlaveId");
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of boiler is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("CommunicationInterface config of boiler is null, Thing & Bridge are offline");
return;
}
BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config);
baseadress = 2000 + 100 * myconfig.getSubindex();
reg50baseadress = baseadress + 50;
if (boilerPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledBoilerData(registers);
}
};
poller.registerPollTask(baseadress, 4, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
boilerPoller = poller;
}
if (boilerReg50Poller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledBoilerReg50Data(registers);
}
};
poller.registerPollTask(reg50baseadress, 1, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
boilerReg50Poller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll tasks and release the endpoint reference
*/
private void tearDown() {
AbstractBasePoller poller = boilerPoller;
if (poller != null) {
poller.unregisterPollTask();
boilerPoller = null;
}
poller = boilerReg50Poller;
if (poller != null) {
poller.unregisterPollTask();
boilerReg50Poller = null;
}
comms = null;
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to Checks
* that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected State getUnscaled(Number value, Unit<?> unit) {
return QuantityType.valueOf(value.doubleValue(), unit);
}
/**
* Returns high value * 1000 + low value
*
* @param high the high value
*
* @param low the low value
*
* @return the scaled value as a DecimalType
*/
protected State getEnergyQuantity(int high, int low) {
double value = high * 1000 + low;
return QuantityType.valueOf(value, KILOWATT_HOUR);
}
/**
* These methods are called each time new data has been polled from the modbus
* slave The register array is first parsed, then each of the channels are
* updated to the new values
*
* @param registers byte array read from the modbus slave
*/
protected void handlePolledBoilerData(ModbusRegisterArray registers) {
BoilerBlock block = boilerBlockParser.parse(registers);
// Boiler group
updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ERROR_NUMBER), new DecimalType(block.boilerErrorNumber));
updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_OPERATING_STATE),
new DecimalType(block.boilerOperatingState));
updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_HIGH_TEMPERATURE),
getScaled(block.boilerActualHighTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_LOW_TEMPERATURE),
getScaled(block.boilerActualLowTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
protected void handlePolledBoilerReg50Data(ModbusRegisterArray registers) {
BoilerReg50Block block = boilerReg50BlockParser.parse(registers);
// BoilerReg50 groupaximumBoilerTemperature: {}",
updateState(channelUID(GROUP_BOILER_REG50, CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE),
getScaled(block.boilerMaximumBoilerTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
/**
* @param bridgeStatusInfo
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Handle errors received during communication
*/
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
*
* @param string the channel id in that group
*
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,582 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.*;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.BufferConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.BufferBlock;
import org.openhab.binding.modbus.lambda.internal.dto.BufferReg50Block;
import org.openhab.binding.modbus.lambda.internal.parser.BufferBlockParser;
import org.openhab.binding.modbus.lambda.internal.parser.BufferReg50BlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BufferHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class BufferHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = BufferHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Register poll task This is where we set up our regular poller
*/
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
ModbusCommunicationInterface mycomms = BufferHandler.this.comms;
BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, BufferHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = BufferHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(BufferHandler.class);
/**
* Configuration instance
*/
protected @Nullable BufferConfiguration config = null;
/**
* Parsers used to convert incoming raw messages into state blocks
*/
private final BufferBlockParser bufferBlockParser = new BufferBlockParser();
private final BufferReg50BlockParser bufferReg50BlockParser = new BufferReg50BlockParser();
/**
* These are the tasks used to poll the device
*/
private volatile @Nullable AbstractBasePoller bufferPoller = null;
private volatile @Nullable AbstractBasePoller bufferReg50Poller = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
*/
public BufferHandler(Thing thing) {
super(thing);
}
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config);
ModbusCommunicationInterface mycomms = BufferHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
BufferHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
BufferHandler.this.handleWriteError(failure);
});
}
/**
* @param command get the value of this command.
*
* @return short the value of the command as short
*/
private short getInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(WATT);
if (c != null) {
return c.shortValue();
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return c.shortValue();
}
throw new LambdaException("Unsupported command type");
}
private short getScaledInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
if (c != null) {
return (short) (c.doubleValue() * 10);
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return (short) (c.doubleValue() * 10);
}
throw new LambdaException("Unsupported command type");
}
/**
* Handle incoming commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_BUFFER:
poller = bufferPoller;
break;
case GROUP_BUFFER_REG50:
poller = bufferReg50Poller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("Buffer: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_BUFFER.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_BUFFER_ACTUAL_MODBUS_TEMPERATURE:
writeInt16(baseadress + 4, getScaledInt16Value(command));
break;
case CHANNEL_BUFFER_REQUEST_TYPE:
writeInt16(baseadress + 5, getInt16Value(command));
break;
case CHANNEL_BUFFER_REQUEST_FLOW_LINE_TEMPERATURE:
writeInt16(baseadress + 6, getScaledInt16Value(command));
break;
case CHANNEL_BUFFER_REQUEST_RETURN_LINE_TEMPERATURE:
writeInt16(baseadress + 7, getScaledInt16Value(command));
break;
case CHANNEL_BUFFER_REQUEST_HEAT_SINK_TEMPERATURE:
writeInt16(baseadress + 8, getScaledInt16Value(command));
break;
case CHANNEL_BUFFER_REQUEST_HEATING_CAPACITY:
writeInt16(baseadress + 9, getScaledInt16Value(command));
break;
}
}
if (GROUP_BUFFER_REG50.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_BUFFER_MAXIMUM_BUFFER_TEMPERATURE:
writeInt16(reg50baseadress, getScaledInt16Value(command));
break;
}
}
}
catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
/**
* Initialization: Load the config object of the block Connect to the slave
* bridge Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(BufferConfiguration.class);
startUp();
}
/**
* Adresses for the polling registers, used for reading and writing
*/
private int baseadress;
private int reg50baseadress;
/**
* This method starts the operation of this handler Connect to the slave bridge
* Start the periodic polling1
*/
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
logger.debug("Buffer: Error setting up SlaveId");
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of buffer is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("Invalid comms/config/manager ref for Buffer handler");
return;
}
BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config);
baseadress = 3000 + 100 * myconfig.getSubindex();
reg50baseadress = baseadress + 50;
if (bufferPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledBufferData(registers);
}
};
poller.registerPollTask(baseadress, 10, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
bufferPoller = poller;
}
if (bufferReg50Poller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledBufferReg50Data(registers);
}
};
poller.registerPollTask(reg50baseadress, 1, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
bufferReg50Poller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll tasks and release the endpoint reference
*/
private void tearDown() {
AbstractBasePoller poller = bufferPoller;
if (poller != null) {
poller.unregisterPollTask();
bufferPoller = null;
}
poller = bufferReg50Poller;
if (poller != null) {
poller.unregisterPollTask();
bufferReg50Poller = null;
}
comms = null;
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to Checks
* that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected State getUnscaled(Number value, Unit<?> unit) {
return QuantityType.valueOf(value.doubleValue(), unit);
}
/**
* Returns high value * 1000 + low value
*
* @param high the high value
*
* @param low the low value
*
* @return the scaled value as a DecimalType
*/
protected State getEnergyQuantity(int high, int low) {
double value = high * 1000 + low;
return QuantityType.valueOf(value, KILOWATT_HOUR);
}
/**
* These methods are called each time new data has been polled from the modbus
* slave The register array is first parsed, then each of the channels are
* updated to the new values
*
* @param registers byte array read from the modbus slave
*/
protected void handlePolledBufferData(ModbusRegisterArray registers) {
BufferBlock block = bufferBlockParser.parse(registers);
// Buffer group
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_ERROR_NUMBER), new DecimalType(block.bufferErrorNumber));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_OPERATING_STATE),
new DecimalType(block.bufferOperatingState));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_ACTUAL_HIGH_TEMPERATURE),
getScaled(block.bufferActualHighTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_ACTUAL_LOW_TEMPERATURE),
getScaled(block.bufferActualLowTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_ACTUAL_MODBUS_TEMPERATURE),
getScaled(block.bufferActualModbusTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_REQUEST_TYPE), new DecimalType(block.bufferRequestType));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_REQUEST_FLOW_LINE_TEMPERATURE),
getScaled(block.bufferrequestFlowLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_REQUEST_RETURN_LINE_TEMPERATURE),
getScaled(block.bufferrequestReturnLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_REQUEST_HEAT_SINK_TEMPERATURE),
getScaled(block.bufferrequestHeatSinkTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_BUFFER, CHANNEL_BUFFER_REQUEST_HEATING_CAPACITY),
getScaled(block.bufferrequestHeatingCapacity, WATT, 2.0));
resetCommunicationError();
}
protected void handlePolledBufferReg50Data(ModbusRegisterArray registers) {
BufferReg50Block block = bufferReg50BlockParser.parse(registers);
// BufferReg50 group
updateState(channelUID(GROUP_BUFFER_REG50, CHANNEL_BUFFER_MAXIMUM_BUFFER_TEMPERATURE),
getScaled(block.bufferMaximumBufferTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
/**
* @param bridgeStatusInfo
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Handle errors received during communication
*/
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
*
* @param string the channel id in that group
*
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,542 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.*;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.GeneralConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.AmbientBlock;
import org.openhab.binding.modbus.lambda.internal.dto.EManagerBlock;
import org.openhab.binding.modbus.lambda.internal.parser.AmbientBlockParser;
import org.openhab.binding.modbus.lambda.internal.parser.EManagerBlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GeneralHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class GeneralHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = GeneralHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Register poll task This is where we set up our regular poller
*/
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
ModbusCommunicationInterface mycomms = GeneralHandler.this.comms;
GeneralConfiguration myconfig = Objects.requireNonNull(GeneralHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, GeneralHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = GeneralHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(GeneralHandler.class);
/**
* Configuration instance
*/
protected @Nullable GeneralConfiguration config = null;
/**
* Parsers used to convert incoming raw messages into state blocks
*/
private final AmbientBlockParser ambientBlockParser = new AmbientBlockParser();
private final EManagerBlockParser emanagerBlockParser = new EManagerBlockParser();
/**
* These are the tasks used to poll the device
*/
private volatile @Nullable AbstractBasePoller ambientPoller = null;
private volatile @Nullable AbstractBasePoller emanagerPoller = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
*/
public GeneralHandler(Thing thing) {
super(thing);
}
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
GeneralConfiguration myconfig = Objects.requireNonNull(GeneralHandler.this.config);
ModbusCommunicationInterface mycomms = GeneralHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
GeneralHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
GeneralHandler.this.handleWriteError(failure);
});
}
/**
* @param command get the value of this command.
*
* @return short the value of the command as short
*/
private short getInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(WATT);
if (c != null) {
return c.shortValue();
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return c.shortValue();
}
throw new LambdaException("Unsupported command type");
}
/**
* Handle incoming commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_GENERAL_AMBIENT:
poller = ambientPoller;
break;
case GROUP_GENERAL_EMANAGER:
poller = emanagerPoller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("General: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_GENERAL_AMBIENT.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_ACTUAL_AMBIENT_TEMPERATURE:
writeInt16(2, getInt16Value(command));
break;
}
}
if (GROUP_GENERAL_EMANAGER.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_EMANAGER_ACTUAL_POWER:
writeInt16(102, getInt16Value(command));
break;
case CHANNEL_EMANAGER_ACTUAL_POWER_SIGNED:
writeInt16(102, getInt16Value(command));
break;
}
}
}
catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
/**
* Initialization: Load the config object of the block Connect to the slave
* bridge Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(GeneralConfiguration.class);
logger.debug("Initializing thing with properties: {}", thing.getProperties());
startUp();
}
/**
* This method starts the operation of this handler Connect to the slave bridge
* Start the periodic polling1
*/
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
logger.debug("General: Error setting up SlaveId");
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of general section is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("Invalid comms/config/manager ref for lambda general handler");
return;
}
if (ambientPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledAmbientData(registers);
}
};
poller.registerPollTask(0, 5, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
ambientPoller = poller;
}
if (emanagerPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledEManagerData(registers);
}
};
poller.registerPollTask(100, 5, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
emanagerPoller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll tasks and release the endpoint reference
*/
private void tearDown() {
AbstractBasePoller poller = ambientPoller;
if (poller != null) {
poller.unregisterPollTask();
ambientPoller = null;
}
poller = emanagerPoller;
if (poller != null) {
poller.unregisterPollTask();
emanagerPoller = null;
}
comms = null;
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to Checks
* that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected State getUnscaled(Number value, Unit<?> unit) {
return QuantityType.valueOf(value.doubleValue(), unit);
}
/**
* Returns high value * 1000 + low value
*
* @param high the high value
*
* @param low the low value
*
* @return the scaled value as a DecimalType
*/
protected State getEnergyQuantity(int high, int low) {
double value = high * 1000 + low;
return QuantityType.valueOf(value, KILOWATT_HOUR);
}
/**
* These methods are called each time new data has been polled from the modbus
* slave The register array is first parsed, then each of the channels are
* updated to the new values
*
* @param registers byte array read from the modbus slave
*/
protected void handlePolledAmbientData(ModbusRegisterArray registers) {
// Ambient group
AmbientBlock block = ambientBlockParser.parse(registers);
updateState(channelUID(GROUP_GENERAL_AMBIENT, CHANNEL_AMBIENT_ERROR_NUMBER),
new DecimalType(block.ambientErrorNumber));
updateState(channelUID(GROUP_GENERAL_AMBIENT, CHANNEL_AMBIENT_OPERATING_STATE),
new DecimalType(block.ambientOperatingState));
updateState(channelUID(GROUP_GENERAL_AMBIENT, CHANNEL_ACTUAL_AMBIENT_TEMPERATURE),
getScaled(block.actualAmbientTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_GENERAL_AMBIENT, CHANNEL_AVERAGE_AMBIENT_TEMPERATURE),
getScaled(block.averageAmbientTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_GENERAL_AMBIENT, CHANNEL_CALCULATED_AMBIENT_TEMPERATURE),
getScaled(block.calculatedAmbientTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
protected void handlePolledEManagerData(ModbusRegisterArray registers) {
// EManager group
EManagerBlock block = emanagerBlockParser.parse(registers);
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_EMANAGER_ERROR_NUMBER),
new DecimalType(block.emanagerErrorNumber));
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_EMANAGER_OPERATING_STATE),
new DecimalType(block.emanagerOperatingState));
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_EMANAGER_ACTUAL_POWER_CONSUMPTION),
getUnscaled(block.emanagerActualPowerConsumption, WATT));
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_EMANAGER_ACTUAL_POWER),
getUnscaled(block.emanagerActualPower, WATT));
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_EMANAGER_ACTUAL_POWER_SIGNED),
getUnscaled(block.emanagerActualPowerSigned, WATT));
updateState(channelUID(GROUP_GENERAL_EMANAGER, CHANNEL_POWER_CONSUMPTION_SETPOINT),
getUnscaled(block.emanagerPowerConsumptionSetpoint, WATT));
resetCommunicationError();
}
/**
* @param bridgeStatusInfo
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Handle errors received during communication
*/
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
*
* @param string the channel id in that group
*
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,590 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.*;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.HeatingCircuitConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.HeatingCircuitBlock;
import org.openhab.binding.modbus.lambda.internal.dto.HeatingCircuitReg50Block;
import org.openhab.binding.modbus.lambda.internal.parser.HeatingCircuitBlockParser;
import org.openhab.binding.modbus.lambda.internal.parser.HeatingCircuitReg50BlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HeatingCircuitHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class HeatingCircuitHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private final Logger logger = LoggerFactory.getLogger(HeatingCircuitHandler.class);
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Register poll task This is where we set up our regular poller
*/
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
logger.debug("Setting up regular polling Address: {}", address);
ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms;
HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("HeatingCircuit: registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, HeatingCircuitHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(HeatingCircuitHandler.class);
/**
* Configuration instance
*/
protected @Nullable HeatingCircuitConfiguration config = null;
/**
* Parsers used to convert incoming raw messages into state blocks
*/
private final HeatingCircuitBlockParser heatingcircuitBlockParser = new HeatingCircuitBlockParser();
private final HeatingCircuitReg50BlockParser heatingcircuitReg50BlockParser = new HeatingCircuitReg50BlockParser();
/**
* These are the tasks used to poll the device
*/
private volatile @Nullable AbstractBasePoller heatingcircuitPoller = null;
private volatile @Nullable AbstractBasePoller heatingcircuitReg50Poller = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
*/
public HeatingCircuitHandler(Thing thing) {
super(thing);
}
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config);
ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
HeatingCircuitHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
HeatingCircuitHandler.this.handleWriteError(failure);
});
}
/**
* @param command get the value of this command.
*
* @return short the value of the command as short
*/
private short getInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType<?> quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(WATT);
if (c != null) {
return c.shortValue();
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return c.shortValue();
}
throw new LambdaException("Unsupported command type");
}
private short getScaledInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType<?> quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
if (c != null) {
return (short) (c.doubleValue() * 10);
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return (short) (c.doubleValue() * 10);
}
throw new LambdaException("Unsupported command type");
}
/**
* Handle incoming commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_HEATING_CIRCUIT:
poller = heatingcircuitPoller;
break;
case GROUP_HEATING_CIRCUIT_REG50:
poller = heatingcircuitReg50Poller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("HeatingCircuit: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_HEATING_CIRCUIT.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_HEATING_CIRCUIT_ROOM_DEVICE_TEMPERATURE:
writeInt16(baseadress + 4, getScaledInt16Value(command));
break;
case CHANNEL_HEATING_CIRCUIT_SETPOINT_FLOW_LINE_TEMPERATURE:
writeInt16(baseadress + 5, getScaledInt16Value(command));
break;
case CHANNEL_HEATING_CIRCUIT_OPERATING_MODE:
writeInt16(baseadress + 6, getInt16Value(command));
break;
}
}
if (GROUP_HEATING_CIRCUIT_REG50.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_HEATING_CIRCUIT_OFFSET_FLOW_LINE_TEMPERATURE:
writeInt16(reg50baseadress, getScaledInt16Value(command));
break;
case CHANNEL_HEATING_CIRCUIT_ROOM_HEATING_TEMPERATURE:
writeInt16(reg50baseadress + 1, getScaledInt16Value(command));
break;
case CHANNEL_HEATING_CIRCUIT_ROOM_COOLING_TEMPERATURE:
writeInt16(reg50baseadress + 2, getScaledInt16Value(command));
break;
}
}
} catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
/**
* Initialization: Load the config object of the block Connect to the slave
* bridge Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(HeatingCircuitConfiguration.class);
logger.debug("Initializing thing with properties: {}", thing.getProperties());
startUp();
}
/**
* Adresses for the polling registers, used for reading and writing
*/
private int baseadress;
private int reg50baseadress;
/**
* This method starts the operation of this handler Connect to the slave bridge
* Start the periodic polling1
*/
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
logger.debug("HeatingCircuit: Error setting up SlaveId");
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of Heating Circuit is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("Invalid comms/config/manager ref for lambda heatingcircuit handler");
return;
}
HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config);
baseadress = 5000 + 100 * myconfig.getSubindex();
reg50baseadress = baseadress + 50;
if (heatingcircuitPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledHeatingData(registers);
}
};
// neu: poller.registerPollTask(baseadress, 8, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
poller.registerPollTask(baseadress, 7, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
heatingcircuitPoller = poller;
}
if (heatingcircuitReg50Poller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledHeatingReg50Data(registers);
}
};
poller.registerPollTask(reg50baseadress, 3, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
heatingcircuitReg50Poller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll tasks and release the endpoint reference
*/
private void tearDown() {
AbstractBasePoller poller = heatingcircuitPoller;
if (poller != null) {
poller.unregisterPollTask();
heatingcircuitPoller = null;
}
poller = heatingcircuitReg50Poller;
if (poller != null) {
poller.unregisterPollTask();
heatingcircuitReg50Poller = null;
}
comms = null;
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to Checks
* that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected State getUnscaled(Number value, Unit<?> unit) {
return QuantityType.valueOf(value.doubleValue(), unit);
}
/**
* Returns high value * 1000 + low value
*
* @param high the high value
*
* @param low the low value
*
* @return the scaled value as a DecimalType
*/
protected State getEnergyQuantity(int high, int low) {
double value = high * 1000 + low;
return QuantityType.valueOf(value, KILOWATT_HOUR);
}
/**
* These methods are called each time new data has been polled from the modbus
* slave The register array is first parsed, then each of the channels are
* updated to the new values
*
* @param registers byte array read from the modbus slave
*/
protected void handlePolledHeatingData(ModbusRegisterArray registers) {
HeatingCircuitBlock block = heatingcircuitBlockParser.parse(registers);
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_ERROR_NUMBER),
new DecimalType(block.heatingcircuitErrorNumber));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_OPERATING_STATE),
new DecimalType(block.heatingcircuitOperatingState));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_FLOW_LINE_TEMPERATURE),
getScaled(block.heatingcircuitFlowLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_RETURN_LINE_TEMPERATURE),
getScaled(block.heatingcircuitReturnLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_ROOM_DEVICE_TEMPERATURE),
getScaled(block.heatingcircuitRoomDeviceTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_SETPOINT_FLOW_LINE_TEMPERATURE),
getScaled(block.heatingcircuitSetpointFlowLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_OPERATING_MODE),
new DecimalType(block.heatingcircuitOperatingMode));
// neu: updateState(channelUID(GROUP_HEATING_CIRCUIT, CHANNEL_HEATING_CIRCUIT_TARGET_TEMPERATURE_FLOW_LINE),
// neu: getScaled(block.heatingcircuitTargetTemperatureFlowLine, CELSIUS, -1.0));
resetCommunicationError();
}
protected void handlePolledHeatingReg50Data(ModbusRegisterArray registers) {
HeatingCircuitReg50Block block = heatingcircuitReg50BlockParser.parse(registers);
// HeatingCircuit1Settting group
updateState(channelUID(GROUP_HEATING_CIRCUIT_REG50, CHANNEL_HEATING_CIRCUIT_OFFSET_FLOW_LINE_TEMPERATURE),
getScaled(block.heatingcircuitOffsetFlowLineTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT_REG50, CHANNEL_HEATING_CIRCUIT_ROOM_HEATING_TEMPERATURE),
getScaled(block.heatingcircuitRoomHeatingTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEATING_CIRCUIT_REG50, CHANNEL_HEATING_CIRCUIT_ROOM_COOLING_TEMPERATURE),
getScaled(block.heatingcircuitRoomCoolingTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
/**
* @param bridgeStatusInfo
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Handle errors received during communication
*/
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
*
* @param string the channel id in that group
*
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,534 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.Units.*;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.HeatpumpConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.HeatpumpBlock;
import org.openhab.binding.modbus.lambda.internal.parser.HeatpumpBlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HeatpumpHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class HeatpumpHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Register poll task This is where we set up our regular poller
*/
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms;
HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("Heatpump: registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, HeatpumpHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(HeatpumpHandler.class);
/**
* Configuration instance
*/
protected @Nullable HeatpumpConfiguration config = null;
/**
* Parsers used to convert incoming raw messages into state blocks
*/
private final HeatpumpBlockParser heatpumpBlockParser = new HeatpumpBlockParser();
/**
* These are the tasks used to poll the device
*/
private volatile @Nullable AbstractBasePoller heatpumpPoller = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
*/
public HeatpumpHandler(Thing thing) {
super(thing);
}
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config);
ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
HeatpumpHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
HeatpumpHandler.this.handleWriteError(failure);
});
}
private short getScaledInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType<?> quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
if (c != null) {
return (short) (c.doubleValue() * 10);
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return (short) (c.doubleValue() * 10);
}
throw new LambdaException("Unsupported command type");
}
/**
* Handle incoming commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_HEAT_PUMP:
poller = heatpumpPoller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("Heatpump: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_HEAT_PUMP.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_HEAT_PUMP_REQUEST_T_FLOW:
writeInt16(baseadress + 16, getScaledInt16Value(command));
break;
case CHANNEL_HEAT_PUMP_REQUEST_T_RETURN:
writeInt16(baseadress + 17, getScaledInt16Value(command));
break;
case CHANNEL_HEAT_PUMP_REQUEST_HEAT_SINK:
writeInt16(baseadress + 18, getScaledInt16Value(command));
break;
}
}
} catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
/**
* Initialization: Load the config object of the block Connect to the slave
* bridge Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(HeatpumpConfiguration.class);
logger.debug("Initializing thing with properties: {}", thing.getProperties());
startUp();
}
/**
* Adresses for the polling registers, used for reading and writing
*/
private int baseadress;
/**
* This method starts the operation of this handler Connect to the slave bridge
* Start the periodic polling1
*/
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
// this will be handled below as endpoint remains null
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of Heat Pump is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("Invalid comms/config/manager ref for lambda heat-pump handler");
return;
}
HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config);
baseadress = 1000 + 100 * myconfig.getSubindex();
if (heatpumpPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledHeatpumpData(registers);
}
};
poller.registerPollTask(baseadress, 24, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
heatpumpPoller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll tasks and release the endpoint reference
*/
private void tearDown() {
AbstractBasePoller poller = heatpumpPoller;
if (poller != null) {
poller.unregisterPollTask();
heatpumpPoller = null;
}
comms = null;
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to Checks
* that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected State getUnscaled(Number value, Unit<?> unit) {
return QuantityType.valueOf(value.doubleValue(), unit);
}
/**
* Returns high value * 1000 + low value
*
* @param high the high value
*
* @param low the low value
*
* @return the scaled value as a DecimalType
*/
protected State getEnergyQuantity(int high, int low) {
double value = high * 1000 + low;
return QuantityType.valueOf(value, KILOWATT_HOUR);
}
/**
* These methods are called each time new data has been polled from the modbus
* slave The register array is first parsed, then each of the channels are
* updated to the new values
*
* @param registers byte array read from the modbus slave
*/
protected void handlePolledHeatpumpData(ModbusRegisterArray registers) {
HeatpumpBlock block = heatpumpBlockParser.parse(registers);
// Heatpump group
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_ERROR_STATE),
new DecimalType(block.heatpumpErrorState));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_ERROR_NUMBER),
new DecimalType(block.heatpumpErrorNumber));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_OPERATING_STATE),
new DecimalType(block.heatpumpOperatingState));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_STATE), new DecimalType(block.heatpumpState));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_T_FLOW),
getScaled(block.heatpumpTFlow, CELSIUS, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_T_RETURN),
getScaled(block.heatpumpTReturn, CELSIUS, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_VOL_SINK),
getScaled(block.heatpumpVolSink, LITRE_PER_MINUTE, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_T_EQIN),
getScaled(block.heatpumpTEQin, CELSIUS, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_T_EQOUT),
getScaled(block.heatpumpTEQout, CELSIUS, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_VOL_SOURCE),
getScaled(block.heatpumpVolSource, LITRE_PER_MINUTE, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_COMPRESSOR_RATING),
getScaled(block.heatpumpCompressorRating, PERCENT, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_QP_HEATING),
getScaled(block.heatpumpQpHeating, WATT, 2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_FI_POWER_CONSUMPTION),
getUnscaled(block.heatpumpFIPowerConsumption, WATT));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_COP), getScaled(block.heatpumpCOP, PERCENT, -2.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_REQUEST_PASSWORD),
new DecimalType(block.heatpumpRequestPassword));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_REQUEST_TYPE),
new DecimalType(block.heatpumpRequestType));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_REQUEST_T_FLOW),
getScaled(block.heatpumpRequestTFlow, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_REQUEST_T_RETURN),
getScaled(block.heatpumpRequestTReturn, CELSIUS, -1.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_REQUEST_HEAT_SINK),
getScaled(block.heatpumpRequestHeatSink, KELVIN, -1.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_RELAIS_STATE),
new DecimalType(block.heatpumpRelaisState));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_VDAE),
getScaled(block.heatpumpVdAE, KILOWATT_HOUR, -3.0));
updateState(channelUID(GROUP_HEAT_PUMP, CHANNEL_HEAT_PUMP_VDAQ),
getScaled(block.heatpumpVdAQ, KILOWATT_HOUR, -3.0));
resetCommunicationError();
}
/**
* @param bridgeStatusInfo
*/
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Handle errors received during communication
*/
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
*
* @param string the channel id in that group
*
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Thrown when the lambda handler sees an error.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class LambdaException extends Exception {
private static final long serialVersionUID = 1L;
public LambdaException() {
}
public LambdaException(String message) {
super(message);
}
}
@@ -0,0 +1,437 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.handler;
import static org.openhab.binding.modbus.lambda.internal.LambdaBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import java.util.Objects;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.lambda.internal.SolarConfiguration;
import org.openhab.binding.modbus.lambda.internal.dto.SolarBlock;
import org.openhab.binding.modbus.lambda.internal.dto.SolarReg50Block;
import org.openhab.binding.modbus.lambda.internal.parser.SolarBlockParser;
import org.openhab.binding.modbus.lambda.internal.parser.SolarReg50BlockParser;
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
import org.openhab.core.io.transport.modbus.PollTask;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SolarHandler} is responsible for handling commands,
* which are sent to one of the channels and for polling the modbus
* for solar thermic component data.
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*/
@NonNullByDefault
public class SolarHandler extends BaseThingHandler {
public abstract class AbstractBasePoller {
private volatile @Nullable PollTask pollTask;
public synchronized void unregisterPollTask() {
PollTask task = pollTask;
if (task == null) {
return;
}
ModbusCommunicationInterface mycomms = SolarHandler.this.comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) {
ModbusCommunicationInterface mycomms = SolarHandler.this.comms;
SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config);
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("Solar: registerPollTask called without proper configuration");
}
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(), readFunctionCode, address,
length, myconfig.getMaxTries());
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, SolarHandler.this::handleReadError);
}
public synchronized void poll() {
PollTask task = pollTask;
ModbusCommunicationInterface mycomms = SolarHandler.this.comms;
if (task != null && mycomms != null) {
mycomms.submitOneTimePoll(task.getRequest(), task.getResultCallback(), task.getFailureCallback());
}
}
protected abstract void handlePolledData(ModbusRegisterArray registers);
}
private final Logger logger = LoggerFactory.getLogger(SolarHandler.class);
protected @Nullable SolarConfiguration config = null;
private final SolarBlockParser solarBlockParser = new SolarBlockParser();
private final SolarReg50BlockParser solarReg50BlockParser = new SolarReg50BlockParser();
private volatile @Nullable AbstractBasePoller solarPoller = null;
private volatile @Nullable AbstractBasePoller solarReg50Poller = null;
protected volatile @Nullable ModbusCommunicationInterface comms = null;
private volatile int slaveId;
public SolarHandler(Thing thing) {
super(thing);
}
/**
* @param command get the value of this command.
*
* @return short the value of the command as short
*/
/**
* @param address address of the value to be written on the modbus
*
* @param shortValue value to be written on the modbus
*/
protected void writeInt16(int address, short shortValue) {
SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config);
ModbusCommunicationInterface mycomms = SolarHandler.this.comms;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
// big endian byte ordering
byte hi = (byte) (shortValue >> 8);
byte lo = (byte) shortValue;
ModbusRegisterArray data = new ModbusRegisterArray(hi, lo);
ModbusWriteRegisterRequestBlueprint request = new ModbusWriteRegisterRequestBlueprint(slaveId, address, data,
true, myconfig.getMaxTries());
mycomms.submitOneTimeWrite(request, result -> {
if (hasConfigurationError()) {
return;
}
SolarHandler.this.updateStatus(ThingStatus.ONLINE);
}, failure -> {
SolarHandler.this.handleWriteError(failure);
});
}
private short getScaledInt16Value(Command command) throws LambdaException {
if (command instanceof QuantityType<?> quantityCommand) {
QuantityType<?> c = quantityCommand.toUnit(CELSIUS);
if (c != null) {
return (short) (c.doubleValue() * 10);
} else {
throw new LambdaException("Unsupported unit");
}
}
if (command instanceof DecimalType c) {
return (short) (c.doubleValue() * 10);
}
throw new LambdaException("Unsupported command type");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH == command) {
String groupId = channelUID.getGroupId();
if (groupId != null) {
AbstractBasePoller poller;
switch (groupId) {
case GROUP_SOLAR:
poller = solarPoller;
break;
case GROUP_SOLAR_REG50:
poller = solarReg50Poller;
break;
default:
poller = null;
break;
}
if (poller != null) {
logger.trace("Solar: Polling initiated");
poller.poll();
}
}
} else {
try {
if (GROUP_SOLAR_REG50.equals(channelUID.getGroupId())) {
switch (channelUID.getIdWithoutGroup()) {
case CHANNEL_SOLAR_MAXIMUM_BUFFER_TEMPERATURE:
writeInt16(reg50baseadress, getScaledInt16Value(command));
break;
case CHANNEL_SOLAR_BUFFER_CHANGEOVER_TEMPERATURE:
writeInt16(reg50baseadress + 1, getScaledInt16Value(command));
break;
}
}
} catch (LambdaException error) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String cls = error.getClass().getName();
String msg = error.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with: %s: %s", cls, msg));
}
}
}
@Override
public void initialize() {
config = getConfigAs(SolarConfiguration.class);
logger.debug("Initializing solar thing with properties: {}", thing.getProperties());
startUp();
}
private int baseadress;
private int reg50baseadress;
private void startUp() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
// this will be handled below as endpoint remains null
}
if (comms == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("CommunicationInterface of Solar is null, Thing & Bridge are offline");
return;
}
if (config == null) {
logger.debug("Invalid comms/config/manager ref for lambda solar handler");
return;
}
SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config);
// Base address for solar is 4000 as mentioned in README.md
baseadress = 4000 + 100 * myconfig.getSubindex();
reg50baseadress = baseadress + 50;
if (solarPoller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledSolarData(registers);
}
};
poller.registerPollTask(baseadress, 5, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
solarPoller = poller;
}
if (solarReg50Poller == null) {
AbstractBasePoller poller = new AbstractBasePoller() {
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
handlePolledSolarReg50Data(registers);
}
};
poller.registerPollTask(reg50baseadress, 2, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
solarReg50Poller = poller;
}
updateStatus(ThingStatus.UNKNOWN);
}
@Override
public void dispose() {
tearDown();
}
private void tearDown() {
AbstractBasePoller poller = solarPoller;
if (poller != null) {
poller.unregisterPollTask();
solarPoller = null;
}
poller = solarReg50Poller;
if (poller != null) {
poller.unregisterPollTask();
solarReg50Poller = null;
}
comms = null;
}
public int getSlaveId() {
return slaveId;
}
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler thingHandler) {
return thingHandler;
} else {
throw new IllegalStateException("Unexpected bridge handler: " + handler.toString());
}
}
protected State getScaled(Number value, Unit<?> unit, Double pow) {
double factor = Math.pow(10, pow);
return QuantityType.valueOf(value.doubleValue() * factor, unit);
}
protected void handlePolledSolarData(ModbusRegisterArray registers) {
SolarBlock block = solarBlockParser.parse(registers);
// Update solar channels
updateState(channelUID(GROUP_SOLAR, CHANNEL_SOLAR_ERROR_NUMBER), new DecimalType(block.solarErrorNumber));
updateState(channelUID(GROUP_SOLAR, CHANNEL_SOLAR_OPERATING_STATE), new DecimalType(block.solarOperatingState));
updateState(channelUID(GROUP_SOLAR, CHANNEL_SOLAR_COLLECTOR_TEMPERATURE),
getScaled(block.solarCollectorTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_SOLAR, CHANNEL_SOLAR_BUFFER1_TEMPERATURE),
getScaled(block.solarBuffer1Temperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_SOLAR, CHANNEL_SOLAR_BUFFER2_TEMPERATURE),
getScaled(block.solarBuffer2Temperature, CELSIUS, -1.0));
resetCommunicationError();
}
protected void handlePolledSolarReg50Data(ModbusRegisterArray registers) {
SolarReg50Block block = solarReg50BlockParser.parse(registers);
updateState(channelUID(GROUP_SOLAR_REG50, CHANNEL_SOLAR_MAXIMUM_BUFFER_TEMPERATURE),
getScaled(block.solarMaximumBufferTemperature, CELSIUS, -1.0));
updateState(channelUID(GROUP_SOLAR_REG50, CHANNEL_SOLAR_BUFFER_CHANGEOVER_TEMPERATURE),
getScaled(block.solarBufferChangeoverTemperature, CELSIUS, -1.0));
resetCommunicationError();
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
protected void handleReadError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Handle errors received during communication
*/
protected void handleWriteError(AsyncModbusFailure<ModbusWriteRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with write: %s: %s", cls, msg));
}
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
}
@@ -0,0 +1,157 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
import org.openhab.core.library.types.DecimalType;
/**
* Base class for parsers with some helper methods
*
* @author Nagy Attila Gabor - Initial contribution
* @author Paul Frank - Added more methods
*/
@NonNullByDefault
public class AbstractBaseParser {
/**
* Extract an optional double value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Double> extractOptionalDouble(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT16)
.map(value -> ((double) value.intValue()) / 10.0).filter(value -> value != (short) 0x8000);
}
/**
* Extract a mandatory double value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @param def the default value
*
* @return the parsed value or the default if the field is not implemented
*/
protected Double extractDouble(ModbusRegisterArray raw, int index, double def) {
return Objects.requireNonNull(extractOptionalDouble(raw, index).orElse(def));
}
/**
* Extract an optional int16 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Short> extractOptionalInt16(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT16).map(DecimalType::shortValue)
.filter(value -> value != (short) 0x8000);
}
/**
* Extract a mandatory int16 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @param def the default value
*
* @return the parsed value or the default if the field is not implemented
*/
protected Short extractInt16(ModbusRegisterArray raw, int index, short def) {
return Objects.requireNonNull(extractOptionalInt16(raw, index).orElse(def));
}
/**
* Extract an optional uint16 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Integer> extractOptionalUInt16(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.UINT16).map(DecimalType::intValue)
.filter(value -> value != 0xffff);
}
/**
* Extract a mandatory uint16 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @param def the default value
*
* @return the parsed value or the default if the field is not implemented
*/
protected Integer extractUInt16(ModbusRegisterArray raw, int index, int def) {
return Objects.requireNonNull(extractOptionalUInt16(raw, index).orElse(def));
}
/**
* Extract an optional acc32 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Long> extractOptionalUInt32(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.UINT32).map(DecimalType::longValue)
.filter(value -> value != 0);
}
protected Optional<Long> extractOptionalInt32(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT32).map(DecimalType::longValue)
.filter(value -> value != 0);
}
/**
* Extract a mandatory acc32 value
*
* @param raw the register array to extract from
*
* @param index the address of the field
*
* @param def the default value
*
* @return the parsed value or default if the field is not implemented
*/
protected Long extractUInt32(ModbusRegisterArray raw, int index, long def) {
return Objects.requireNonNull(extractOptionalUInt32(raw, index).orElse(def));
}
protected Long extractInt32(ModbusRegisterArray raw, int index, long def) {
return Objects.requireNonNull(extractOptionalInt32(raw, index).orElse(def));
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.AmbientBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into an Ambient Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class AmbientBlockParser extends AbstractBaseParser {
public AmbientBlock parse(ModbusRegisterArray raw) {
AmbientBlock block = new AmbientBlock();
block.ambientErrorNumber = extractInt16(raw, 0, (short) 0);
block.ambientOperatingState = extractUInt16(raw, 1, (short) 0);
block.actualAmbientTemperature = extractInt16(raw, 2, (short) 0);
block.averageAmbientTemperature = extractInt16(raw, 3, (short) 0);
block.calculatedAmbientTemperature = extractInt16(raw, 4, (short) 0);
return block;
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.BoilerBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into a Boiler Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class BoilerBlockParser extends AbstractBaseParser {
public BoilerBlock parse(ModbusRegisterArray raw) {
BoilerBlock block = new BoilerBlock();
block.boilerErrorNumber = extractUInt16(raw, 0, (short) 0);
block.boilerOperatingState = extractInt16(raw, 1, (short) 0);
block.boilerActualHighTemperature = extractInt16(raw, 2, (short) 0);
block.boilerActualLowTemperature = extractInt16(raw, 3, (short) 0);
return block;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.BoilerReg50Block;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses modbus data into an BoilerReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class BoilerReg50BlockParser extends AbstractBaseParser {
public BoilerReg50Block parse(ModbusRegisterArray raw) {
BoilerReg50Block block = new BoilerReg50Block();
block.boilerMaximumBoilerTemperature = extractInt16(raw, 0, (short) 0);
return block;
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.BufferBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into a Buffer Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class BufferBlockParser extends AbstractBaseParser {
public BufferBlock parse(ModbusRegisterArray raw) {
BufferBlock block = new BufferBlock();
block.bufferErrorNumber = extractInt16(raw, 0, (short) 0);
block.bufferOperatingState = extractUInt16(raw, 1, (short) 0);
block.bufferActualHighTemperature = extractInt16(raw, 2, (short) 0);
block.bufferActualLowTemperature = extractInt16(raw, 3, (short) 0);
block.bufferActualModbusTemperature = extractInt16(raw, 4, (short) 0);
block.bufferRequestType = extractInt16(raw, 5, (short) 0);
block.bufferrequestFlowLineTemperature = extractInt16(raw, 6, (short) 0);
block.bufferrequestReturnLineTemperature = extractInt16(raw, 7, (short) 0);
block.bufferrequestHeatSinkTemperature = extractInt16(raw, 8, (short) 0);
block.bufferrequestHeatingCapacity = extractInt16(raw, 9, (short) 0);
return block;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.BufferReg50Block;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into an BufferReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class BufferReg50BlockParser extends AbstractBaseParser {
public BufferReg50Block parse(ModbusRegisterArray raw) {
BufferReg50Block block = new BufferReg50Block();
block.bufferMaximumBufferTemperature = extractUInt16(raw, 0, (short) 0);
return block;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.EManagerBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses inverter modbus data into an EManager Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class EManagerBlockParser extends AbstractBaseParser {
public EManagerBlock parse(ModbusRegisterArray raw) {
EManagerBlock block = new EManagerBlock();
block.emanagerErrorNumber = extractInt16(raw, 0, (short) 0);
block.emanagerOperatingState = extractUInt16(raw, 1, (short) 0);
block.emanagerActualPower = extractUInt16(raw, 2, (short) 0);
block.emanagerActualPowerSigned = extractInt16(raw, 2, (short) 0);
block.emanagerActualPowerConsumption = extractInt16(raw, 3, (short) 0);
block.emanagerPowerConsumptionSetpoint = extractInt16(raw, 4, (short) 0);
return block;
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.HeatingCircuitBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda
* modbus data into a Heating Circuit Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class HeatingCircuitBlockParser extends AbstractBaseParser {
public HeatingCircuitBlock parse(ModbusRegisterArray raw) {
HeatingCircuitBlock block = new HeatingCircuitBlock();
block.heatingcircuitErrorNumber = extractInt16(raw, 0, (short) 0);
block.heatingcircuitOperatingState = extractUInt16(raw, 1, (short) 0);
block.heatingcircuitFlowLineTemperature = extractInt16(raw, 2, (short) 0);
block.heatingcircuitReturnLineTemperature = extractInt16(raw, 3, (short) 0);
block.heatingcircuitRoomDeviceTemperature = extractInt16(raw, 4, (short) 0);
block.heatingcircuitSetpointFlowLineTemperature = extractInt16(raw, 5, (short) 0);
block.heatingcircuitOperatingMode = extractInt16(raw, 6, (short) 0);
return block;
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.HeatingCircuitReg50Block;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into a HeatingCircuitReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class HeatingCircuitReg50BlockParser extends AbstractBaseParser {
public HeatingCircuitReg50Block parse(ModbusRegisterArray raw) {
HeatingCircuitReg50Block block = new HeatingCircuitReg50Block();
block.heatingcircuitOffsetFlowLineTemperature = extractInt16(raw, 0, (short) 0);
block.heatingcircuitRoomHeatingTemperature = extractInt16(raw, 1, (short) 0);
block.heatingcircuitRoomCoolingTemperature = extractInt16(raw, 2, (short) 0);
return block;
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.HeatpumpBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into a Heatpump Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class HeatpumpBlockParser extends AbstractBaseParser {
public HeatpumpBlock parse(ModbusRegisterArray raw) {
HeatpumpBlock block = new HeatpumpBlock();
block.heatpumpErrorState = extractUInt16(raw, 0, (short) 0);
block.heatpumpErrorNumber = extractInt16(raw, 1, (short) 0);
block.heatpumpState = extractUInt16(raw, 2, (short) 0);
block.heatpumpOperatingState = extractUInt16(raw, 3, (short) 0);
block.heatpumpTFlow = extractInt16(raw, 4, (short) 0);
block.heatpumpTReturn = extractInt16(raw, 5, (short) 0);
block.heatpumpVolSink = extractInt16(raw, 6, (short) 0);
block.heatpumpTEQin = extractInt16(raw, 7, (short) 0);
block.heatpumpTEQout = extractInt16(raw, 8, (short) 0);
block.heatpumpVolSource = extractInt16(raw, 9, (short) 0);
block.heatpumpCompressorRating = extractUInt16(raw, 10, (short) 0);
block.heatpumpQpHeating = extractInt16(raw, 11, (short) 0);
block.heatpumpFIPowerConsumption = extractInt16(raw, 12, (short) 0);
block.heatpumpCOP = extractInt16(raw, 13, (short) 0);
block.heatpumpRequestPassword = extractUInt16(raw, 14, (short) 0);
block.heatpumpRequestType = extractInt16(raw, 15, (short) 0);
block.heatpumpRequestTFlow = extractInt16(raw, 16, (short) 0);
block.heatpumpRequestTReturn = extractInt16(raw, 17, (short) 0);
block.heatpumpRequestHeatSink = extractInt16(raw, 18, (short) 0);
block.heatpumpRelaisState = extractInt16(raw, 19, (short) 0);
block.heatpumpVdAE = extractInt32(raw, 20, (long) 0);
block.heatpumpVdAQ = extractInt32(raw, 22, (long) 0);
return block;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
/**
* Parses lambda modbus data into a Solar Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.SolarBlock;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parser for solar thermic component data from modbus registers
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class SolarBlockParser extends AbstractBaseParser {
public SolarBlock parse(ModbusRegisterArray registers) {
SolarBlock block = new SolarBlock();
block.solarErrorNumber = extractUInt16(registers, 0, 0);
block.solarOperatingState = extractUInt16(registers, 1, 0);
block.solarCollectorTemperature = extractInt16(registers, 2, (short) 0);
// Two different versions for usage in different configurations of the manufacturer
block.solarBuffer1Temperature = extractInt16(registers, 3, (short) 0);
block.solarBuffer2Temperature = extractUInt16(registers, 4, 0);
return block;
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.modbus.lambda.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.lambda.internal.dto.SolarReg50Block;
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
/**
* Parses lambda modbus data into a SolarReg50 Block
*
* @author Paul Frank - Initial contribution
* @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus
*
*/
@NonNullByDefault
public class SolarReg50BlockParser extends AbstractBaseParser {
public SolarReg50Block parse(ModbusRegisterArray raw) {
SolarReg50Block block = new SolarReg50Block();
block.solarMaximumBufferTemperature = extractInt16(raw, 0, (short) 0);
block.solarBufferChangeoverTemperature = extractInt16(raw, 1, (short) 0);
return block;
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="modbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Lambda Heat Pump Binding</name>
<description>Lambda heat pump binding via Modbus TCP/IP.</description>
<connection>local</connection>
</addon:addon>
@@ -0,0 +1,156 @@
# add-on
addon.modbus.lambda.name = Lambda Heat Pump Binding
addon.modbus.lambda.description = The Lambda Heat Pump binding supports the different parts of a Lambda Heat Pump heating.
# thing types
thing-type.modbus.general.label = Lambda General Sections
thing-type.modbus.general.description = Provides Channels of the General Sections
thing-type.modbus.heat-pump.label = Lambda Heat Pump
thing-type.modbus.heat-pump.description = Provides Channels of a Heat Pump
thing-type.modbus.boiler.label = Lambda Boiler
thing-type.modbus.boiler.description = Provides Channels of a Boiler
thing-type.modbus.buffer.label = Lambda Buffer
thing-type.modbus.buffer.description = Provides Channels of a Buffer
thing-type.modbus.heating-circuit.label = Lambda Heating Circuit
thing-type.modbus.heating-circuit.description = Provides Channels of a Heating Circuit
thing-type.modbus.solar.label = Lambda Solar
thing-type.modbus.solar.description = Provides Channels of Solar
# thing types config
thing-type.config.general.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.general.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.general.modbusconfig.refresh.label = Refresh Interval
thing-type.config.general.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.general.baseadress.label = BaseAdress of general section
thing-type.config.boiler.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.boiler.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.boiler.modbusconfig.refresh.label = Refresh Interval
thing-type.config.boiler.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.boiler.baseadress.label = BaseAdress of boiler
thing-type.config.buffer.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.buffer.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.buffer.modbusconfig.refresh.label = Refresh Interval
thing-type.config.buffer.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.buffer.baseadress.label = BaseAdress of buffer
thing-type.config.heat-pump.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.heat-pump.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.heat-pump.modbusconfig.refresh.label = Refresh Interval
thing-type.config.heat-pump.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.heat-pump.baseadress.label = BaseAdress of Heatpump
thing-type.config.heating-circuit.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.heating-circuit.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.heating-circuit.modbusconfig.refresh.label = Refresh Interval
thing-type.config.heating-circuit.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.heating-circuit.baseadress.label = BaseAdress of Heating Circuit
thing-type.config.solar.modbusconfig.maxTries.label = Maximum Tries When Reading
thing-type.config.solar.modbusconfig.maxTries.description = Number of tries when reading data, if some of the reading fail. For single try, enter 1.
thing-type.config.solar.modbusconfig.refresh.label = Refresh Interval
thing-type.config.solar.modbusconfig.refresh.description = Refresh interval in seconds. Use zero to disable automatic polling.
thing-type.config.solar.baseadress.label = Base Adress of Heating Circuit
# channel group types
channel-group-type.modbus.ambient.label = General Ambient Group
channel-group-type.modbus.emanager.label = General E-Manager Group
channel-group-type.modbus.heat-pump.label = Heatpump Group
channel-group-type.modbus.boiler.label = Boiler Group
channel-group-type.modbus.boilerreg50.label = Boiler Reg50 Group
channel-group-type.modbus.buffer.label = Buffer Group
channel-group-type.modbus.bufferreg50.label = Buffer Reg50 Group
channel-group-type.modbus.heating-circuit.label = Heating-Circuit Group
channel-group-type.modbus.heating-circuitreg50.label = Heating-Circuit Reg50 Group
channel-group-type.modbus.solar.label = Solar Group
channel-group-type.modbus.solarreg50.label = Solar Reg50 Group
# channel types
# General Ambient
channel-type.modbus.ambient-error-number-channel.label = Ambient Error Number
channel-type.modbus.ambient-operating-state-channel.label = Ambient Operating State
channel-type.modbus.actual-ambient-temperature-channel.label = Actual Ambient Temperature
channel-type.modbus.average-ambient-temperature-channel.label = Average Ambient Temperature 1h
channel-type.modbus.calculated-ambient-temperature-channel.label = Calculated Ambient Temperature
# General E-Manager
channel-type.modbus.emanager-error-number-channel.label = E-Manager Error Number
channel-type.modbus.emanager-operating-state-channel.label = E-Manager Operating State
channel-type.modbus.emanager-actual-power-channel.label = Actual Power (input or excess)
channel-type.modbus.emanager-actual-power-signed-channel.label = Actual Power Signed (input or excess)
channel-type.modbus.emanager-actual-power-consumption-channel.label = Actual Power Consumption
channel-type.modbus.emanager-power-consumption-setpoint-channel.label = Power Consumption setpoint as sum of all heat pumps
# Heat Pump
channel-type.modbus.heat-pump-error-state-channel.label = Heatpump Error State
channel-type.modbus.heat-pump-error-number-channel.label = Heatpump Error Number
channel-type.modbus.heat-pump-state-channel.label = Heatpump State
channel-type.modbus.heat-pump-operating-state-channel.label = Heatpump Operating State
channel-type.modbus.heat-pump-t-flow-channel.label = Heatpump T-Flow
channel-type.modbus.heat-pump-t-return-channel.label = Heatpump T-Return
channel-type.modbus.heat-pump-vol-sink-channel.label = Heatpump Volume flow heat sink
channel-type.modbus.heat-pump-t-eqin-channel.label = Heatpump Energy source inlet temperature
channel-type.modbus.heat-pump-t-eqout-channel.label = Heatpump Energy source outlet temperature
channel-type.modbus.heat-pump-vol-source-channel.label = Heatpump Volume flow energy source
channel-type.modbus.heat-pump-compressor-rating-channel.label = Heatpump Compressor unit rating
channel-type.modbus.heat-pump-qp-heating-channel.label = Heatpump Actual heating capacity
channel-type.modbus.heat-pump-fi-power-consumption-channel.label = Heatpump Frequency inverter actual power consumption
channel-type.modbus.heat-pump-cop-channel.label = Heatpump COP
channel-type.modbus.heat-pump-request-password-channel.label = Heatpump Request Password
channel-type.modbus.heat-pump-request-type-channel.label = Heatpump Request Type
channel-type.modbus.heat-pump-request-t-flow-channel.label = Heatpump requested T-Flow
channel-type.modbus.heat-pump-request-t-return-channel.label = Heatpump requested T-Return
channel-type.modbus.heat-pump-request-heat-sink-channel.label = Heatpump requested temperature difference between T-Flow and T-Return
channel-type.modbus.heat-pump-relais-state-channel.label = Heatpump Relais State
channel-type.modbus.heat-pump-vdae-channel.label = Heatpump VdA E
channel-type.modbus.heat-pump-vdaq-channel.label = Heatpump VdA Q
# Boiler
channel-type.modbus.boiler-error-number-channel.label = Boiler Error Number
channel-type.modbus.boiler-operating-state-channel.label = Boiler Operating State
channel-type.modbus.boiler-actual-high-temperature-channel.label = Boiler Actual High Temperature
channel-type.modbus.boiler-actual-low-temperature-channel.label = Boiler Actual Low Temperature
channel-type.modbus.maximum-boiler-temperature-channel.label = Maximum Boiler Temperature
# Buffer
channel-type.modbus.buffer-error-number-channel.label = Buffer Error Number
channel-type.modbus.buffer-operating-state-channel.label = Buffer Operating State
channel-type.modbus.buffer-actual-high-temperature-channel.label = Buffer Actual High Temperature
channel-type.modbus.buffer-actual-low-temperature-channel.label = Buffer Actual Low Temperature
channel-type.modbus.buffer-actual-modbus-temperature-channel.label = Buffer Actual Modbus Temperature
channel-type.modbus.buffer-request-type-channel.label = Buffer Request Type
channel-type.modbus.buffer-request-flow-line-temperature-channel.label = Buffer Request Flow Line Temperature
channel-type.modbus.buffer-request-return-line-temperature-channel.label = Buffer Request Return Line Temperature
channel-type.modbus.buffer-request-heat-sink-temperature-difference-channel.label = Buffer Request Heat Sink Temperatur Difference
channel-type.modbus.buffer-request-heating-capacity-channel.label = Buffer Request Heating Capacity
channel-type.modbus.maximum-buffer-temperature-channel.label = Maximum Buffer Temperature
# Heating Circuit
channel-type.modbus.heating-circuit-error-number-channel.label = Heating circuit Error Number
channel-type.modbus.heating-circuit-operating-state-channel.label = Heating circuit Operating State
channel-type.modbus.heating-circuit-flow-line-temperature-channel.label = Heating circuit Flow Line Temperature
channel-type.modbus.heating-circuit-return-line-temperature-channel.label = Heating circuit Return Line Temperature
channel-type.modbus.heating-circuit-room-device-temperature-channel.label = Heating circuit Room Device Temperature
channel-type.modbus.heating-circuit-setpoint-flow-line-temperature-channel.label = Heating circuit Setpoint Flow Line Temperature
channel-type.modbus.heating-circuit-operating-mode-channel.label = Heating circuit Operating Mode
channel-type.modbus.heating-circuit-target-temperature-flow-line-channel.label = Heating circuit Target Temperature Flow Line
channel-type.modbus.heating-circuit-offset-flow-line-temperature-channel.label = Heating circuit Offset Flow Line Temperature
channel-type.modbus.heating-circuit-room-heating-temperature-channel.label = Heating circuit Room Heating Temperature
channel-type.modbus.heating-circuit-room-cooling-temperature-channel.label = Heating circuit Room Cooling Temperature
# Solar
channel-type.modbus.solar-error-number-channel.label = Solar Error Number
channel-type.modbus.solar-operating-state-channel.label = Operating State
channel-type.modbus.solar-collector-temperature-channel.label = Solar Collector Temperature
channel-type.modbus.solar-buffer1-temperature-channel.label = Solar Buffer 1 Temperature
channel-type.modbus.solar-buffer2-temperature-channel.label = Solar Buffer 2 Temperature
channel-type.modbus.solar-maximum-buffer-temperature-channel.label = Solar Maximum Buffer Temperature
channel-type.modbus.solar-buffer-changeover-temperature-channel.label = Solar Buffer Changeover Temperature
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="boiler">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Boiler</label>
<description>Lambda Boiler connected through modbus</description>
<channel-groups>
<channel-group id="boiler-group" typeId="boiler"/>
<channel-group id="boiler-reg50-group" typeId="boiler-reg50"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="1" max="3600">
<label>Refresh Interval</label>
<description>Poll interval in seconds. Increase this if you encounter connection errors.</description>
<default>30</default>
</parameter>
<parameter name="maxTries" type="integer" min="1" max="10">
<label>Maximum Tries</label>
<description>Number of retries before giving up reading from this thing.</description>
<default>3</default>
</parameter>
<parameter name="subindex" type="integer" min="0" max="4" required="true">
<label>Subindex</label>
<description>Subindex for multiple boiler components</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="boiler">
<label>Boiler</label>
<channels>
<channel id="boiler-error-number" typeId="boiler-error-number-channel"/>
<channel id="boiler-operating-state" typeId="boiler-operating-state-channel"/>
<channel id="boiler-actual-high-temperature" typeId="boiler-actual-high-temperature-channel"/>
<channel id="boiler-actual-low-temperature" typeId="boiler-actual-low-temperature-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="boiler-reg50">
<label>Boiler Reg50 Group</label>
<channels>
<channel id="maximum-boiler-temperature" typeId="maximum-boiler-temperature-channel"/>
</channels>
</channel-group-type>
<channel-type id="boiler-error-number-channel">
<item-type>Number</item-type>
<label>Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="boiler-operating-state-channel">
<item-type>Number</item-type>
<label>Operating State</label>
<state readOnly="true">
<options>
<option value="0">STBY</option>
<option value="1">DHW</option>
<option value="2">LEGIO</option>
<option value="3">SUMMER</option>
<option value="4">FROST</option>
<option value="5">HOLIDAY</option>
<option value="6">PRIO-STOP</option>
<option value="7">ERROR</option>
<option value="8">OFF</option>
<option value="9">PROMPT-DHW</option>
<option value="10">TRAILING-STOP</option>
<option value="11">TEMP-LOCK</option>
<option value="12">STBY-FROST</option>
</options>
</state>
</channel-type>
<channel-type id="boiler-actual-high-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Boiler Actual High Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="boiler-actual-low-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Boiler Actual Low Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="maximum-boiler-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Maximum Boiler Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="buffer">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Buffer</label>
<description>Lambda Buffer connected through modbus</description>
<channel-groups>
<channel-group id="buffer-group" typeId="buffer"/>
<channel-group id="buffer-reg50-group" typeId="buffer-reg50"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="1" max="3600">
<label>Refresh Interval</label>
<description>Poll interval in seconds. Increase this if you encounter connection errors.</description>
<default>30</default>
</parameter>
<parameter name="maxTries" type="integer" min="1" max="10">
<label>Maximum Tries</label>
<description>Number of retries before giving up reading from this thing.</description>
<default>3</default>
</parameter>
<parameter name="subindex" type="integer" min="0" max="4" required="true">
<label>Subindex</label>
<description>Subindex for multiple buffer components</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="buffer">
<label>Buffer</label>
<channels>
<channel id="buffer-error-number" typeId="buffer-error-number-channel"/>
<channel id="buffer-operating-state" typeId="buffer-operating-state-channel"/>
<channel id="buffer-actual-high-temperature" typeId="buffer-actual-high-temperature-channel"/>
<channel id="buffer-actual-low-temperature" typeId="buffer-actual-low-temperature-channel"/>
<channel id="buffer-actual-modbus-temperature" typeId="buffer-actual-modbus-temperature-channel"/>
<channel id="buffer-request-type" typeId="buffer-request-type-channel"/>
<channel id="buffer-request-flow-line-temperature" typeId="buffer-request-flow-line-temperature-channel"/>
<channel id="buffer-request-return-line-temperature" typeId="buffer-request-return-line-temperature-channel"/>
<channel id="buffer-request-heat-sink-temperature" typeId="buffer-request-heat-sink-temperature-channel"/>
<channel id="buffer-request-heating-capacity" typeId="buffer-request-heating-capacity-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="buffer-reg50">
<label>Buffer Reg50 Group</label>
<channels>
<channel id="maximum-buffer-temperature" typeId="maximum-buffer-temperature-channel"/>
</channels>
</channel-group-type>
<channel-type id="buffer-error-number-channel">
<item-type>Number</item-type>
<label>Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="buffer-operating-state-channel">
<item-type>Number</item-type>
<label>Operating State</label>
<state readOnly="true">
<options>
<option value="0">STBY</option>
<option value="1">HEATING</option>
<option value="2">COOLING</option>
<option value="3">SUMMER</option>
<option value="4">FROST</option>
<option value="5">HOLIDAY</option>
<option value="6">PRIO-STOP</option>
<option value="7">ERROR</option>
<option value="8">OFF</option>
<option value="9">STBY-FROST</option>
</options>
</state>
</channel-type>
<channel-type id="buffer-actual-high-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Actual High Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-actual-low-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Buffer Actual Low Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-actual-modbus-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Actual Modbus Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-request-type-channel">
<item-type>Number</item-type>
<label>Request Type</label>
<state readOnly="false">
<options>
<option value="-1">INVALID REQUEST</option>
<option value="0">NO REQUEST</option>
<option value="1">FLOW PUMP CIRCULATION</option>
<option value="2">CENTRAL HEATING</option>
<option value="3">CENTRAL COOLING</option>
</options>
</state>
</channel-type>
<channel-type id="buffer-request-flow-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Request Flow Line Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-request-return-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Request Return Line Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-request-heat-sink-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Request Heat Sink Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="buffer-request-heating-capacity-channel">
<item-type>Number:Power</item-type>
<label>Request Heating Capacity</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="maximum-buffer-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Max. Buffer Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="general">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Lambda General</label>
<description> Ambient and E-Manager sections connected through modbus</description>
<channel-groups>
<channel-group id="ambient-group" typeId="ambient"/>
<channel-group id="emanager-group" typeId="emanager"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="1" max="3600">
<label>Refresh Interval</label>
<description>Poll interval in seconds. Increase this if you encounter connection errors.</description>
<default>30</default>
</parameter>
<parameter name="maxTries" type="integer" min="1" max="10">
<label>Maximum Tries</label>
<description>Number of retries before giving up reading from this thing.</description>
<default>3</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="ambient">
<label>General Ambient</label>
<channels>
<channel id="ambient-error-number" typeId="ambient-error-number-channel"/>
<channel id="ambient-operating-state" typeId="ambient-operating-state-channel"/>
<channel id="actual-ambient-temperature" typeId="actual-ambient-temperature-channel"/>
<channel id="average-ambient-temperature" typeId="average-ambient-temperature-channel"/>
<channel id="calculated-ambient-temperature" typeId="calculated-ambient-temperature-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="emanager">
<label>General E-Manager</label>
<channels>
<channel id="emanager-error-number" typeId="emanager-error-number-channel"/>
<channel id="emanager-operating-state" typeId="emanager-operating-state-channel"/>
<channel id="emanager-actual-power" typeId="emanager-actual-power-channel"/>
<channel id="emanager-actual-power-signed" typeId="emanager-actual-power-signed-channel"/>
<channel id="emanager-actual-power-consumption" typeId="emanager-actual-power-consumption-channel"/>
<channel id="power-consumption-setpoint" typeId="emanager-power-consumption-setpoint-channel"/>
</channels>
</channel-group-type>
<channel-type id="ambient-error-number-channel">
<item-type>Number</item-type>
<label>Ambient Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="ambient-operating-state-channel">
<item-type>Number</item-type>
<label>Ambient Operating State</label>
<state readOnly="true">
<options>
<option value="0">OFF</option>
<option value="1">AUTOMATIC</option>
<option value="2">MANUAL</option>
<option value="3">ERROR</option>
</options>
</state>
</channel-type>
<channel-type id="actual-ambient-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Actual Ambient Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="average-ambient-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Average Ambient Temperature 1h</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="calculated-ambient-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Calculated Ambient Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="emanager-error-number-channel">
<item-type>Number</item-type>
<label>E-Manager Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="emanager-operating-state-channel">
<item-type>Number</item-type>
<label>E-Manager Operating State</label>
<state readOnly="true">
<options>
<option value="0">OFF</option>
<option value="1">AUTOMATIC</option>
<option value="2">MANUAL</option>
<option value="3">ERROR</option>
<option value="4">OFFLINE</option>
</options>
</state>
</channel-type>
<channel-type id="emanager-actual-power-channel">
<item-type>Number:Power</item-type>
<label>Actual Power</label>
<state readOnly="false" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="emanager-actual-power-signed-channel">
<item-type>Number:Power</item-type>
<label>Actual Power Signed (input or excess)</label>
<state readOnly="false" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="emanager-actual-power-consumption-channel">
<item-type>Number:Power</item-type>
<label>Actual Power Consumption</label>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="emanager-power-consumption-setpoint-channel">
<item-type>Number:Power</item-type>
<label>Total Power Consumption</label>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="heat-pump">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Heat Pump</label>
<description>Lambda Heat Pump connected through modbus</description>
<channel-groups>
<channel-group id="heat-pump-group" typeId="heat-pump"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="0" unit="s">
<label>Refresh Interval</label>
<description>Refresh interval in seconds. Use zero to disable automatic polling.</description>
<default>30</default>
<unitLabel>s</unitLabel>
</parameter>
<parameter name="maxTries" type="integer" min="1">
<label>Maximum Tries</label>
<default>3</default>
<description>Number of tries when reading data, if some of the reading fail. For single try, enter 1.</description>
<advanced>true</advanced>
</parameter>
<parameter name="subindex" type="integer" required="true" min="0" max="2">
<label>Subindex of Heatpump Thing (0..2)</label>
<description>See Lambda Modbus description</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="heat-pump">
<label>Heat Pump</label>
<channels>
<channel id="heat-pump-error-state" typeId="heat-pump-error-state"/>
<channel id="heat-pump-error-number" typeId="heat-pump-error-number"/>
<channel id="heat-pump-state" typeId="heat-pump-state"/>
<channel id="heat-pump-operating-state" typeId="heat-pump-operating-state"/>
<channel id="heat-pump-t-flow" typeId="heat-pump-t-flow"/>
<channel id="heat-pump-t-return" typeId="heat-pump-t-return"/>
<channel id="heat-pump-vol-sink" typeId="heat-pump-vol-sink"/>
<channel id="heat-pump-t-eqin" typeId="heat-pump-t-eqin"/>
<channel id="heat-pump-t-eqout" typeId="heat-pump-t-eqout"/>
<channel id="heat-pump-vol-source" typeId="heat-pump-vol-source"/>
<channel id="heat-pump-compressor-rating" typeId="heat-pump-compressor-rating"/>
<channel id="heat-pump-qp-heating" typeId="heat-pump-qp-heating"/>
<channel id="heat-pump-fi-power-consumption" typeId="heat-pump-fi-power-consumption"/>
<channel id="heat-pump-cop" typeId="heat-pump-cop"/>
<channel id="heatpump-request-password" typeId="heatpump-request-password-channel"/>
<channel id="heatpump-request-type" typeId="heatpump-request-type-channel"/>
<channel id="heat-pump-request-t-flow" typeId="heat-pump-request-t-flow"/>
<channel id="heat-pump-request-t-return" typeId="heat-pump-request-t-return"/>
<channel id="heat-pump-request-heat-sink" typeId="heat-pump-request-heat-sink"/>
<channel id="heatpump-relais-state" typeId="heatpump-relais-state-channel"/>
<channel id="heat-pump-vdae" typeId="heat-pump-vdae"/>
<channel id="heat-pump-vdaq" typeId="heat-pump-vdaq"/>
</channels>
</channel-group-type>
<channel-type id="heat-pump-error-state">
<item-type>Number</item-type>
<label>Heatpump Error State</label>
<state readOnly="true">
<options>
<option value="0">NONE</option>
<option value="1">MESSAGE</option>
<option value="2">WARNING</option>
<option value="3">ALARM</option>
<option value="4">FAULT</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-error-number">
<item-type>Number</item-type>
<label>Heatpump Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-state">
<item-type>Number</item-type>
<label>Heatpump State</label>
<state readOnly="true">
<options>
<option value="0">INIT</option>
<option value="1">REFERENCE</option>
<option value="2">RESTART-BLOCK</option>
<option value="3">READY</option>
<option value="4">START PUMPS</option>
<option value="5">START COMPRESSOR</option>
<option value="6">PRE-REGULATION</option>
<option value="7">REGULATION</option>
<option value="8">Not used</option>
<option value="9">COOLING</option>
<option value="10">DEFROSTING</option>
<option value="20">STOPPING</option>
<option value="30">FAULT-LOCK</option>
<option value="31">ALARM-BLOCK</option>
<option value="40">ERROR-RESET</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-operating-state">
<item-type>Number</item-type>
<label>Heatpump Operating State</label>
<state readOnly="true">
<options>
<option value="0">STBY</option>
<option value="1">CH</option>
<option value="2">DHW</option>
<option value="3">CC</option>
<option value="4">CIRCULATE</option>
<option value="5">DEFROST</option>
<option value="6">OFF</option>
<option value="7">FROST</option>
<option value="8">STBY-FROST</option>
<option value="9">Not used</option>
<option value="10">SUMMER</option>
<option value="11">HOLIDAY</option>
<option value="12">ERROR</option>
<option value="13">WARNING</option>
<option value="14">INFO-MESSAGE</option>
<option value="15">TIME-BLOCK</option>
<option value="16">RELEASE-BLOCK</option>
<option value="17">MINTEMP-BLOCK</option>
<option value="18">FIRMWARE-DOWNLOAD</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-t-flow">
<item-type>Number:Temperature</item-type>
<label>Heatpump T-Flow</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-t-return">
<item-type>Number:Temperature</item-type>
<label>Heatpump T-Return</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-vol-sink">
<item-type>Number:VolumetricFlowRate</item-type>
<label>Heatpump Volume flow heat sink</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-t-eqin">
<item-type>Number:Temperature</item-type>
<label>Heatpump Energy source inlet temperature</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-t-eqout">
<item-type>Number:Temperature</item-type>
<label>Heatpump Energy source outlet temperature</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-vol-source">
<item-type>Number:VolumetricFlowRate</item-type>
<label>Heatpump Volume flow energy source</label>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="heat-pump-compressor-rating">
<item-type>Number</item-type>
<label>Heatpump Compressor unit rating</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="heat-pump-qp-heating">
<item-type>Number:Power</item-type>
<label>Heatpump Actual heating capacity</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="heat-pump-fi-power-consumption">
<item-type>Number:Power</item-type>
<label>Heatpump Frequency inverter actual power consumption</label>
<state readOnly="true" pattern="%.1f kW"/>
</channel-type>
<channel-type id="heat-pump-cop">
<item-type>Number</item-type>
<label>Heatpump COP</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="heatpump-request-password-channel">
<item-type>Number</item-type>
<label>Requested Password</label>
<state readOnly="false"/>
</channel-type>
<channel-type id="heatpump-request-type-channel">
<item-type>Number</item-type>
<label>Requested Type</label>
<state readOnly="false">
<options>
<option value="0">NO REQUEST</option>
<option value="1">FLOW PUMP CIRCULATION</option>
<option value="2">CENTRAL HEATING</option>
<option value="3">CENTRAL COOLING</option>
<option value="4">DOMESTIC HOT WATER</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-request-t-flow">
<item-type>Number:Temperature</item-type>
<label>Heatpump requested T-Flow</label>
<state readOnly="false"/>
</channel-type>
<channel-type id="heat-pump-request-t-return">
<item-type>Number:Temperature</item-type>
<label>Heatpump requested T-Return</label>
<state readOnly="false"/>
</channel-type>
<channel-type id="heat-pump-request-heat-sink">
<item-type>Number:Temperature</item-type>
<label>Heatpump requested difference between T-Flow and T-Return</label>
<state readOnly="false"/>
</channel-type>
<channel-type id="heatpump-relais-state-channel">
<item-type>Number</item-type>
<label>Relais State</label>
<state readOnly="true">
<options>
<option value="1">NO Relais Error for 2nd heating stage activated</option>
</options>
</state>
</channel-type>
<channel-type id="heat-pump-vdae">
<item-type>Number:Energy</item-type>
<label>Heatpump VdA E since last reset</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="heat-pump-vdaq">
<item-type>Number:Energy</item-type>
<label>Heatpump VdA Q since last reset</label>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="heating-circuit">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Heating Circuit</label>
<description>Lambda Heating Circuit connected through modbus</description>
<channel-groups>
<channel-group id="heating-circuit-group" typeId="heating-circuit"/>
<channel-group id="heating-circuit-reg50-group" typeId="heating-circuit-reg50"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="0" unit="s">
<label>Refresh Interval</label>
<description>Refresh interval in seconds. Use zero to disable automatic polling.</description>
<default>30</default>
<unitLabel>s</unitLabel>
</parameter>
<parameter name="maxTries" type="integer" min="1">
<label>Maximum Tries</label>
<default>3</default>
<description>Number of tries when reading data, if some of the reading fail. For single try, enter 1.</description>
<advanced>true</advanced>
</parameter>
<parameter name="subindex" type="integer" required="true" min="0" max="11">
<label>Subindex of Heating Circuit (0..11)</label>
<description>See Lambda Modbus description</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="heating-circuit">
<label>Heating-Circuit Group</label>
<channels>
<channel id="heating-circuit-error-number" typeId="heating-circuit-error-number-channel"/>
<channel id="heating-circuit-operating-state" typeId="heating-circuit-operating-state-channel"/>
<channel id="heating-circuit-flow-line-temperature" typeId="heating-circuit-flow-line-temperature-channel"/>
<channel id="heating-circuit-return-line-temperature"
typeId="heating-circuit-return-line-temperature-channel"/>
<channel id="heating-circuit-room-device-temperature"
typeId="heating-circuit-room-device-temperature-channel"/>
<channel id="heating-circuit-setpoint-flow-line-temperature"
typeId="heating-circuit-setpoint-flow-line-temperature-channel"/>
<channel id="heating-circuit-operating-mode" typeId="heating-circuit-operating-mode-channel"/>
<channel id="heating-circuit-target-temperature-flow-line"
typeId="heating-circuit-target-temperature-flow-line-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="heating-circuit-reg50">
<label>Heating-Circuit Reg50 Group</label>
<channels>
<channel id="heating-circuit-offset-flow-line-temperature"
typeId="heating-circuit-offset-flow-line-temperature-channel"/>
<channel id="heating-circuit-room-heating-temperature"
typeId="heating-circuit-room-heating-temperature-channel"/>
<channel id="heating-circuit-room-cooling-temperature"
typeId="heating-circuit-room-cooling-temperature-channel"/>
</channels>
</channel-group-type>
<channel-type id="heating-circuit-error-number-channel">
<item-type>Number</item-type>
<label>Error Number</label>
<state readOnly="true">
<options>
<option value="0">No Error</option>
</options>
</state>
</channel-type>
<channel-type id="heating-circuit-operating-state-channel">
<item-type>Number</item-type>
<label>Operating State</label>
<state readOnly="true">
<options>
<option value="0">HEATING</option>
<option value="1">ECO</option>
<option value="2">COOLING</option>
<option value="3">FLOORDRY</option>
<option value="4">FROST</option>
<option value="5">MAX-TEMP</option>
<option value="6">ERROR</option>
<option value="7">SERVICE</option>
<option value="8">HOLIDAY</option>
<option value="9">CH-SUMMER</option>
<option value="10">CC-WINTER</option>
<option value="11">PRIO-STOP</option>
<option value="12">OFF</option>
<option value="13">RELEASE-OFF</option>
<option value="14">TIME-OFF</option>
<option value="15">STBY</option>
<option value="16">STBY-HEATING</option>
<option value="17">STBY-ECHO</option>
<option value="18">STBY-COOLING</option>
<option value="19">STBY-FROST</option>
<option value="20">STBY-FLOORDRY</option>
</options>
</state>
</channel-type>
<channel-type id="heating-circuit-flow-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Flow Line Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-return-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Return Line Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-room-device-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Room Device Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-setpoint-flow-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Setpoint Flow Line Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-operating-mode-channel">
<item-type>Number</item-type>
<label>Operating Mode</label>
<state readOnly="false">
<options>
<option value="0">OFF(RW)</option>
<option value="1">MANUAL(R)</option>
<option value="2">AUTOMATIK(RW)</option>
<option value="3">AUTO-HEATING(RW)</option>
<option value="4">AUTO-COOLING(RW)</option>
<option value="5">FROST(RW)</option>
<option value="6">SUMMER(RW)</option>
<option value="7">FLOOR-DRY(R)</option>
</options>
</state>
</channel-type>
<channel-type id="heating-circuit-offset-flow-line-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Set Offset Flow Line Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-room-heating-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Set Room Heating Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heating-circuit-room-cooling-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Set Room Cooling Temperature</label>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="modbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="solar">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
</supported-bridge-type-refs>
<label>Solar</label>
<description>Solar thermic component data</description>
<channel-groups>
<channel-group id="solar-group" typeId="solar"/>
<channel-group id="solar-reg50-group" typeId="solar-reg50"/>
</channel-groups>
<config-description>
<parameter name="refresh" type="integer" min="1" max="3600">
<label>Refresh Interval</label>
<description>Poll interval in seconds. Increase this if you encounter connection errors.</description>
<default>30</default>
</parameter>
<parameter name="maxTries" type="integer" min="1" max="10">
<label>Maximum Tries</label>
<description>Number of retries before giving up reading from this thing.</description>
<default>3</default>
</parameter>
<parameter name="subindex" type="integer" min="0" max="1" required="true">
<label>Subindex</label>
<description>Subindex for multiple solar components</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="solar">
<label>Solar</label>
<channels>
<channel id="solar-error-number" typeId="solar-error-number-channel"/>
<channel id="solar-operating-state" typeId="solar-operating-state-channel"/>
<channel id="solar-collector-temperature" typeId="solar-collector-temperature-channel"/>
<channel id="solar-buffer1-temperature" typeId="solar-buffer1-temperature-channel"/>
<channel id="solar-buffer2-temperature" typeId="solar-buffer2-temperature-channel"/>
</channels>
</channel-group-type>
<channel-group-type id="solar-reg50">
<label>Solar Reg50 Group</label>
<channels>
<channel id="solar-maximum-buffer-temperature" typeId="solar-maximum-buffer-temperature-channel"/>
<channel id="solar-buffer-changeover-temperature" typeId="solar-buffer-changeover-temperature-channel"/>
</channels>
</channel-group-type>
<channel-type id="solar-error-number-channel">
<item-type>Number</item-type>
<label>Solar Error Number</label>
<description>Error number (0 = No error)</description>
<state readOnly="true" pattern="%d">
<options>
<option value="0">NO ERROR</option>
</options>
</state>
</channel-type>
<channel-type id="solar-operating-state-channel">
<item-type>Number</item-type>
<label>Solar Operating State</label>
<description>Operating state of the solar component</description>
<state readOnly="true" pattern="%d">
<options>
<option value="0">STBY</option>
<option value="1">HEATING</option>
<option value="2">ERROR</option>
<option value="3">OFF</option>
</options>
</state>
</channel-type>
<channel-type id="solar-collector-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Solar Collector Temperature</label>
<description>Temperature of the solar collector</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="solar-buffer1-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Solar Buffer1 Temperature</label>
<description>Temperature of the solar Buffer 1</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="solar-buffer2-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Solar Buffer2 Temperature</label>
<description>Temperature of the solar Buffer 2</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="solar-maximum-buffer-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Solar Maximum Buffer Temperature</label>
<description>Maximum Buffer Temperature</description>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="solar-buffer-changeover-temperature-channel">
<item-type>Number:Temperature</item-type>
<label>Solar Buffer Changeover Temperature</label>
<description>Solar Buffer Changeover Temperature</description>
<state readOnly="false" pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>
+1
View File
@@ -279,6 +279,7 @@
<module>org.openhab.binding.modbus.e3dc</module>
<module>org.openhab.binding.modbus.foxinverter</module>
<module>org.openhab.binding.modbus.kermi</module>
<module>org.openhab.binding.modbus.lambda</module>
<module>org.openhab.binding.modbus.sbc</module>
<module>org.openhab.binding.modbus.studer</module>
<module>org.openhab.binding.modbus.sungrow</module>
@@ -37,6 +37,7 @@
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.e3dc/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.helioseasycontrols/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.kermi/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.lambda/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.sbc/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.stiebeleltron/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.studer/${project.version}</bundle>