From 95fb888c097d0ee29720cb4b96b4f3374a0e3810 Mon Sep 17 00:00:00 2001 From: openhab-bot Date: Mon, 22 Sep 2025 20:21:24 +0200 Subject: [PATCH 01/24] New Crowdin updates (#19375) * New translations bluetooth.properties (German) * New translations bluetooth.properties (Italian) Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../src/main/resources/OH-INF/i18n/bluetooth_de.properties | 4 ---- .../src/main/resources/OH-INF/i18n/bluetooth_it.properties | 4 ---- 2 files changed, 8 deletions(-) diff --git a/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_de.properties b/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_de.properties index a6a8734826a3f..bf7387b7321e7 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_de.properties +++ b/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_de.properties @@ -7,15 +7,11 @@ addon.bluetooth.description = Diese Bindung unterstützt das Bluetooth-Protokoll thing-type.bluetooth.beacon.label = Bluetooth Gerät thing-type.bluetooth.beacon.description = Ein generisches Bluetooth-Gerät im Beacon-Modus -thing-type.bluetooth.connected.label = Verbundenes Bluetoothgerät -thing-type.bluetooth.connected.description = Ein generisches Bluetooth-Gerät im verbundenen Modus # thing types config thing-type.config.bluetooth.beacon.address.label = Adresse thing-type.config.bluetooth.beacon.address.description = Die einzigartige Bluetooth-Adresse des Geräts -thing-type.config.bluetooth.connected.address.label = Adresse -thing-type.config.bluetooth.connected.address.description = Die einzigartige Bluetooth-Adresse des Geräts # channel types diff --git a/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_it.properties b/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_it.properties index 269c86305a071..75c1d208c3670 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_it.properties +++ b/bundles/org.openhab.binding.bluetooth/src/main/resources/OH-INF/i18n/bluetooth_it.properties @@ -7,15 +7,11 @@ addon.bluetooth.description = Questo binding supporta il protocollo Bluetooth. thing-type.bluetooth.beacon.label = Dispositivo Bluetooth thing-type.bluetooth.beacon.description = Un dispositivo Bluetooth generico in modalità beacon -thing-type.bluetooth.connected.label = Dispositivo Bluetooth Connesso -thing-type.bluetooth.connected.description = Un dispositivo Bluetooth generico in modalità connessa # thing types config thing-type.config.bluetooth.beacon.address.label = Indirizzo thing-type.config.bluetooth.beacon.address.description = L'indirizzo Bluetooth univoco del dispositivo -thing-type.config.bluetooth.connected.address.label = Indirizzo -thing-type.config.bluetooth.connected.address.description = L'indirizzo Bluetooth univoco del dispositivo # channel types From 66e7b900a569719c23f2f8eeb3183650ffca58fe Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:18:49 +0200 Subject: [PATCH 02/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../org.openhab.binding.modbus.lambda/NOTICE | 13 + .../README.md | 315 ++++++++++ .../org.openhab.binding.modbus.lambda/pom.xml | 25 + .../lambda/internal/BoilerConfiguration.java | 38 ++ .../lambda/internal/BufferConfiguration.java | 38 ++ .../lambda/internal/GeneralConfiguration.java | 47 ++ .../internal/HeatingCircuitConfiguration.java | 38 ++ .../internal/HeatpumpConfiguration.java | 38 ++ .../internal/LambdaBindingConstants.java | 141 +++++ .../lambda/internal/LambdaHandlerFactory.java | 71 +++ .../lambda/internal/SolarConfiguration.java | 39 ++ .../lambda/internal/dto/AmbientBlock.java | 28 + .../lambda/internal/dto/BoilerBlock.java | 29 + .../lambda/internal/dto/BoilerReg50Block.java | 25 + .../lambda/internal/dto/BufferBlock.java | 33 + .../lambda/internal/dto/BufferReg50Block.java | 25 + .../lambda/internal/dto/EManagerBlock.java | 29 + .../internal/dto/HeatingCircuitBlock.java | 31 + .../dto/HeatingCircuitReg50Block.java | 27 + .../lambda/internal/dto/HeatpumpBlock.java | 45 ++ .../lambda/internal/dto/SolarBlock.java | 27 + .../lambda/internal/dto/SolarReg50Block.java | 26 + .../internal/handler/BoilerHandler.java | 535 ++++++++++++++++ .../internal/handler/BufferHandler.java | 582 +++++++++++++++++ .../internal/handler/GeneralHandler.java | 539 ++++++++++++++++ .../handler/HeatingCircuitHandler.java | 587 ++++++++++++++++++ .../internal/handler/HeatpumpHandler.java | 559 +++++++++++++++++ .../internal/handler/LambdaException.java | 35 ++ .../lambda/internal/handler/SolarHandler.java | 453 ++++++++++++++ .../internal/parser/AbstractBaseParser.java | 157 +++++ .../internal/parser/AmbientBlockParser.java | 38 ++ .../internal/parser/BoilerBlockParser.java | 37 ++ .../parser/BoilerReg50BlockParser.java | 34 + .../internal/parser/BufferBlockParser.java | 43 ++ .../parser/BufferReg50BlockParser.java | 34 + .../internal/parser/EManagerBlockParser.java | 40 ++ .../parser/HeatingCircuitBlockParser.java | 41 ++ .../HeatingCircuitReg50BlockParser.java | 36 ++ .../internal/parser/HeatpumpBlockParser.java | 55 ++ .../internal/parser/SolarBlockParser.java | 48 ++ .../parser/SolarReg50BlockParser.java | 36 ++ .../src/main/resources/OH-INF/addon/addon.xml | 10 + .../resources/OH-INF/i18n/lambda.properties | 155 +++++ .../OH-INF/thing/boiler-thing-types.xml | 117 ++++ .../OH-INF/thing/buffer-thing-types.xml | 146 +++++ .../OH-INF/thing/general-thing-types.xml | 148 +++++ .../OH-INF/thing/heat-pump-thing-types.xml | 268 ++++++++ .../thing/heating-circuit-thing-types.xml | 168 +++++ .../OH-INF/thing/solar-thing-types.xml | 111 ++++ bundles/pom.xml | 1 + .../src/main/resources/footer.xml | 1 + 53 files changed, 6148 insertions(+) create mode 100644 bundles/org.openhab.binding.modbus.lambda/NOTICE create mode 100644 bundles/org.openhab.binding.modbus.lambda/README.md create mode 100644 bundles/org.openhab.binding.modbus.lambda/pom.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BoilerConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BufferConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/GeneralConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatingCircuitConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatpumpConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/SolarConfiguration.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/AmbientBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerReg50Block.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferReg50Block.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/EManagerBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitReg50Block.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatpumpBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarReg50Block.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/LambdaException.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AbstractBaseParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AmbientBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferReg50BlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/EManagerBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitReg50BlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatpumpBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarBlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarReg50BlockParser.java create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/general-thing-types.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heat-pump-thing-types.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml create mode 100644 bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/solar-thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 1865229aed998..6416012bcefd6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -244,6 +244,7 @@ /bundles/org.openhab.binding.modbus/ @ssalonen /bundles/org.openhab.binding.modbus.e3dc/ @weymann /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 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 1ce231440bb18..87480e2e97b1b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1211,6 +1211,11 @@ org.openhab.binding.modbus.kermi ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.modbus.lambda + ${project.version} + org.openhab.addons.bundles org.openhab.binding.modbus.sbc diff --git a/bundles/org.openhab.binding.modbus.lambda/NOTICE b/bundles/org.openhab.binding.modbus.lambda/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/NOTICE @@ -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 diff --git a/bundles/org.openhab.binding.modbus.lambda/README.md b/bundles/org.openhab.binding.modbus.lambda/README.md new file mode 100644 index 0000000000000..29245a293a595 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/README.md @@ -0,0 +1,315 @@ +# Lambda Heat Pump + +This extension adds support for the Lambda Heat Pump modbus protocol as provided by + + +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 | +| 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 | +| 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 | +| boiler-actual-circulation-temperature | Number:Temperature | true | Actual circulation temperature | +| boiler-actual-circulation-pump-state | Number | true | Actual circulation pump state | +| 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) +''' + diff --git a/bundles/org.openhab.binding.modbus.lambda/pom.xml b/bundles/org.openhab.binding.modbus.lambda/pom.xml new file mode 100644 index 0000000000000..0bd8326f8fc8e --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/pom.xml @@ -0,0 +1,25 @@ + + + + 4.0.0 + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 5.1.0-SNAPSHOT + + + org.openhab.binding.modbus.lambda + + openHAB Add-ons :: Bundles :: Lambda Binding + + + + org.openhab.addons.bundles + org.openhab.binding.modbus + ${project.version} + provided + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BoilerConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BoilerConfiguration.java new file mode 100644 index 0000000000000..268554251444d --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BoilerConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BufferConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BufferConfiguration.java new file mode 100644 index 0000000000000..305acef9c7a6e --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/BufferConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/GeneralConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/GeneralConfiguration.java new file mode 100644 index 0000000000000..59cdafb853212 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/GeneralConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatingCircuitConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatingCircuitConfiguration.java new file mode 100644 index 0000000000000..a6b03a27a2bdb --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatingCircuitConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatpumpConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatpumpConfiguration.java new file mode 100644 index 0000000000000..e384e2ea27d2e --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/HeatpumpConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java new file mode 100644 index 0000000000000..30effa86eeff2 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java @@ -0,0 +1,141 @@ +/* + * 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; + private static final String GENERAL = "general"; + private static final String BOILER = "boiler"; + private static final String BUFFER = "buffer"; + private static final String HEAT_PUMP = "heat-pump"; + private static final String HEATING_CIRCUIT = "heating-circuit"; + private static final String SOLAR = "solar"; + + // 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_HEATING_CIRCUIT = new ThingTypeUID(BINDING_ID, HEATING_CIRCUIT); + public static final ThingTypeUID THING_TYPE_SOLAR = new ThingTypeUID(BINDING_ID, SOLAR); + + // 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_HEATING_CIRCUIT = "heating-circuit-group"; + public static final String GROUP_HEATING_CIRCUIT_REG50 = "heating-circuit-reg50-group"; + public static final String GROUP_SOLAR = "solar-group"; + public static final String GROUP_SOLAR_REG50 = "solar-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_ACTUAL_CIRCULATION_TEMPERATURE = "boiler-actual-circulation-low-temperature"; + public static final String CHANNEL_BOILER_ACTUAL_CIRCULATION_PUMP_STATE = "boiler-actual-circulation-pump-state"; + public static final String CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE = "boiler-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 = "buffer-maximum-buffer-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"; + + // 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"; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java new file mode 100644 index 0000000000000..442938350d846 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java @@ -0,0 +1,71 @@ +/* + * 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.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 SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GENERAL, THING_TYPE_HEAT_PUMP, + THING_TYPE_BOILER, THING_TYPE_BUFFER, THING_TYPE_HEATING_CIRCUIT); + + private static final Map> 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); + + @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 factory = HANDLER_FACTORY_MAP.get(thingTypeUID); + return factory != null ? factory.apply(thing) : null; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/SolarConfiguration.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/SolarConfiguration.java new file mode 100644 index 0000000000000..79b841f40e0f7 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/SolarConfiguration.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/AmbientBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/AmbientBlock.java new file mode 100644 index 0000000000000..24dc715e846b7 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/AmbientBlock.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java new file mode 100644 index 0000000000000..351f43074956f --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java @@ -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 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; + public int boilerActualCirculationTemperature; + public int boilerActualCirculationPumpState; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerReg50Block.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerReg50Block.java new file mode 100644 index 0000000000000..08f6e8b043ed2 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerReg50Block.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferBlock.java new file mode 100644 index 0000000000000..847d6c599eaa5 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferBlock.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferReg50Block.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferReg50Block.java new file mode 100644 index 0000000000000..ed741ba3a8448 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BufferReg50Block.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/EManagerBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/EManagerBlock.java new file mode 100644 index 0000000000000..f8fa19bad6c5d --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/EManagerBlock.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitBlock.java new file mode 100644 index 0000000000000..33079ed119a90 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitBlock.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitReg50Block.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitReg50Block.java new file mode 100644 index 0000000000000..eb672c8439e72 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatingCircuitReg50Block.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatpumpBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatpumpBlock.java new file mode 100644 index 0000000000000..283a3f78f1706 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/HeatpumpBlock.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java new file mode 100644 index 0000000000000..bdb357e9be6cf --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java @@ -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 + */ +public class SolarBlock { + public int solarErrorNumber; + public int solarOperatingState; + public int solarCollectorTemperature; + public int solarBuffer1Temperature; + public int solarBuffer2Temperature; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarReg50Block.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarReg50Block.java new file mode 100644 index 0000000000000..3d8c4f985994b --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarReg50Block.java @@ -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; +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java new file mode 100644 index 0000000000000..d34e21ddd0c51 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java @@ -0,0 +1,535 @@ +/* + * 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 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 = 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 = 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: Es wird gepollt }"); + 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 = 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); + } + }; + // neu: poller.registerPollTask(baseadress, 6, ModbusReadFunctionCode.READ_MULTIPLE_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; + + 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)); + // neu: updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_CIRCULATION_TEMPERATURE), + // neu: getScaled(block.boilerActualCirculationTemperature, CELSIUS, -1.0)); + // neu: updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_CIRCULATION_PUMP_STATE), + // neu: new DecimalType(block.boilerActualCirculationPumpState)); + resetCommunicationError(); + } + + protected void handlePolledBoilerReg50Data(ModbusRegisterArray registers) { + BoilerReg50Block block = boilerReg50BlockParser.parse(registers); + + // BoilerReg50 group + 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java new file mode 100644 index 0000000000000..b3561fcf3d045 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java @@ -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 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 final Logger logger = LoggerFactory.getLogger(BufferHandler.class); + + 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 = 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 = 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) { + 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 = 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; + + 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java new file mode 100644 index 0000000000000..0a83067cfd5cf --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java @@ -0,0 +1,539 @@ +/* + * 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 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 = 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 = 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) { + 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java new file mode 100644 index 0000000000000..cd1a189ebdc37 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -0,0 +1,587 @@ +/* + * 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 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 = 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 = 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) { + 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 = 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java new file mode 100644 index 0000000000000..34a14a75d17d1 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -0,0 +1,559 @@ +/* + * 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 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 final Logger logger = LoggerFactory.getLogger(HeatpumpHandler.class); + + 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 = 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 = 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); + }); + } + + /** + * @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_HEAT_PUMP: + poller = heatpumpPoller; + break; + default: + poller = null; + break; + } + if (poller != null) { + 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 = 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/LambdaException.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/LambdaException.java new file mode 100644 index 0000000000000..1e5b2692e1c3a --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/LambdaException.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java new file mode 100644 index 0000000000000..a6d7e02b3acc9 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -0,0 +1,453 @@ +/* + * 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 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 final Logger logger = LoggerFactory.getLogger(SolarHandler.class); + + 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 = 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 = 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 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"); + } + + @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 in progress"); + 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 = 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AbstractBaseParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AbstractBaseParser.java new file mode 100644 index 0000000000000..ddda4421dd3a5 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AbstractBaseParser.java @@ -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 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 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 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 extractOptionalUInt32(ModbusRegisterArray raw, int index) { + return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.UINT32).map(DecimalType::longValue) + .filter(value -> value != 0); + } + + protected Optional 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)); + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AmbientBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AmbientBlockParser.java new file mode 100644 index 0000000000000..eba6d0e99ce2c --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/AmbientBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerBlockParser.java new file mode 100644 index 0000000000000..417ef3a44602a --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java new file mode 100644 index 0000000000000..70c66c5b3ddef --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java @@ -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.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 = extractUInt16(raw, 0, (short) 0); + return block; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferBlockParser.java new file mode 100644 index 0000000000000..fece431371e33 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferReg50BlockParser.java new file mode 100644 index 0000000000000..839661f424b8b --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BufferReg50BlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/EManagerBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/EManagerBlockParser.java new file mode 100644 index 0000000000000..c7bf8882ca294 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/EManagerBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitBlockParser.java new file mode 100644 index 0000000000000..f030e57712ce0 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitReg50BlockParser.java new file mode 100644 index 0000000000000..967d3a215b666 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatingCircuitReg50BlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatpumpBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatpumpBlockParser.java new file mode 100644 index 0000000000000..790354cde250f --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/HeatpumpBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarBlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarBlockParser.java new file mode 100644 index 0000000000000..8345c2d0f410e --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarBlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarReg50BlockParser.java new file mode 100644 index 0000000000000..4acc9e0018eb6 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/SolarReg50BlockParser.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..017963f038028 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + binding + Lambda Heat Pump Binding + Lambda heat pump binding via Modbus TCP/IP. + local + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties new file mode 100644 index 0000000000000..532312437bd50 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties @@ -0,0 +1,155 @@ +# 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.buffer.label = Buffer Group +channel-group-type.modbus.heating-circuit.label = Heating Circuit Group +channel-group-type.modbus.solar.label = Solar 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.boiler-actual-circulation-temperature-channel.label = Boiler Actual Circulation Temperature +channel-type.modbus.boiler-actual-circulation-pump-state-channel.label = Boiler Actual Circulation Pump State +channel-type.modbus.boiler-state-channel.label = Boiler Operating State +channel-type.modbus.maximum-boiler-temperature-channel.label = Boiler Maximum 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 = Buffer Maximum 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 diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml new file mode 100644 index 0000000000000..32efdf30c6df6 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml @@ -0,0 +1,117 @@ + + + + + + + + + + Lambda Boiler connected through modbus + + + + + + + + + Poll interval in seconds. Increase this if you encounter connection errors. + 30 + + + + Number of retries before giving up reading from this thing. + 3 + + + + Subindex for multiple boiler components + 0 + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number + + + + + + + + + + + Number:Temperature + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml new file mode 100644 index 0000000000000..41f773bc10fa6 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml @@ -0,0 +1,146 @@ + + + + + + + + + Lambda Buffer connected through modbus + + + + + + + + + Poll interval in seconds. Increase this if you encounter connection errors. + 30 + + + + Number of retries before giving up reading from this thing. + 3 + + + + Subindex for multiple buffer components + 0 + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Power + + + + + + Number:Temperature + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/general-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/general-thing-types.xml new file mode 100644 index 0000000000000..d2387f1dc0d84 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/general-thing-types.xml @@ -0,0 +1,148 @@ + + + + + + + + + Ambient and E-Manager sections connected through modbus + + + + + + + + + + Poll interval in seconds. Increase this if you encounter connection errors. + 30 + + + + Number of retries before giving up reading from this thing. + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + Number:Temperature + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + + Number:Power + + + + + + Number:Power + + + + + + Number:Power + + + + + + Number:Power + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heat-pump-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heat-pump-thing-types.xml new file mode 100644 index 0000000000000..503cf80c86680 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heat-pump-thing-types.xml @@ -0,0 +1,268 @@ + + + + + + + + + + Lambda Heat Pump connected through modbus + + + + + + + + + Refresh interval in seconds. Use zero to disable automatic polling. + 30 + s + + + + + 3 + Number of tries when reading data, if some of the reading fail. For single try, enter 1. + true + + + + + See Lambda Modbus description + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:VolumetricFlowRate + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:VolumetricFlowRate + + + + + + Number + + + + + + Number:Power + + + + + + Number:Power + + + + + + Number + + + + + + Number + + + + + + Number + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number + + + + + + + + + + Number:Energy + + + + + + Number:Energy + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml new file mode 100644 index 0000000000000..397b0427111b2 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + Lambda Heating Circuit connected through modbus + + + + + + + + + + Refresh interval in seconds. Use zero to disable automatic polling. + 30 + s + + + + + 3 + Number of tries when reading data, if some of the reading fail. For single try, enter 1. + true + + + + + See Lambda Modbus description + 0 + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number + + + + + + + + + + + + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + + Number:Temperature + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/solar-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/solar-thing-types.xml new file mode 100644 index 0000000000000..2ea78954b440e --- /dev/null +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/solar-thing-types.xml @@ -0,0 +1,111 @@ + + + + + + + + + + Solar thermic component data + + + + + + + + + Poll interval in seconds. Increase this if you encounter connection errors. + 30 + + + + Number of retries before giving up reading from this thing. + 3 + + + + Subindex for multiple solar components + 0 + + + + + + + + + + + + + + + + + + + Number + + Error number (0 = No error) + + + + + + + + + Number + + Operating state of the solar component + + + + + + + + + + + + Number:Temperature + + Temperature of the solar collector + + + + + Number:Temperature + + Temperature of the solar Buffer 1 + + + + + Number:Temperature + + Temperature of the solar Buffer 2 + + + + + Number:Temperature + + Maximum Buffer Temperature + + + + + Number:Temperature + + Solar Buffer Changeover Temperature + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 818669b741fcf..f4d931e5a3146 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -277,6 +277,7 @@ org.openhab.binding.modbus org.openhab.binding.modbus.e3dc org.openhab.binding.modbus.kermi + org.openhab.binding.modbus.lambda org.openhab.binding.modbus.sbc org.openhab.binding.modbus.studer org.openhab.binding.modbus.sungrow diff --git a/features/openhab-addons/src/main/resources/footer.xml b/features/openhab-addons/src/main/resources/footer.xml index 50891eba1ab13..3001a55b642dc 100644 --- a/features/openhab-addons/src/main/resources/footer.xml +++ b/features/openhab-addons/src/main/resources/footer.xml @@ -57,6 +57,7 @@ mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.e3dc/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.helioseasycontrols/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.kermi/${project.version} + mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.lambda/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.sbc/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.stiebeleltron/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.studer/${project.version} From 4644f43049bcc8ef4f726348ded3dc5d69f367db Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:38:57 +0200 Subject: [PATCH 03/24] Fixed Co-Pilot guidance and formatting error Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- bom/openhab-addons/pom.xml | 2 +- .../binding/modbus/lambda/internal/LambdaHandlerFactory.java | 5 +++-- .../modbus/lambda/internal/handler/BoilerHandler.java | 3 +-- .../modbus/lambda/internal/handler/BufferHandler.java | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 87480e2e97b1b..1b165f120f860 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1215,7 +1215,7 @@ org.openhab.addons.bundles org.openhab.binding.modbus.lambda ${project.version} - + org.openhab.addons.bundles org.openhab.binding.modbus.sbc diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java index 442938350d846..d2b571edcd013 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaHandlerFactory.java @@ -25,6 +25,7 @@ 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; @@ -48,12 +49,12 @@ public class LambdaHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LambdaHandlerFactory.class); private static final Set 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_BOILER, THING_TYPE_BUFFER, THING_TYPE_HEATING_CIRCUIT, THING_TYPE_SOLAR); private static final Map> 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); + HeatingCircuitHandler::new, THING_TYPE_SOLAR, SolarHandler::new); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java index d34e21ddd0c51..7cbf811270f6d 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java @@ -224,7 +224,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { - logger.trace("Boiler: Es wird gepollt }"); + logger.trace("Boiler: Polling initiated"); poller.poll(); } } @@ -347,7 +347,6 @@ public void dispose() { private void tearDown() { AbstractBasePoller poller = boilerPoller; - poller = boilerPoller; if (poller != null) { poller.unregisterPollTask(); boilerPoller = null; diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java index b3561fcf3d045..82eea182000c9 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java @@ -385,7 +385,6 @@ public void dispose() { private void tearDown() { AbstractBasePoller poller = bufferPoller; - poller = bufferPoller; if (poller != null) { poller.unregisterPollTask(); bufferPoller = null; From 10960481952b74150a416443872a2339e95685eb Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:40:48 +0200 Subject: [PATCH 04/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../internal/handler/BufferHandler.java | 3 +- .../internal/handler/GeneralHandler.java | 1 + .../handler/HeatingCircuitHandler.java | 1 + .../internal/handler/HeatpumpHandler.java | 33 ++++++++-------- .../lambda/internal/handler/SolarHandler.java | 39 ++++++++++--------- 5 files changed, 40 insertions(+), 37 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java index 82eea182000c9..0cbe540b7fdaf 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java @@ -63,8 +63,6 @@ public class BufferHandler extends BaseThingHandler { public abstract class AbstractBasePoller { - private final Logger logger = LoggerFactory.getLogger(BufferHandler.class); - private volatile @Nullable PollTask pollTask; public synchronized void unregisterPollTask() { @@ -240,6 +238,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { + logger.trace("Buffer: Polling initiated"); poller.poll(); } } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java index 0a83067cfd5cf..ae415e04f3013 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java @@ -222,6 +222,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { + logger.trace("General: Polling initiated"); poller.poll(); } } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java index cd1a189ebdc37..89b492069e392 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -243,6 +243,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { + logger.trace("HeatingCircuit: Polling initiated"); poller.poll(); } } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 34a14a75d17d1..5f1cfcf37db37 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -61,8 +61,6 @@ public class HeatpumpHandler extends BaseThingHandler { public abstract class AbstractBasePoller { - private final Logger logger = LoggerFactory.getLogger(HeatpumpHandler.class); - private volatile @Nullable PollTask pollTask; public synchronized void unregisterPollTask() { @@ -184,21 +182,23 @@ protected void writeInt16(int address, short shortValue) { * @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 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) { @@ -233,6 +233,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { + logger.trace("Heatpump: Polling initiated"); poller.poll(); } } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index a6d7e02b3acc9..60ca5ff16f606 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -14,7 +14,7 @@ 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 static org.openhab.core.library.unit.Units.*; import javax.measure.Unit; @@ -64,8 +64,6 @@ public class SolarHandler extends BaseThingHandler { public abstract class AbstractBasePoller { - private final Logger logger = LoggerFactory.getLogger(SolarHandler.class); - private volatile @Nullable PollTask pollTask; public synchronized void unregisterPollTask() { @@ -167,21 +165,24 @@ protected void writeInt16(int address, short shortValue) { }); } - 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 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); @@ -215,7 +216,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; } if (poller != null) { - logger.trace("Solar: Polling in progress"); + logger.trace("Solar: Polling initiated"); poller.poll(); } } From 7fe2eae555987e425067776dc931bbca40a5e9c9 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:46:41 +0200 Subject: [PATCH 05/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/SolarHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index 60ca5ff16f606..ec75ae86cfa9e 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -14,7 +14,6 @@ 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 javax.measure.Unit; @@ -165,7 +164,7 @@ protected void writeInt16(int address, short shortValue) { }); } - /** + /** May be used in the future * * private short getInt16Value(Command command) throws LambdaException { * if (command instanceof QuantityType quantityCommand) { @@ -183,6 +182,7 @@ protected void writeInt16(int address, short shortValue) { * } * */ + private short getScaledInt16Value(Command command) throws LambdaException { if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(CELSIUS); From f5957ee85962c9c8e64c57c4be11ca6394792a2f Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Tue, 21 Oct 2025 10:29:56 +0200 Subject: [PATCH 06/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/SolarHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index ec75ae86cfa9e..832e032a77cd5 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -164,7 +164,8 @@ protected void writeInt16(int address, short shortValue) { }); } - /** May be used in the future + /** + * May be used in the future * * private short getInt16Value(Command command) throws LambdaException { * if (command instanceof QuantityType quantityCommand) { From 7648e4638e49a9192717b846410dab439e64adcf Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:08:20 +0100 Subject: [PATCH 07/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../README.md | 15 +++--- .../internal/LambdaBindingConstants.java | 46 ++++++++----------- .../lambda/internal/dto/BoilerBlock.java | 2 - .../internal/handler/BoilerHandler.java | 7 +-- .../lambda/internal/handler/SolarHandler.java | 20 -------- .../parser/BoilerReg50BlockParser.java | 5 +- .../resources/OH-INF/i18n/lambda.properties | 15 +++--- .../OH-INF/thing/boiler-thing-types.xml | 26 ++++------- .../OH-INF/thing/buffer-thing-types.xml | 7 +++ .../thing/heating-circuit-thing-types.xml | 9 +++- .../OH-INF/thing/solar-thing-types.xml | 9 +++- 11 files changed, 70 insertions(+), 91 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/README.md b/bundles/org.openhab.binding.modbus.lambda/README.md index 29245a293a595..a69b37b776251 100644 --- a/bundles/org.openhab.binding.modbus.lambda/README.md +++ b/bundles/org.openhab.binding.modbus.lambda/README.md @@ -21,6 +21,7 @@ Note, that the things will show up under the Modbus binding. | 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. @@ -64,6 +65,7 @@ Heat Pump, Boiler, Buffer and Heating Circuit things use another parameter Subin | Heatpump | 0..2 | | Boiler | 0..4 | | Buffer | 0..4 | +| Solar | 0..2 | | Heating Circuit | 0..11 | ## Channels @@ -134,8 +136,6 @@ This group contains information about the boiler for the water for domestic use | 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 | -| boiler-actual-circulation-temperature | Number:Temperature | true | Actual circulation temperature | -| boiler-actual-circulation-pump-state | Number | true | Actual circulation pump state | | maximum-boiler-temperature | Number:Temperature | false | Setting for maximum boiler temperature (min = 25.0°C; max = 65.0°C) | ### Buffer Group @@ -170,7 +170,6 @@ This group contains information about the solar thermic component. | 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. @@ -221,11 +220,11 @@ Number lambdabuffer_operatingstate "Buffer Operatin 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 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: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" } ``` @@ -268,9 +267,9 @@ Number lambdasolar_operatingstate "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" } +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 diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java index 30effa86eeff2..601d31e90c82c 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/LambdaBindingConstants.java @@ -27,20 +27,14 @@ public class LambdaBindingConstants { private static final String BINDING_ID = ModbusBindingConstants.BINDING_ID; - private static final String GENERAL = "general"; - private static final String BOILER = "boiler"; - private static final String BUFFER = "buffer"; - private static final String HEAT_PUMP = "heat-pump"; - private static final String HEATING_CIRCUIT = "heating-circuit"; - private static final String SOLAR = "solar"; // 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_HEATING_CIRCUIT = new ThingTypeUID(BINDING_ID, HEATING_CIRCUIT); - public static final ThingTypeUID THING_TYPE_SOLAR = new ThingTypeUID(BINDING_ID, SOLAR); + 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"; @@ -50,10 +44,10 @@ public class LambdaBindingConstants { 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_HEATING_CIRCUIT = "heating-circuit-group"; - public static final String GROUP_HEATING_CIRCUIT_REG50 = "heating-circuit-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 @@ -100,9 +94,7 @@ public class LambdaBindingConstants { 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_ACTUAL_CIRCULATION_TEMPERATURE = "boiler-actual-circulation-low-temperature"; - public static final String CHANNEL_BOILER_ACTUAL_CIRCULATION_PUMP_STATE = "boiler-actual-circulation-pump-state"; - public static final String CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE = "boiler-maximum-boiler-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"; @@ -115,7 +107,16 @@ public class LambdaBindingConstants { 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 = "buffer-maximum-buffer-temperature"; + 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"; @@ -129,13 +130,4 @@ public class LambdaBindingConstants { 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"; - - // 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"; } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java index 351f43074956f..eceea63f428c5 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/BoilerBlock.java @@ -24,6 +24,4 @@ public class BoilerBlock { public int boilerOperatingState; public int boilerActualHighTemperature; public int boilerActualLowTemperature; - public int boilerActualCirculationTemperature; - public int boilerActualCirculationPumpState; } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java index 7cbf811270f6d..c3f9a10e5d833 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java @@ -313,7 +313,6 @@ protected void handlePolledData(ModbusRegisterArray registers) { handlePolledBoilerData(registers); } }; - // neu: poller.registerPollTask(baseadress, 6, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS); poller.registerPollTask(baseadress, 4, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS); boilerPoller = poller; } @@ -439,17 +438,13 @@ protected void handlePolledBoilerData(ModbusRegisterArray registers) { getScaled(block.boilerActualHighTemperature, CELSIUS, -1.0)); updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_LOW_TEMPERATURE), getScaled(block.boilerActualLowTemperature, CELSIUS, -1.0)); - // neu: updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_CIRCULATION_TEMPERATURE), - // neu: getScaled(block.boilerActualCirculationTemperature, CELSIUS, -1.0)); - // neu: updateState(channelUID(GROUP_BOILER, CHANNEL_BOILER_ACTUAL_CIRCULATION_PUMP_STATE), - // neu: new DecimalType(block.boilerActualCirculationPumpState)); resetCommunicationError(); } protected void handlePolledBoilerReg50Data(ModbusRegisterArray registers) { BoilerReg50Block block = boilerReg50BlockParser.parse(registers); - // BoilerReg50 group + // BoilerReg50 groupaximumBoilerTemperature: {}", updateState(channelUID(GROUP_BOILER_REG50, CHANNEL_BOILER_MAXIMUM_BOILER_TEMPERATURE), getScaled(block.boilerMaximumBoilerTemperature, CELSIUS, -1.0)); resetCommunicationError(); diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index 832e032a77cd5..3fc6224b968ba 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -164,26 +164,6 @@ protected void writeInt16(int address, short shortValue) { }); } - /** - * May be used in the future - * - * 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); diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java index 70c66c5b3ddef..439eedd2a3d80 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java @@ -15,6 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.modbus.lambda.internal.dto.BoilerReg50Block; import org.openhab.core.io.transport.modbus.ModbusRegisterArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Parses modbus data into an BoilerReg50 Block @@ -25,10 +27,11 @@ */ @NonNullByDefault public class BoilerReg50BlockParser extends AbstractBaseParser { + private final Logger logger = LoggerFactory.getLogger(BoilerReg50BlockParser.class); public BoilerReg50Block parse(ModbusRegisterArray raw) { BoilerReg50Block block = new BoilerReg50Block(); - block.boilerMaximumBoilerTemperature = extractUInt16(raw, 0, (short) 0); + block.boilerMaximumBoilerTemperature = extractInt16(raw, 0, (short) 0); return block; } } diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties index 532312437bd50..d363c48c97b86 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/i18n/lambda.properties @@ -59,12 +59,16 @@ 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.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.heating-circuit.label = Heating Circuit 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 @@ -113,10 +117,7 @@ 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.boiler-actual-circulation-temperature-channel.label = Boiler Actual Circulation Temperature -channel-type.modbus.boiler-actual-circulation-pump-state-channel.label = Boiler Actual Circulation Pump State -channel-type.modbus.boiler-state-channel.label = Boiler Operating State -channel-type.modbus.maximum-boiler-temperature-channel.label = Boiler Maximum Temperature +channel-type.modbus.maximum-boiler-temperature-channel.label = Maximum Boiler Temperature # Buffer channel-type.modbus.buffer-error-number-channel.label = Buffer Error Number @@ -129,7 +130,7 @@ channel-type.modbus.buffer-request-flow-line-temperature-channel.label = Buffer 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 = Buffer Maximum Temperature +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 diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml index 32efdf30c6df6..9febd83fd84bc 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/boiler-thing-types.xml @@ -14,6 +14,7 @@ + @@ -43,6 +44,12 @@ + + + + + + @@ -91,26 +98,9 @@ - - Number:Temperature - - - - - - Number - - - - - - - - - Number:Temperature - + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml index 41f773bc10fa6..c24671ccac8d1 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/buffer-thing-types.xml @@ -13,6 +13,7 @@ + @@ -48,6 +49,12 @@ + + + + + + diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml index 397b0427111b2..45dc56c5d01cb 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/resources/OH-INF/thing/heating-circuit-thing-types.xml @@ -15,6 +15,7 @@ + @@ -43,7 +44,7 @@ - + @@ -57,6 +58,12 @@ + + + + + + + @@ -33,8 +34,8 @@ 0 - + @@ -43,6 +44,12 @@ + + + + + + From 704379d4f390b622ddac3adcbbc238864f3c74dc Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:42:02 +0100 Subject: [PATCH 08/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../modbus/lambda/internal/handler/HeatingCircuitHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java index 89b492069e392..e85ecfecde380 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -193,7 +193,7 @@ protected void writeInt16(int address, short shortValue) { * @return short the value of the command as short */ private short getInt16Value(Command command) throws LambdaException { - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(WATT); if (c != null) { return c.shortValue(); From 417f58fd5cbf52e93388ae43d0bc3967195c6546 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:42:36 +0100 Subject: [PATCH 09/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../modbus/lambda/internal/handler/HeatingCircuitHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java index e85ecfecde380..a51c0a0796152 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -208,7 +208,7 @@ private short getInt16Value(Command command) throws LambdaException { } private short getScaledInt16Value(Command command) throws LambdaException { - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(CELSIUS); if (c != null) { return (short) (c.doubleValue() * 10); From 44729816528f460bee12c5eba42174970c69ccee Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:43:08 +0100 Subject: [PATCH 10/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../modbus/lambda/internal/handler/HeatingCircuitHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java index a51c0a0796152..06fef6f703051 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -347,7 +347,7 @@ private void startUp() { return; } - HeatingCircuitConfiguration myconfig = HeatingCircuitHandler.this.config; + HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config); baseadress = 5000 + 100 * myconfig.getSubindex(); reg50baseadress = baseadress + 50; From 01baf5abdab25d93a457bf137db6ebfb1ac9a7d0 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:43:35 +0100 Subject: [PATCH 11/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 5f1cfcf37db37..e0fcad2a0ff0c 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -201,7 +201,7 @@ protected void writeInt16(int address, short shortValue) { */ private short getScaledInt16Value(Command command) throws LambdaException { - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(CELSIUS); if (c != null) { return (short) (c.doubleValue() * 10); From 280d21946abc300cf39a9b820ae8fa4c16b2a0d8 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:44:02 +0100 Subject: [PATCH 12/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index e0fcad2a0ff0c..3d6ee4e96f46a 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -239,7 +239,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } else { try { - if (GROUP_HEAT_PUMP.equals(channelUID.getGroupId())) { switch (channelUID.getIdWithoutGroup()) { From 1a0510666681277b22328382ce29a861b5347bf9 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:44:16 +0100 Subject: [PATCH 13/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 3d6ee4e96f46a..b10d3d8709e20 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -240,7 +240,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { } else { try { if (GROUP_HEAT_PUMP.equals(channelUID.getGroupId())) { - switch (channelUID.getIdWithoutGroup()) { case CHANNEL_HEAT_PUMP_REQUEST_T_FLOW: From 3ea7fafc045a564a75e53cb4de9c8816c1b21c84 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:44:32 +0100 Subject: [PATCH 14/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index b10d3d8709e20..721db277fef19 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -241,7 +241,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { try { if (GROUP_HEAT_PUMP.equals(channelUID.getGroupId())) { switch (channelUID.getIdWithoutGroup()) { - case CHANNEL_HEAT_PUMP_REQUEST_T_FLOW: writeInt16(baseadress + 16, getScaledInt16Value(command)); break; From 7cd4162eabc9da110ddda18e2e37ec0b9397e4a9 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:44:51 +0100 Subject: [PATCH 15/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 721db277fef19..12a26fcc8dc1f 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -350,7 +350,6 @@ public void dispose() { * Unregister the poll tasks and release the endpoint reference */ private void tearDown() { - AbstractBasePoller poller = heatpumpPoller; if (poller != null) { poller.unregisterPollTask(); From f8579852bb9bb85e1828c893433f6d203a3c3fee Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:46:04 +0100 Subject: [PATCH 16/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 12a26fcc8dc1f..d4229a9a0cfb1 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -427,7 +427,6 @@ protected State getEnergyQuantity(int high, int low) { */ protected void handlePolledHeatpumpData(ModbusRegisterArray registers) { - HeatpumpBlock block = heatpumpBlockParser.parse(registers); // Heatpump group From 53c82bfa1afc770b15db18870466fce4346160c8 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:25:26 +0100 Subject: [PATCH 17/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index d4229a9a0cfb1..48b9626d1fead 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -319,7 +319,7 @@ private void startUp() { return; } - HeatpumpConfiguration myconfig = HeatpumpHandler.this.config; + HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config); baseadress = 1000 + 100 * myconfig.getSubindex(); From 82cb14e242af7380bcc195e9787df2a5d2fb0581 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:26:00 +0100 Subject: [PATCH 18/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/SolarHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index 3fc6224b968ba..14bb5ca71972c 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -165,7 +165,7 @@ protected void writeInt16(int address, short shortValue) { } private short getScaledInt16Value(Command command) throws LambdaException { - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(CELSIUS); if (c != null) { return (short) (c.doubleValue() * 10); From cfc50dcbe098580603ea0dfefb72bbd68d0df664 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:26:30 +0100 Subject: [PATCH 19/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/SolarHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index 14bb5ca71972c..3adf537a80e29 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -268,7 +268,7 @@ private void startUp() { return; } - SolarConfiguration myconfig = SolarHandler.this.config; + SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config); // Base address for solar is 4000 as mentioned in README.md baseadress = 4000 + 100 * myconfig.getSubindex(); From 99ff23423ebbadbc7f4d7f473d3897c84d9f2dab Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:27:07 +0100 Subject: [PATCH 20/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../modbus/lambda/internal/parser/BoilerReg50BlockParser.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java index 439eedd2a3d80..f96f338e1aee8 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java @@ -27,8 +27,6 @@ */ @NonNullByDefault public class BoilerReg50BlockParser extends AbstractBaseParser { - private final Logger logger = LoggerFactory.getLogger(BoilerReg50BlockParser.class); - public BoilerReg50Block parse(ModbusRegisterArray raw) { BoilerReg50Block block = new BoilerReg50Block(); block.boilerMaximumBoilerTemperature = extractInt16(raw, 0, (short) 0); From 98143315a6926a48ec35a93aadbde21453d048d1 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:27:42 +0100 Subject: [PATCH 21/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../binding/modbus/lambda/internal/handler/HeatpumpHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 48b9626d1fead..8a12e05a1a938 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -80,7 +80,6 @@ public synchronized void unregisterPollTask() { * 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 = HeatpumpHandler.this.config; From 83d1fef7bf9f88b7f9057b1d939322a8492b21e8 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:28:29 +0100 Subject: [PATCH 22/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java Co-authored-by: lsiepel Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../internal/handler/HeatpumpHandler.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 8a12e05a1a938..10bb6e1df73fe 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -177,27 +177,6 @@ protected void writeInt16(int address, short shortValue) { }); } - /** - * @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) { From d8e2392f3fc12b21e04549c599c72f1b78976293 Mon Sep 17 00:00:00 2001 From: Christian Koch <78686276+chilobo@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:01:13 +0100 Subject: [PATCH 23/24] Updated and signed off version of Lambda Heat Pump Signed-off-by: Christian Koch <78686276+chilobo@users.noreply.github.com> --- .../modbus/lambda/internal/handler/BoilerHandler.java | 8 +++++--- .../modbus/lambda/internal/handler/BufferHandler.java | 8 +++++--- .../modbus/lambda/internal/handler/GeneralHandler.java | 6 ++++-- .../lambda/internal/handler/HeatingCircuitHandler.java | 6 ++++-- .../modbus/lambda/internal/handler/HeatpumpHandler.java | 7 ++++--- .../modbus/lambda/internal/handler/SolarHandler.java | 6 ++++-- .../lambda/internal/parser/BoilerReg50BlockParser.java | 2 -- 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java index c3f9a10e5d833..c4edbae9b6ce5 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BoilerHandler.java @@ -16,6 +16,8 @@ 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; @@ -87,7 +89,7 @@ public synchronized void registerPollTask(int address, int length, ModbusReadFun logger.debug("Setting up regular polling Address: {}", address); ModbusCommunicationInterface mycomms = BoilerHandler.this.comms; - BoilerConfiguration myconfig = BoilerHandler.this.config; + BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("Boiler: registerPollTask called without proper configuration"); } @@ -164,7 +166,7 @@ public BoilerHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - BoilerConfiguration myconfig = BoilerHandler.this.config; + BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config); ModbusCommunicationInterface mycomms = BoilerHandler.this.comms; if (myconfig == null || mycomms == null) { @@ -301,7 +303,7 @@ private void startUp() { return; } - BoilerConfiguration myconfig = BoilerHandler.this.config; + BoilerConfiguration myconfig = Objects.requireNonNull(BoilerHandler.this.config); baseadress = 2000 + 100 * myconfig.getSubindex(); reg50baseadress = baseadress + 50; diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java index 0cbe540b7fdaf..e4b727bd2ad32 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/BufferHandler.java @@ -16,6 +16,8 @@ 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; @@ -83,7 +85,7 @@ public synchronized void unregisterPollTask() { */ public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) { ModbusCommunicationInterface mycomms = BufferHandler.this.comms; - BufferConfiguration myconfig = BufferHandler.this.config; + BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("registerPollTask called without proper configuration"); } @@ -158,7 +160,7 @@ public BufferHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - BufferConfiguration myconfig = BufferHandler.this.config; + BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config); ModbusCommunicationInterface mycomms = BufferHandler.this.comms; if (myconfig == null || mycomms == null) { @@ -339,7 +341,7 @@ private void startUp() { return; } - BufferConfiguration myconfig = BufferHandler.this.config; + BufferConfiguration myconfig = Objects.requireNonNull(BufferHandler.this.config); baseadress = 3000 + 100 * myconfig.getSubindex(); reg50baseadress = baseadress + 50; diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java index ae415e04f3013..0fb66df1e96c6 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/GeneralHandler.java @@ -16,6 +16,8 @@ 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; @@ -83,7 +85,7 @@ public synchronized void unregisterPollTask() { */ public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) { ModbusCommunicationInterface mycomms = GeneralHandler.this.comms; - GeneralConfiguration myconfig = GeneralHandler.this.config; + GeneralConfiguration myconfig = Objects.requireNonNull(GeneralHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("registerPollTask called without proper configuration"); } @@ -157,7 +159,7 @@ public GeneralHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - GeneralConfiguration myconfig = GeneralHandler.this.config; + GeneralConfiguration myconfig = Objects.requireNonNull(GeneralHandler.this.config); ModbusCommunicationInterface mycomms = GeneralHandler.this.comms; if (myconfig == null || mycomms == null) { diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java index 06fef6f703051..33891eec9e690 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatingCircuitHandler.java @@ -16,6 +16,8 @@ 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; @@ -87,7 +89,7 @@ public synchronized void registerPollTask(int address, int length, ModbusReadFun logger.debug("Setting up regular polling Address: {}", address); ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms; - HeatingCircuitConfiguration myconfig = HeatingCircuitHandler.this.config; + HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("HeatingCircuit: registerPollTask called without proper configuration"); } @@ -163,7 +165,7 @@ public HeatingCircuitHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - HeatingCircuitConfiguration myconfig = HeatingCircuitHandler.this.config; + HeatingCircuitConfiguration myconfig = Objects.requireNonNull(HeatingCircuitHandler.this.config); ModbusCommunicationInterface mycomms = HeatingCircuitHandler.this.comms; if (myconfig == null || mycomms == null) { diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java index 10bb6e1df73fe..2ed4b8ace72ec 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/HeatpumpHandler.java @@ -16,6 +16,8 @@ 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; @@ -81,7 +83,7 @@ public synchronized void unregisterPollTask() { */ public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) { ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms; - HeatpumpConfiguration myconfig = HeatpumpHandler.this.config; + HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("Heatpump: registerPollTask called without proper configuration"); @@ -153,7 +155,7 @@ public HeatpumpHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - HeatpumpConfiguration myconfig = HeatpumpHandler.this.config; + HeatpumpConfiguration myconfig = Objects.requireNonNull(HeatpumpHandler.this.config); ModbusCommunicationInterface mycomms = HeatpumpHandler.this.comms; if (myconfig == null || mycomms == null) { @@ -177,7 +179,6 @@ protected void writeInt16(int address, short shortValue) { }); } - private short getScaledInt16Value(Command command) throws LambdaException { if (command instanceof QuantityType quantityCommand) { QuantityType c = quantityCommand.toUnit(CELSIUS); diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java index 3adf537a80e29..e016b9a791860 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/handler/SolarHandler.java @@ -15,6 +15,8 @@ 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; @@ -80,7 +82,7 @@ public synchronized void unregisterPollTask() { public synchronized void registerPollTask(int address, int length, ModbusReadFunctionCode readFunctionCode) { ModbusCommunicationInterface mycomms = SolarHandler.this.comms; - SolarConfiguration myconfig = SolarHandler.this.config; + SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config); if (myconfig == null || mycomms == null) { throw new IllegalStateException("Solar: registerPollTask called without proper configuration"); @@ -140,7 +142,7 @@ public SolarHandler(Thing thing) { * @param shortValue value to be written on the modbus */ protected void writeInt16(int address, short shortValue) { - SolarConfiguration myconfig = SolarHandler.this.config; + SolarConfiguration myconfig = Objects.requireNonNull(SolarHandler.this.config); ModbusCommunicationInterface mycomms = SolarHandler.this.comms; if (myconfig == null || mycomms == null) { diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java index f96f338e1aee8..80fa07289c9eb 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/parser/BoilerReg50BlockParser.java @@ -15,8 +15,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.modbus.lambda.internal.dto.BoilerReg50Block; import org.openhab.core.io.transport.modbus.ModbusRegisterArray; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Parses modbus data into an BoilerReg50 Block From 565e9c01de0a78084854db9318b573b290c5bb5d Mon Sep 17 00:00:00 2001 From: Johannes Koch Date: Mon, 10 Nov 2025 10:25:09 +0100 Subject: [PATCH 24/24] Update bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java Signed-off-by: Johannes Koch --- .../openhab/binding/modbus/lambda/internal/dto/SolarBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java index bdb357e9be6cf..e5d2dfbddc518 100644 --- a/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java +++ b/bundles/org.openhab.binding.modbus.lambda/src/main/java/org/openhab/binding/modbus/lambda/internal/dto/SolarBlock.java @@ -16,7 +16,7 @@ * 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 + * @author Christian Koch - modified for lambda heat pump based on stiebeleltron binding for modbus to add handler */ public class SolarBlock { public int solarErrorNumber;