]> git.basschouten.com Git - openhab-addons.git/commitdiff
[openthermgateway] Add support for Ventilation/Heat Recovery units (#12367)
authorAndrew Fiddian-Green <software@whitebear.ch>
Sun, 6 Mar 2022 20:18:02 +0000 (20:18 +0000)
committerGitHub <noreply@github.com>
Sun, 6 Mar 2022 20:18:02 +0000 (21:18 +0100)
* [openthermgateway] clean version of prior PR
* [openthermgateway] fix NoSuchMethod exception
* [openthermgateway] create thread according to OH guidelines
* [openthermgateway] fix annotation warning
* [openthermgateway] framework handles bridge status changes
* [openthermgateway] implement learnings from PR #12356
* [openthermgateway] add ReadMe chapter on migration v3.2 .. v3.3
* [openthermgateway] delete duplicate OpenThermGatewayBindingConstants class
* [openthermgateway] remove redundant else clause, remove npe warning
* [openthermgateway] log to debug rather than info; eliminate static import
* [openthermgateway] eliminate static import
* [openthermgateway] suppress unused argument warning
* [openthermgateway] delete xml definitions for no longer used 'otgw' Thing type

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
37 files changed:
bundles/org.openhab.binding.openthermgateway/README.md
bundles/org.openhab.binding.openthermgateway/pom.xml
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/OpenThermGatewayBindingConstants.java [deleted file]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BaseDeviceHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BoilerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/OpenThermGatewayHandler.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/VentilationHeatRecoveryHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/ByteType.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/CodeType.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/ConnectionState.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/DataItem.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/DataItemGroup.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/DataType.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FlagDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FloatDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/GatewayCommand.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/GatewayCommandCode.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/IntDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/Message.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/MessageType.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/Msg.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayCallback.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayConfiguration.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayConnector.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayHandlerFactory.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewaySocketConnector.java
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbSizeDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbValueDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/UIntDataItem.java [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/config.xml [deleted file]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/openthermgateway.xml [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/boiler.xml [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/openthermgateway.xml [new file with mode: 0644]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/otgw.xml [deleted file]
bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/ventilationheatrecovery.xml [new file with mode: 0644]

index 1d88b66f8f30f36c3edf0fd5f5ba6ce71ada978e..3b736c8e23959b85ffd55d4c411ef0b1a9f2af38 100644 (file)
 # OpenTherm Gateway Binding
 
 This binding is used to integrate the OpenTherm Gateway into openHAB.
-The OpenTherm Gateway is a module designed by Schelte Bron that is connected in between a boiler and a thermostat and communicates using the OpenTherm protocol.
+The OpenTherm Gateway is a module designed by Schelte Bron that can be connected to units that support communication using the OpenTherm protocol, such as boiler or ventilation / heat recovery unit.
 
 More information on the OpenTherm Gateway device can be found at https://otgw.tclcode.com/
 
-## Supported Things
-
-The OpenTherm Gateway binding currently only supports one thing, and that's the gateway itself.
-
 ## Discovery
 
 The binding does not support auto discovery.
 
 ## Binding Configuration
 
-The binding itself does not require any configuration.
+The binding does not require any configuration.
+
+## Supported Things
+
+The OpenTherm Gateway binding supports three Things:
+
+- `openthermgateway` which is the bridge that handles communication with the OpenTherm Gateway device.
+- `boiler` which represents a central heating boiler unit.
+- `ventilationheatrecovery` which represents a ventilation / heat recovery unit.
 
 ## Thing Configuration
 
-The binding is designed to support various ways of connecting to the OpenTherm Gateway device, but currently only supports a TCP socket connection.
-The configuration settings for the thing are Hostname/IP address and Port, which are used to connect to the gateway, and an automatic connection retry interval in case the connection to the OpenTherm Gateway device is lost.
+### Thing Configuration for `openthermgateway`
+
+The `openthermgateway` bridge is designed to support various ways of connecting to the OpenTherm Gateway device, but currently only supports a TCP socket connection.
+The configuration settings for the bridge are Hostname/IP address and Port, which are used to connect to the gateway, and an automatic connection retry interval in case the connection to the OpenTherm Gateway device is lost.
 
-| Parameter                 | Name                      | Description                                                     | Required | Default |
-|---------------------------|---------------------------|-----------------------------------------------------------------|----------|---------|
-| `ipaddress`               | Hostname or IP address    | The hostname or IP address to connect to the OpenTherm Gateway. | yes      |         |
-| `port`                    | Port                      | The port used to connect to the OpenTherm Gateway.              | yes      |         |
-| `connectionRetryInterval` | Connection Retry Interval | The interval in seconds to retry connecting (0 = disabled).     | yes      | 60      |
+| Parameter                 | Name                      | Description                                                                      | Required | Default |
+|---------------------------|---------------------------|----------------------------------------------------------------------------------|----------|---------|
+| `ipaddress`               | Hostname or IP address    | The hostname or IP address to connect to the OpenTherm Gateway.                  | yes      |         |
+| `port`                    | Port                      | The port used to connect to the OpenTherm Gateway.                               | yes      |         |
+| `connectionRetryInterval` | Connection Retry Interval | The interval in seconds to retry connecting (0 = disabled).                      | yes      | 60      |
+| `connectTimeoutSeconds`   | Connect Timeout           | The maximum time (seconds) to wait for establishing a connection to the gateway. | yes      | 5       |
+| `readTimeoutSeconds`      | Read Timeout              | The maximum time (seconds) to wait for reading responses from the gateway.       | yes      | 20      |
+
+### Thing Configuration for `boiler` and `ventilationheatrecovery`
+
+The `boiler` and `ventilationheatrecovery` things do not require any configuration settings.
 
 ## Channels
 
-The OpenTherm Gateway binding supports the following channels:
+### Channels for `openthermgateway`
+
+The `openthermgateway` bridge supports the following channels:
 
 | Channel Type ID           | Item Type            | Description                                              | Access |
 |---------------------------|----------------------|----------------------------------------------------------|--------|
-| roomtemp                  | Number:Temperature   | Current sensed room temperature                          | R      |
-| roomsetpoint              | Number:Temperature   | Current room temperature setpoint                        | R      |
-| temperaturetemporary      | Number:Temperature   | Temporary override room temperature setpoint             | R/W    |
-| temperatureconstant       | Number:Temperature   | Constant override room temperature setpoint              | R/W    |
-| controlsetpoint           | Number:Temperature   | Central heating water setpoint set at boiler             | R      |
-| controlsetpointrequested  | Number:Temperature   | Central heating water setpoint requested by thermostat   | R      |
-| controlsetpointoverride   | Number:Temperature   | Central heating water setpoint configured at gateway     | R/W    |
-| controlsetpoint2          | Number:Temperature   | Central heating 2 water setpoint set at boiler           | R      |
-| controlsetpoint2requested | Number:Temperature   | Central heating 2 water setpoint requested by thermostat | R      |
-| controlsetpoint2override  | Number:Temperature   | Central heating 2 water setpoint configured at gateway   | R/W    |
-| dhwtemp                   | Number:Temperature   | Domestic hot water temperature                           | R      |
-| tdhwset                   | Number:Temperature   | Domestic hot water temperature setpoint                  | R      |
-| overridedhwsetpoint       | Number:Temperature   | Domestic hot water temperature setpoint override         | R/W    |
-| flowtemp                  | Number:Temperature   | Boiler water temperature                                 | R      |
-| returntemp                | Number:Temperature   | Return water temperature                                 | R      |
-| outsidetemp               | Number:Temperature   | Outside temperature                                      | R/W    |
-| waterpressure             | Number:Pressure      | Central heating water pressure                           | R      |
-| ch_enable                 | Switch               | Central heating enabled set at boiler                    | R      |
-| ch_enablerequested        | Switch               | Central heating enabled requested by thermostat          | R      |
-| ch_enableoverride         | Switch               | Central heating enabled overridden at gateway            | R      |
-| ch2_enable                | Switch               | Central heating 2 enabled set at boiler                  | R      |
-| ch2_enablerequested       | Switch               | Central heating 2 enabled requested by thermostat        | R      |
-| ch2_enableoverride        | Switch               | Central heating 2 enabled overridden at gateway          | R      |
-| ch_mode                   | Switch               | Central heating active                                   | R      |
-| dhw_enable                | Switch               | Domestic hot water enabled                               | R      |
-| dhw_mode                  | Switch               | Domestic hot water active                                | R      |
-| flame                     | Switch               | Burner active                                            | R      |
-| modulevel                 | Number:Dimensionless | Relative modulation level                                | R      |
-| maxrelmdulevel            | Number:Dimensionless | Maximum relative modulation level                        | R      |
-| fault                     | Switch               | Fault indication                                         | R      |
-| servicerequest            | Switch               | Service required                                         | R      |
-| lockout-reset             | Switch               | Lockout-reset enabled                                    | R      |
-| lowwaterpress             | Switch               | Low water pressure fault                                 | R      |
-| gasflamefault             | Switch               | Gas or flame fault                                       | R      |
-| airpressfault             | Switch               | Air pressure fault                                       | R      |
-| waterovtemp               | Switch               | Water over-temperature fault                             | R      |
-| oemfaultcode              | Switch               | OEM fault code                                           | R      |
-| diag                      | Switch               | Diagnostics indication                                   | R      |
-| unsuccessfulburnerstarts  | Number:Dimensionless | Unsuccessful burner starts                               | R      |
-| burnerstarts              | Number:Dimensionless | Burner starts                                            | R      |
-| chpumpstarts              | Number:Dimensionless | Central heating pump starts                              | R      |
-| dhwpvstarts               | Number:Dimensionless | Domestic hot water pump/valve starts                     | R      |
-| dhwburnerstarts           | Number:Dimensionless | Domestic hot water burner starts                         | R      |
-| burnerhours               | Number:Dimensionless | Burner hours                                             | R      |
-| chpumphours               | Number:Dimensionless | Central heating pump hours                               | R      |
-| dhwpvhours                | Number:Dimensionless | Domestic hot water pump/valve hours                      | R      |
-| dhwburnerhours            | Number:Dimensionless | Domestic hot water burner hours                          | R      |
 | sendcommand               | Text                 | Channel to send commands to the OpenTherm Gateway device | W      |
 
+### Channels for `boiler`
+
+The `boiler` thing supports the following channels:
+
+| Channel ID | Item Type | Description | Access |
+|------------|-----------|-------------|--------|
+| roomtemp | Number:Temperature | Current sensed room temperature | R |
+| roomsetpoint | Number:Temperature | Current room temperature setpoint | R |
+| temperaturetemporary | Number:Temperature | Temporary override room temperature setpoint | R/W |
+| temperatureconstant | Number:Temperature | Constant override room temperature setpoint | R/W |
+| controlsetpoint | Number:Temperature | Central heating water setpoint set at boiler | R |
+| controlsetpointrequested | Number:Temperature | Central heating water setpoint requested by Thermostat | R |
+| controlsetpointoverride | Number:Temperature | Central heating water setpoint configured on OTGW | R/W |
+| controlsetpoint2 | Number:Temperature | Central heating 2 water setpoint set at boiler | R |
+| controlsetpoint2requested | Number:Temperature | Central heating 2 water setpoint requested by Thermostat | R |
+| controlsetpoint2override | Number:Temperature | Central heating 2 water setpoint configured on OTGW | R/W |
+| dhwtemp | Number:Temperature | Domestic hot water temperature | R |
+| tdhwset | Number:Temperature | Domestic hot water temperature setpoint | R |
+| overridedhwsetpoint | Number:Temperature | Domestic hot water temperature setpoint override | R/W |
+| flowtemp | Number:Temperature | Boiler water temperature | R |
+| returntemp | Number:Temperature | Return water temperature | R |
+| outsidetemp | Number:Temperature | Outside temperature | R/W |
+| waterpressure | Number:Dimensionless | Central heating water pressure | R |
+| ch_enable | Switch | Central heating enabled set at boiler | R |
+| ch_enablerequested | Switch | Central heating enabled requested by thermostat | R |
+| ch_enableoverride | Switch | Central heating enabled overridden at OTGW | R/W |
+| ch2_enable | Switch | Central heating 2 enabled set at boiler | R |
+| ch2_enablerequested | Switch | Central heating 2 enabled requested by thermostat | R |
+| ch2_enableoverride | Switch | Central heating 2 enabled overridden at OTGW | R/W |
+| ch_mode | Switch | Central heating active | R |
+| dhw_enable | Switch | Domestic hot water enabled | R |
+| dhw_mode | Switch | Domestic hot water active | R |
+| flame | Switch | Burner active | R |
+| modulevel | Number:Dimensionless | Relative modulation level | R |
+| maxrelmdulevel | Number:Dimensionless | Maximum relative modulation level | R |
+| fault | Switch | Fault indication | R |
+| servicerequest | Switch | Service required | R |
+| lockout-reset | Switch | Lockout-reset enabled | R |
+| lowwaterpress | Switch | Low water pressure fault | R |
+| gasflamefault | Switch | Gas or flame fault | R |
+| airpressfault | Switch | Air pressure fault | R |
+| waterovtemp | Switch | Water over-temperature fault | R |
+| oemfaultcode | Number:Dimensionless | OEM fault code | R |
+| diag | Switch | Diagnostics indication | R |
+| unsuccessfulburnerstarts | Number:Dimensionless | Unsuccessful burner starts | R |
+| burnerstarts | Number:Dimensionless | Burner starts | R |
+| chpumpstarts | Number:Dimensionless | Central heating pump starts | R |
+| dhwpvstarts | Number:Dimensionless | Domestic hot water pump/valve starts | R |
+| dhwburnerstarts | Number:Dimensionless | Domestic hot water burner starts | R |
+| burnerhours | Number:Time | Burner hours | R |
+| chpumphours | Number:Time | Central heating pump hours | R |
+| dhwpvhours | Number:Time | Domestic hot water pump/valve hours | R |
+| dhwburnerhours | Number:Time | Domestic hot water burner hours | R |
+| tspnumber | Number:Dimensionless | Number of transparant slave parameter entries | R |
+| tspentry | Number:Dimensionless | Transparent slave parameter entry | R |
+| fhbnumber | Number:Dimensionless | Number of fault history buffer entries | R |
+| fhbentry | Number:Dimensionless | Fault history buffer entry | R |
+
+### Channels for `ventilationheatrecovery`
+
+The `ventilationheatrecovery` thing supports the following channels:
+
+| Channel ID | Item Type | Description | Access |
+|------------|-----------|-------------|--------|
+| vh_ventilationenable | Switch | Ventilation enabled | R |
+| vh_bypassposition | Number:Dimensionless | Bypass position | R |
+| vh_bypassmode | Number:Dimensionless | Bypass mode | R |
+| vh_freeventilationmode | Switch | Free ventilation mode | R |
+| vh_faultindication | Switch | Fault indication | R |
+| vh_ventilationmode | Switch | Ventilation mode | R |
+| vh_bypassstatus | Switch | Bypass status | R |
+| vh_bypassautomaticstatus | Number:Dimensionless | Bypass automatic status | R |
+| vh_freeventilationstatus | Switch | Free ventilation status | R |
+| vh_filtercheck | Switch | Filter Check enabled | R |
+| vh_diagnosticindication | Switch | Diagnostic indication | R |
+| vh_controlsetpoint | Number:Dimensionless | Control setpoint | R |
+| vh_servicerequest | Switch | Service request | R |
+| vh_exhaustfanfault | Switch | Exhaust fan fault | R |
+| vh_inletfanfault | Switch | Inlet fan fault | R |
+| vh_frostprotection | Switch | Frost protection | R |
+| vh_faultcode | Number:Dimensionless | Fault code | R |
+| vh_diagnosticcode | Number:Dimensionless | Diagnostic code | R |
+| vh_systemtype | Number:Dimensionless | System type | R |
+| vh_bypass | Switch | Bypass | R |
+| vh_speedcontrol | Number:Dimensionless | Speed control | R |
+| vh_memberid | Number:Dimensionless | Member ID | R |
+| vh_openthermversion | Number:Dimensionless | OpenTherm version | R |
+| vh_versiontype | Number:Dimensionless | Version type | R |
+| vh_relativeventilation | Number:Dimensionless | Relative ventilation position | R |
+| vh_relativehumidity | Number:Dimensionless | Relative humidity exhaust air | R |
+| vh_co2level | Number:Dimensionless | CO2 level exhaust air | R |
+| vh_supplyinlettemp | Number:Temperature | Supply inlet temperature | R |
+| vh_supplyoutlettemp | Number:Temperature | Supply outlet temperature | R |
+| vh_exhaustinlettemp | Number:Temperature | Exhaust inlet temperature | R |
+| vh_exhaustoutlettemp | Number:Temperature | Exhaust outlet temperature | R |
+| vh_actualexhaustfanspeed | Number:Dimensionless | Actual exhaust fan speed | R |
+| vh_actualinletfanspeed | Number:Dimensionless | Actual inlet fan speed | R |
+| vh_nominalventenable | Switch | Nominal ventilation value transfer enabled | R |
+| vh_nominalventrw | Number:Dimensionless | Nominal ventilation value | R |
+| vh_nominalventilationvalue | Number:Dimensionless | Nominal ventilation value | R |
+| vh_ventilationsetpoint | Number:Dimensionless | Ventilation setpoint override | R/W |
+| vh_tspnumber | Number:Dimensionless | Number of transparent slave parameter entries | R |
+| vh_tspentry | Number:Dimensionless | Transparent slave parameter entry | R |
+| vh_fhbnumber | Number:Dimensionless | Number of fault history buffer entries | R |
+| vh_fhbentry | Number:Dimensionless | Fault history buffer entry | R |
+
+## Transparent Slave Parameters and Fault History Buffer channels
+
+The transparent slave parameters (TSP) and fault history buffer (FHB) use a variable number of entries.
+The number of entries is determined by a TSP or FHB size message.
+Channels for TSP and FHB entries are automatically created when a TSP or FHB size message is received.
+An index number is added to the base channel name to create a unique channel name for each entry.
+
+For example, if a TSP size message is received for a boiler unit (OpenTherm DATA-ID 10) with value 60, then channels `tspentry_0` through `tspentry_59` will be automatically created and linked to the corresponding TSP entry (OpenTherm DATA-ID 11).
+
+## Using OpenTherm Gateway as a master device
+
+When using OpenTherm with a boiler and a thermostat, the thermostat (master) periodically sends messages to the boiler to request data.
+The boiler (slave) then sends a response message with the requested data which is then used by the OpenTherm Gateway binding to update the channel values in openHAB.
+
+If you have a setup without a master device requesting data, then the slave device may send fewer or even no OpenTherm mesages at all.
+
+In this case, you can make the OpenTherm Gateway act as a master device by sending Priority Message (PM) commands.
+With openHAB rules, you can use the `sendcommand` channel of the `openthermgateway` bridge to periodically send PM commands to the OpenTherm Gateway.
+
+Example:
+
+```
+SendCommand.sendCommand("PM=10")
+```
+
+This will cause the OpenTherm Gateway to send a READ-DATA message to the slave device with DATA-ID 10. If supported, the slave device will respond with a READ-ACK message and the current value.
+
 ## Full Example
 
-### demo.things
-
-```
-Thing openthermgateway:otgw:1 [ ipaddress="192.168.1.100", port=8000, connectionRetryInterval=60 ]
-```
-
-### demo.items
-
-```
-Number:Temperature RoomTemperature "Room temperature [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:roomtemp" }
-Number:Temperature RoomSetpoint "Room setpoint [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:roomsetpoint" }
-Number:Temperature TemporaryRoomSetpointOverride "Temporary room setpoint override [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:temperaturetemporary" }
-Number:Temperature ConstantRoomSetpointOverride "Constant room setpoint override [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:temperatureconstant" }
-Number:Temperature ControlSetpoint "Control setpoint [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint" }
-Number:Temperature ControlSetpointRequested "Control setpoint requested [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpointrequested" }
-Number:Temperature ControlSetpointOverride "Control setpoint override [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpointoverride" }
-Number:Temperature ControlSetpoint2 "Control setpoint 2 [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2" }
-Number:Temperature ControlSetpoint2Requested "Control setpoint 2 requested [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2requested" }
-Number:Temperature ControlSetpoint2Override "Control setpoint 2 override [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2override" }
-Number:Temperature DomesticHotWaterTemperature "Domestic hot water temperature [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:dhwtemp" }
-Number:Temperature DomesticHotWaterSetpoint "Domestic hot water setpoint [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:tdhwset" }
-Number:Temperature DomesticHotWaterSetpointOverride "Domestic hot water setpoint override [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:overridedhwsetpoint" }
-Number:Temperature BoilerWaterTemperature "Boiler water temperature [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:flowtemp" }
-Number:Temperature ReturnWaterTemperature "Return water temperature [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:returntemp" }
-Number:Temperature OutsideTemperature "Outside temperature [%.1f Â°C]" <temperature> { channel="openthermgateway:otgw:1:outsidetemp" }
-Number:Pressure CentralHeatingWaterPressure "Central heating water pressure [%.1f bar]" { channel="openthermgateway:otgw:1:waterpressure" }
-Switch CentralHeatingEnabled "Central heating enabled" <switch> { channel="openthermgateway:otgw:1:ch_enable" }
-Switch CentralHeatingEnabledRequested "Central heating enabled requested" <switch> { channel="openthermgateway:otgw:1:ch_enablerequested" }
-Switch CentralHeatingEnabledOverride "Central heating enabled override" <switch> { channel="openthermgateway:otgw:1:ch_enableoverride" }
-Switch CentralHeating2Enabled "Central heating 2 enabled" <switch> { channel="openthermgateway:otgw:1:ch2_enable" }
-Switch CentralHeating2EnabledRequested "Central 2 heating enabled requested" <switch> { channel="openthermgateway:otgw:1:ch2_enablerequested" }
-Switch CentralHeating2EnabledOverride "Central 2 heating enabled override" <switch> { channel="openthermgateway:otgw:1:ch2_enableoverride" }
-Switch CentralHeatingActive "Central heating active" <switch> { channel="openthermgateway:otgw:1:ch_mode" }
-Switch DomesticHotWaterEnabled "Domestic hot water enabled" <switch> { channel="openthermgateway:otgw:1:dhw_enable" }
-Switch DomesticHotWaterActive "Domestic hot water active" <switch> { channel="openthermgateway:otgw:1:dhw_mode" }
-Switch BurnerActive "Burner active" <switch> { channel="openthermgateway:otgw:1:flame" }
-Number:Dimensionless RelativeModulationLevel "Relative modulation level [%.1f %%]" { channel="openthermgateway:otgw:1:modulevel" }
-Number:Dimensionless MaximumRelativeModulationLevel "Maximum relative modulation level [%.1f %%]" { channel="openthermgateway:otgw:1:maxrelmdulevel" }
-Switch Fault "Fault indication" <switch> { channel="openthermgateway:otgw:1:fault" }
-Switch ServiceRequest "Service required" <switch> { channel="openthermgateway:otgw:1:servicerequest" }
-Switch LockoutReset "Lockout-reset" <switch> { channel="openthermgateway:otgw:1:lockout-reset" }
-Switch LowWaterPress "Low water pressure fault" <switch> { channel="openthermgateway:otgw:1:lowwaterpress" }
-Switch GasFlameFault "Gas or flame fault" <switch> { channel="openthermgateway:otgw:1:gasflamefault" }
-Switch AirPressFault "Air pressure fault" <switch> { channel="openthermgateway:otgw:1:airpressfault" }
-Switch WaterOvTemp "Water over-temperature fault" <switch> { channel="openthermgateway:otgw:1:waterovtemp" }
-Number OemFaultCode "OEM fault code" { channel="openthermgateway:otgw:1:oemfaultcode" }
-Switch Diagnostics "Diagnostics indication" { channel="openthermgateway:otgw:1:diag" }
-Number:Dimensionless UnsuccessfulBurnerStarts "Unsuccessful burner starts" { channel="openthermgateway:otgw:1:unsuccessfulburnerstarts" }
-Number:Dimensionless BurnerStarts "Burner starts" { channel="openthermgateway:otgw:1:burnerstarts" }
-Number:Dimensionless CentralHeatingPumpStarts "Central heating pump starts" { channel="openthermgateway:otgw:1:chpumpstarts" }
-Number:Dimensionless DomesticHotWaterPumpValveStarts "Domestic hot water pump/valve starts" { channel="openthermgateway:otgw:1:dhwpvstarts" }
-Number:Dimensionless DomesticHotWaterBurnerStarts "Domestic hot water burner starts" { channel="openthermgateway:otgw:1:dhwburnerstarts" }
-Number:Time BurnerHours "Burner hours" { channel="openthermgateway:otgw:1:burnerhours" }
-Number:Time CentralHeatingPumpHours "Central heating pump hours" { channel="openthermgateway:otgw:1:chpumphours" }
-Number:Time DomesticHotWaterPumpValveHours "Domestic hot water pump/valve hours" { channel="openthermgateway:otgw:1:dhwpvhours" }
-Number:Time DomesticHotWaterBurnerHours "Domestic hot water burner hours" { channel="openthermgateway:otgw:1:dhwburnerhours" }
-Text SendCommand "Send command channel" { channel="openthermgateway:otgw:1:sendcommand" }
+### demo.things 
+
+```
+Bridge openthermgateway:openthermgateway:1 "OpenTherm Gateway" [ ipaddress="192.168.1.100", port="8000", connectionRetryInterval=60 ] {
+    Thing boiler remeha "Remeha Avanta 28c"
+    Thing ventilationheatrecovery brink "Brink Renovent Excellent 300"
+}
+```
+
+### demo.items for `openthermgateway`
+
+```
+Text SendCommand "Send command channel" { channel="openthermgateway:openthermgateway:1:sendcommand" }
+```
+
+### demo.items for `boiler`
+
+```
+Number:Temperature RoomTemperature "Room Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:roomtemp }
+Number:Temperature RoomSetpoint "Room Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:roomsetpoint }
+Number:Temperature TemporaryRoomSetpointOverride "Temporary Room Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:temperaturetemporary }
+Number:Temperature ConstantRoomSetpointOverride "Constant Room Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:temperatureconstant }
+Number:Temperature ControlSetpoint "Control Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint }
+Number:Temperature ControlSetpointRequested "Control Setpoint Requested [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpointrequested }
+Number:Temperature ControlSetpointOverride "Control Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpointoverride }
+Number:Temperature ControlSetpoint2 "Control Setpoint 2 [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2 }
+Number:Temperature ControlSetpoint2Requested "Control Setpoint 2 Requested [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2requested }
+Number:Temperature ControlSetpoint2Override "Control Setpoint 2 Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2override }
+Number:Temperature DomesticHotWaterTemperature "Domestic Hot Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:dhwtemp }
+Number:Temperature DomesticHotWaterSetpoint "Domestic Hot Water Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:tdhwset }
+Number:Temperature DomesticHotWaterSetpointOverride "Domestic Hot Water Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:overridedhwsetpoint }
+Number:Temperature BoilerWaterTemperature "Boiler Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:flowtemp }
+Number:Temperature ReturnWaterTemperature "Return Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:returntemp }
+Number:Temperature OutsideTemperature "Outside Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:outsidetemp }
+Number:Dimensionless CentralHeatingWaterPressure "Central Heating Water Pressure [%.1f bar]" { channel="openthermgateway:boiler:1:remeha:waterpressure }
+Switch CentralHeatingEnabled "Central Heating Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enable }
+Switch CentralHeatingEnabledThermostat "Central Heating Enabled Thermostat" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enablerequested }
+Switch CentralHeatingOverridden "Central Heating Overridden" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enableoverride }
+Switch CentralHeating2Enabled "Central Heating 2 Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enable }
+Switch CentralHeating2EnabledThermostat "Central Heating 2 Enabled Thermostat" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enablerequested }
+Switch CentralHeating2Overridden "Central Heating 2 Overridden" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enableoverride }
+Switch CentralHeatingActive "Central Heating Active" <switch> { channel="openthermgateway:boiler:1:remeha:ch_mode }
+Switch DomesticHotWaterEnabled "Domestic Hot Water Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:dhw_enable }
+Switch DomesticHotWaterActive "Domestic Hot Water Active" <switch> { channel="openthermgateway:boiler:1:remeha:dhw_mode }
+Switch BurnerActive "Burner Active" <switch> { channel="openthermgateway:boiler:1:remeha:flame }
+Number:Dimensionless RelativeModulationLevel "Relative Modulation Level [%.1f %%]" { channel="openthermgateway:boiler:1:remeha:modulevel }
+Number:Dimensionless MaximumRelativeModulationLevel "Maximum Relative Modulation Level [%.1f %%]" { channel="openthermgateway:boiler:1:remeha:maxrelmdulevel }
+Switch FaultIndication "Fault Indication" <switch> { channel="openthermgateway:boiler:1:remeha:fault }
+Switch ServiceRequired "Service Required" <switch> { channel="openthermgateway:boiler:1:remeha:servicerequest }
+Switch LockoutResetEnabled "Lockout-Reset Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:lockout-reset }
+Switch LowWaterPressureFault "Low Water Pressure Fault" <switch> { channel="openthermgateway:boiler:1:remeha:lowwaterpress }
+Switch GasOrFlameFault "Gas Or Flame Fault" <switch> { channel="openthermgateway:boiler:1:remeha:gasflamefault }
+Switch AirPressureFault "Air Pressure Fault" <switch> { channel="openthermgateway:boiler:1:remeha:airpressfault }
+Switch WaterOverTemperatureFault "Water Over-Temperature Fault" <switch> { channel="openthermgateway:boiler:1:remeha:waterovtemp }
+Number:Dimensionless OEMFaultCode "OEM Fault Code" { channel="openthermgateway:boiler:1:remeha:oemfaultcode }
+Number:Dimensionless UnsuccessfulBurnerStarts "Unsuccessful Burner Starts" { channel="openthermgateway:boiler:1:remeha:unsuccessfulburnerstarts }
+Number:Dimensionless BurnerStarts "Burner Starts" { channel="openthermgateway:boiler:1:remeha:burnerstarts }
+Number:Dimensionless CentralHeatingPumpStarts "Central Heating Pump Starts" { channel="openthermgateway:boiler:1:remeha:chpumpstarts }
+Number:Dimensionless DomesticHotWaterPump/ValveStarts "Domestic Hot Water Pump/Valve Starts" { channel="openthermgateway:boiler:1:remeha:dhwpvstarts }
+Number:Dimensionless DomesticHotWaterBurnerStarts "Domestic Hot Water Burner Starts" { channel="openthermgateway:boiler:1:remeha:dhwburnerstarts }
+Number:Time BurnerHours "Burner Hours" { channel="openthermgateway:boiler:1:remeha:burnerhours }
+Number:Time CentralHeatingPumpHours "Central Heating Pump Hours" { channel="openthermgateway:boiler:1:remeha:chpumphours }
+Number:Time DomesticHotWaterPumpValveHours "Domestic Hot Water Pump/Valve Hours" { channel="openthermgateway:boiler:1:remeha:dhwpvhours }
+Number:Time DomesticHotWaterBurnerHours "Domestic Hot Water Burner Hours" { channel="openthermgateway:boiler:1:remeha:dhwburnerhours }
+Number:Dimensionless TransparentSlaveParameterNumber "Transparent Slave Parameter Number" { channel="openthermgateway:boiler:1:remeha:tspnumber }
+Number:Dimensionless TransparentSlaveParameterEntry "Transparent Slave Parameter Entry" { channel="openthermgateway:boiler:1:remeha:tspentry }
+Number:Dimensionless FaultHistoryBufferNumber "Fault History Buffer Number" { channel="openthermgateway:boiler:1:remeha:fhbnumber }
+Number:Dimensionless FaultHistoryBufferEntry "Fault History Buffer Entry" { channel="openthermgateway:boiler:1:remeha:fhbentry }
+```
+
+### demo.items for `ventilationheatrecovery`
+
+```
+Switch VentilationEnabled "Ventilation Enabled" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_ventilationenable }
+Number:Dimensionless BypassPosition "Bypass Position" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_bypassposition }
+Number:Dimensionless BypassMode "Bypass Mode" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_bypassmode }
+Switch FreeVentilationMode "Free Ventilation Mode" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_freeventilationmode }
+Switch FaultIndication "Fault Indication" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_faultindication }
+Switch VentilationMode "Ventilation Mode" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_ventilationmode }
+Switch BypassStatus "Bypass Status" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_bypassstatus }
+Number:Dimensionless BypassAutomaticStatus "Bypass Automatic Status" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_bypassautomaticstatus }
+Switch FreeVentilationStatus "Free Ventilation Status" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_freeventilationstatus }
+Switch FilterCheck "Filter Check" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_filtercheck }
+Switch DiagnosticIndication "Diagnostic Indication" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_diagnosticindication }
+Number:Dimensionless ControlSetpoint "Control Setpoint" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_controlsetpoint }
+Switch ServiceRequest "Service Request" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_servicerequest }
+Switch ExhaustFanFault "Exhaust Fan Fault" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_exhaustfanfault }
+Switch InletFanFault "Inlet Fan Fault" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_inletfanfault }
+Switch FrostProtection "Frost Protection" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_frostprotection }
+Number:Dimensionless FaultCode "Fault Code" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_faultcode }
+Number:Dimensionless DiagnosticCode "Diagnostic Code" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_diagnosticcode }
+Number:Dimensionless SystemType "System Type" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_systemtype }
+Switch Bypass "Bypass" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_bypass }
+Number:Dimensionless SpeedControl "Speed Control" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_speedcontrol }
+Number:Dimensionless MemberID "Member ID" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_memberid }
+Number:Dimensionless OpenThermVersion "OpenTherm Version" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_openthermversion }
+Number:Dimensionless VersionType "Version Type" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_versiontype }
+Number:Dimensionless RelativeVentilation "Relative Ventilation [%d %%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_relativeventilation }
+Number:Dimensionless RelativeHumidity "Relative Humidity [%d %%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_relativehumidity }
+Number:Dimensionless CO2Level "CO2 Level [%d ppm]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_co2level }
+Number:Temperature SupplyInletTemperature "Supply Inlet Temperature [%.1f %unit%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_supplyinlettemp }
+Number:Temperature SupplyOutletTemperature "Supply Outlet Temperature [%.1f %unit%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_supplyoutlettemp }
+Number:Temperature ExhaustInletTemperature "Exhaust Inlet Temperature [%.1f %unit%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_exhaustinlettemp }
+Number:Temperature ExhaustOutletTemperature "Exhaust Outlet Temperature [%.1f %unit%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_exhaustoutlettemp }
+Number:Dimensionless ActualExhaustFanSpeed "Actual Exhaust Fan Speed [%d rpm]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_actualexhaustfanspeed }
+Number:Dimensionless ActualInletFanSpeed "Actual Inlet Fan Speed [%d rpm]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_actualinletfanspeed }
+Switch NominalVentilationValueTransfer "Nominal Ventilation Value Transfer" <switch> { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_nominalventenable }
+Number:Dimensionless NominalVentilationValue "Nominal Ventilation Value" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_nominalventrw }
+Number:Dimensionless NominalVentilationValue "Nominal Ventilation Value [%d %%]" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_nominalventilationvalue }
+Number:Dimensionless VentilationSetpoint "Ventilation Setpoint" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_ventilationsetpoint }
+Number:Dimensionless TransparentSlaveParameterNumber "Transparent Slave Parameter Number" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_tspnumber }
+Number:Dimensionless TransparentSlaveParameterEntry "Transparent Slave Parameter Entry" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_tspentry }
+Number:Dimensionless FaultHistoryBufferNumber "Fault History Buffer Number" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_fhbnumber }
+Number:Dimensionless FaultHistoryBufferEntry "Fault History Buffer Entry" { channel="openthermgateway:ventilationheatrecovery:1:brink:vh_fhbentry }
 ```
 
 ### demo.sitemap
 
 ```
 sitemap demo label="Main Menu" {
-    Frame label="OpenTherm Gateway" {
-        Text item="RoomTemperature" icon="temperature" label="Room temperature [%.1f Â°C]"
-        Text item="RoomSetpoint" icon="temperature" label="Room setpoint [%.1f Â°C]"
-        Setpoint item="TemporaryRoomSetpointOverride" icon="temperature" label="Temporary room setpoint override [%.1f Â°C]" minValue="0" maxValue="30" step="0.1"
-        Setpoint item="ConstantRoomSetpointOverride" icon="temperature" label="Constant room setpoint override [%.1f Â°C]" minValue="0" maxValue="30" step="0.1"
-        Text item="ControlSetpoint" icon="temperature" label="Control setpoint [%.1f Â°C]"
-        Text item="ControlSetpointRequested" icon="temperature" label="Control setpoint requested [%.1f Â°C]"
-        Setpoint item="ControlSetpointOverride" icon="temperature" label="Control setpoint override [%.1f Â°C]" minValue="0" maxValue="100" step="1"
-        Text item="DomesticHotWaterTemperature" icon="temperature" label="Domestic hot water temperature [%.1f Â°C]"
-        Text item="DomesticHotWaterSetpoint" icon="temperature" label="Domestic hot water setpoint [%.1f Â°C]"
-        Setpoint item="DomesticHotWaterSetpointOverride" icon="temperature" label="Domestic hot water setpoint override [%.1f Â°C]" minValue="0" maxValue="100" step="0.1"
-        Text item="BoilerWaterTemperature" icon="temperature" label="Boiler water temperature [%.1f Â°C]"
-        Text item="ReturnWaterTemperature" icon="temperature" label="Return water temperature [%.1f Â°C]"
-        Setpoint item="OutsideTemperature" icon="temperature" label="Outside temperature [%.1f Â°C]" minValue="-40" maxValue="100" step="0.1"
-        Text item="CentralHeatingWaterPressure" icon="" label="Central heating water pressure [%.1f bar]"
-        Switch item="CentralHeatingEnabled" icon="switch" label="Central heating enabled"
-        Switch item="CentralHeatingEnabledRequested" icon="switch" label="Central heating enabled requested"
-        Switch item="CentralHeatingEnabledOverride" icon="switch" label="Central heating enabled override"
-        Switch item="CentralHeatingActive" icon="switch" label="Central heating active"
-        Switch item="DomesticHotWaterEnabled" icon="switch" label="Domestic hot water enabled"
-        Switch item="DomesticHotWaterActive" icon="switch" label="Domestic hot water active"
-        Switch item="BurnerActive" icon="switch" label="Burner active"
-        Text item="RelativeModulationLevel" icon="" label="Relative modulation level [%.1f %%]"
-        Text item="MaximumRelativeModulationLevel" icon="" label="Maximum relative modulation level [%.1f %%]"
-        Switch item="Fault" icon="" label="Fault indication"
-        Switch item="ServiceRequest" icon="" label="Service required"
-        Switch item="LockoutReset" icon="" label="Lockout-reset"
-        Switch item="LowWaterPress" icon="" label="Low water pressure fault"
-        Switch item="GasFlameFault" icon="" label="Gas or flame fault"
-        Switch item="AirPressFault" icon="" label="Air pressure fault"
-        Switch item="waterOvTemp" icon="" label="Water over-temperature fault"
-        Text item="OemFaultCode" icon="" label="OEM fault code"
-        Switch item="Diagnostics" icon="" label="Diagnostics indication"
-        Text item="UnsuccessfulBurnerStarts" icon="" label="Unsuccessful burner starts"
-        Text item="BurnerStarts" icon="" label="Burner starts"
-        Text item="CentralHeatingPumpStarts" icon="" label="Central heating pump starts"
-        Text item="DomesticHotWaterPumpValveStarts" icon="" label="Domestic hot water pump/valve starts"
-        Text item="DomesticHotWaterBurnerStarts" icon="" label="Domestic hot water burner starts"
-        Text item="BurnerHours" icon="" label="Burner hours"
-        Text item="CentralHeatingPumpHours" icon="" label="Central heating pump hours"
-        Text item="DomesticHotWaterPumpValveHours" icon="" label="Domestic hot water pump/valve hours"
-        Text item="DomesticHotWaterBurnerHours" icon="" label="Domestic hot water burner hours"
+    Frame label="Boiler" {
+        Text item="RoomTemperature" icon="temperature" label="Room Temperature [%.1f %unit%]"
+        Text item="RoomSetpoint" icon="temperature" label="Room Setpoint [%.1f %unit%]"
+        Text item="TemporaryRoomSetpointOverride" icon="temperature" label="Temporary Room Setpoint Override [%.1f %unit%]" minValue="0" maxValue="30" step="0.1"
+        Text item="ConstantRoomSetpointOverride" icon="temperature" label="Constant Room Setpoint Override [%.1f %unit%]" minValue="0" maxValue="30" step="0.1"
+        Text item="ControlSetpoint" icon="temperature" label="Control Setpoint [%.1f %unit%]"
+        Text item="ControlSetpointRequested" icon="temperature" label="Control Setpoint Requested [%.1f %unit%]"
+        Text item="ControlSetpointOverride" icon="temperature" label="Control Setpoint Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
+        Text item="ControlSetpoint2" icon="temperature" label="Control Setpoint 2 [%.1f %unit%]"
+        Text item="ControlSetpoint2Requested" icon="temperature" label="Control Setpoint 2 Requested [%.1f %unit%]"
+        Text item="ControlSetpoint2Override" icon="temperature" label="Control Setpoint 2 Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
+        Text item="DomesticHotWaterTemperature" icon="temperature" label="Domestic Hot Water Temperature [%.1f %unit%]"
+        Text item="DomesticHotWaterSetpoint" icon="temperature" label="Domestic Hot Water Setpoint [%.1f %unit%]"
+        Text item="DomesticHotWaterSetpointOverride" icon="temperature" label="Domestic Hot Water Setpoint Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
+        Text item="BoilerWaterTemperature" icon="temperature" label="Boiler Water Temperature [%.1f %unit%]"
+        Text item="ReturnWaterTemperature" icon="temperature" label="Return Water Temperature [%.1f %unit%]"
+        Text item="OutsideTemperature" icon="temperature" label="Outside Temperature [%.1f %unit%]" minValue="-40" maxValue="100" step="0.1"
+        Text item="CentralHeatingWaterPressure" label="Central Heating Water Pressure [%.1f bar]"
+        Switch item="CentralHeatingEnabled" icon="switch" label="Central Heating Enabled"
+        Switch item="CentralHeatingEnabledThermostat" icon="switch" label="Central Heating Enabled Thermostat"
+        Switch item="CentralHeatingOverridden" icon="switch" label="Central Heating Overridden"
+        Switch item="CentralHeating2Enabled" icon="switch" label="Central Heating 2 Enabled"
+        Switch item="CentralHeating2EnabledThermostat" icon="switch" label="Central Heating 2 Enabled Thermostat"
+        Switch item="CentralHeating2Overridden" icon="switch" label="Central Heating 2 Overridden"
+        Switch item="CentralHeatingActive" icon="switch" label="Central Heating Active"
+        Switch item="DomesticHotWaterEnabled" icon="switch" label="Domestic Hot Water Enabled"
+        Switch item="DomesticHotWaterActive" icon="switch" label="Domestic Hot Water Active"
+        Switch item="BurnerActive" icon="switch" label="Burner Active"
+        Text item="RelativeModulationLevel" label="Relative Modulation Level [%.1f %%]"
+        Text item="MaximumRelativeModulationLevel" label="Maximum Relative Modulation Level [%.1f %%]"
+        Switch item="FaultIndication" icon="switch" label="Fault Indication"
+        Switch item="ServiceRequired" icon="switch" label="Service Required"
+        Switch item="Lockout-ResetEnabled" icon="switch" label="Lockout-Reset Enabled"
+        Switch item="LowWaterPressureFault" icon="switch" label="Low Water Pressure Fault"
+        Switch item="GasOrFlameFault" icon="switch" label="Gas Or Flame Fault"
+        Switch item="AirPressureFault" icon="switch" label="Air Pressure Fault"
+        Switch item="WaterOver-TemperatureFault" icon="switch" label="Water Over-Temperature Fault"
+        Text item="OEMFaultCode" label="OEM Fault Code"
+        Text item="UnsuccessfulBurnerStarts" label="Unsuccessful Burner Starts"
+        Text item="BurnerStarts" label="Burner Starts"
+        Text item="CentralHeatingPumpStarts" label="Central Heating Pump Starts"
+        Text item="DomesticHotWaterPump/ValveStarts" label="Domestic Hot Water Pump/Valve Starts"
+        Text item="DomesticHotWaterBurnerStarts" label="Domestic Hot Water Burner Starts"
+        Text item="BurnerHours" label="Burner Hours"
+        Text item="CentralHeatingPumpHours" label="Central Heating Pump Hours"
+        Text item="DomesticHotWaterPumpValveHours" label="Domestic Hot Water Pump/Valve Hours"
+        Text item="DomesticHotWaterBurnerHours" label="Domestic Hot Water Burner Hours"
+        Text item="TransparentSlaveParameterNumber" label="Transparent Slave Parameter Number"
+        Text item="TransparentSlaveParameterEntry" label="Transparent Slave Parameter Entry"
+        Text item="FaultHistoryBufferNumber" label="Fault History Buffer Number"
+        Text item="FaultHistoryBufferEntry" label="Fault History Buffer Entry"
     }
+
+    Frame label="Ventilation / Heat Recovery" {
+        Switch item="VentilationEnabled" icon="switch" label="Ventilation Enabled"
+        Text item="BypassPosition" label="Bypass Position"
+        Text item="BypassMode" label="Bypass Mode"
+        Switch item="FreeVentilationMode" icon="switch" label="Free Ventilation Mode"
+        Switch item="FaultIndication" icon="switch" label="Fault Indication"
+        Switch item="VentilationMode" icon="switch" label="Ventilation Mode"
+        Switch item="BypassStatus" icon="switch" label="Bypass Status"
+        Text item="BypassAutomaticStatus" label="Bypass Automatic Status"
+        Switch item="FreeVentilationStatus" icon="switch" label="Free Ventilation Status"
+        Switch item="FilterCheck" icon="switch" label="Filter Check"
+        Switch item="DiagnosticIndication" icon="switch" label="Diagnostic Indication"
+        Text item="ControlSetpoint" label="Control Setpoint"
+        Switch item="ServiceRequest" icon="switch" label="Service Request"
+        Switch item="ExhaustFanFault" icon="switch" label="Exhaust Fan Fault"
+        Switch item="InletFanFault" icon="switch" label="Inlet Fan Fault"
+        Switch item="FrostProtection" icon="switch" label="Frost Protection"
+        Text item="FaultCode" label="Fault Code"
+        Text item="DiagnosticCode" label="Diagnostic Code"
+        Text item="SystemType" label="System Type"
+        Switch item="Bypass" icon="switch" label="Bypass"
+        Text item="SpeedControl" label="Speed Control"
+        Text item="MemberID" label="Member ID"
+        Text item="OpenThermVersion" label="OpenTherm Version"
+        Text item="VersionType" label="Version Type"
+        Text item="RelativeVentilation" label="Relative Ventilation [%d %%]"
+        Text item="RelativeHumidity" label="Relative Humidity [%d %%]"
+        Text item="CO2Level" label="CO2 Level [%d ppm]"
+        Text item="SupplyInletTemperature" label="Supply Inlet Temperature [%.1f %unit%]"
+        Text item="SupplyOutletTemperature" label="Supply Outlet Temperature [%.1f %unit%]"
+        Text item="ExhaustInletTemperature" label="Exhaust Inlet Temperature [%.1f %unit%]"
+        Text item="ExhaustOutletTemperature" label="Exhaust Outlet Temperature [%.1f %unit%]"
+        Text item="ActualExhaustFanSpeed" label="Actual Exhaust Fan Speed [%d rpm]"
+        Text item="ActualInletFanSpeed" label="Actual Inlet Fan Speed [%d rpm]"
+        Switch item="NominalVentilationValueTransfer" icon="switch" label="Nominal Ventilation Value Transfer"
+        Text item="NominalVentilationValue" label="Nominal Ventilation Value"
+        Text item="NominalVentilationValue" label="Nominal Ventilation Value [%d %%]"
+        Text item="VentilationSetpoint" label="Ventilation Setpoint" minValue="0" maxValue="100" step="1"
+        Text item="TransparentSlaveParameterNumber" label="Transparent Slave Parameter Number"
+        Text item="TransparentSlaveParameterEntry" label="Transparent Slave Parameter Entry"
+        Text item="FaultHistoryBufferNumber" label="Fault History Buffer Number"
+        Text item="FaultHistoryBufferEntry" label="Fault History Buffer Entry"
+    }
+ }
+```
+
+## Migration from Prior Versions of the Binding
+
+Between openHAB v3.2 and v3.3 the structure of Things and Channels was changed.
+This means that Things and their respective Channels and Items that were created on v3.2 or earlier will no longer function on version v3.3 or later.
+To be specific the change is as follows..
+
+- **openHAB Versions v3.2 or earlier**: There was just one single `otgw` Thing that combined the functions that communicate with the OpenTherm Gateway together with the functions of the Channels that monitor/control the connected Boiler.
+
+- **openHAB Versions v3.3 or later**: The communication functions have been moved to a new `openthermgateway` Bridge Thing. The connected Boiler functions have been moved to a new `boiler` Thing. And in addition (if needed) new functions for a connected Ventilation / Heat-Recovery system have been added in a new `ventilationheatrecovery` Thing.
+
+So if you upgrade your system from openHAB v3.2 (or lower) to v3.3 (or higher), then your Thing and Item definitions must be migrated as shown below..
+
+- Divide the contents of your old `openthermgateway:otgw:yourGatewayId` Thing definition into two new parts, namely 1) a new `openthermgateway:openthermgateway:yourGatewayId` Bridge definition for the OpenTherm Gateway, and 2) a new `openthermgateway:boiler:yourGatewayId:yourBoilerId` Thing definition for the connected Boiler.
+
+- Change the `channel=".."` configuration entries of all your Items from referring to the ThingUID of the old `otgw` Thing to refer instead to the ThingUID of the respective newly created `boiler` Thing.
+
+**Old Thing Definition and respective Item Definition (example)**
+
+```
+Thing openthermgateway:otgw:yourGatewayId [ ipaddress="192.168.1.100", port=8000, connectionRetryInterval=60 ]
+
+e.g.
+Number:Temperature Boiler_DHW_Temperature "Boiler DHW Temperature [%.1f %unit%]" <temperature> {channel="openthermgateway:otgw:yourGatewayId:dhwtemp"}
+&c.
+```
+
+**New Thing Definition and respective and Item Definition (example)**
+
+```
+Bridge openthermgateway:openthermgateway:yourGatewayId "OpenTherm Gateway" @ "Kitchen" [ipaddress="192.168.1.100", port=20108, connectionRetryInterval=60] {
+    Thing boiler remeha "Boiler" @ "Kitchen"
 }
+
+e.g.
+Number:Temperature Boiler_DHW_Temperature "Boiler DHW Temperature [%.1f %unit%]" <temperature> {channel="openthermgateway:boiler:yourGatewayId:remeha:dhwtemp"}
+&c.
 ```
index 1501e8968d9aaf5c145493ea18b301c9972cb071..b2f55a967d954190f2f2ecb5afbf05a615643e6a 100644 (file)
@@ -14,4 +14,5 @@
 
   <name>openHAB Add-ons :: Bundles :: OpenTherm Gateway Binding</name>
 
+  <description>This binding integrates OpenTherm capable devices via an OpenThermGateway bridge.</description>
 </project>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/OpenThermGatewayBindingConstants.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/OpenThermGatewayBindingConstants.java
deleted file mode 100644 (file)
index e8d1c34..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Copyright (c) 2010-2022 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.openthermgateway;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.thing.ThingTypeUID;
-
-/**
- * The {@link OpenThermGatewayBindingConstants} class defines common constants, which are
- * used across the whole binding.
- *
- * @author Arjen Korevaar - Initial contribution
- */
-@NonNullByDefault
-public class OpenThermGatewayBindingConstants {
-
-    private static final String BINDING_ID = "openthermgateway";
-
-    // List of all Thing Type UID's
-    public static final ThingTypeUID MAIN_THING_TYPE = new ThingTypeUID(BINDING_ID, "otgw");
-
-    // List of all Channel id's
-    public static final String CHANNEL_SEND_COMMAND = "sendcommand";
-
-    public static final String CHANNEL_OVERRIDE_SETPOINT_TEMPORARY = "temperaturetemporary";
-    public static final String CHANNEL_OVERRIDE_SETPOINT_CONSTANT = "temperatureconstant";
-    public static final String CHANNEL_OVERRIDE_DHW_SETPOINT = "overridedhwsetpoint";
-    public static final String CHANNEL_ROOM_TEMPERATURE = "roomtemp";
-    public static final String CHANNEL_ROOM_SETPOINT = "roomsetpoint";
-    public static final String CHANNEL_FLOW_TEMPERATURE = "flowtemp";
-    public static final String CHANNEL_RETURN_TEMPERATURE = "returntemp";
-    public static final String CHANNEL_OUTSIDE_TEMPERATURE = "outsidetemp";
-    public static final String CHANNEL_CENTRAL_HEATING_WATER_SETPOINT = "controlsetpoint";
-    public static final String CHANNEL_REQUESTED_CENTRAL_HEATING_WATER_SETPOINT = "controlsetpointrequested";
-    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT = "controlsetpointoverride";
-    public static final String CHANNEL_CENTRAL_HEATING2_WATER_SETPOINT = "controlsetpoint2";
-    public static final String CHANNEL_REQUESTED_CENTRAL_HEATING2_WATER_SETPOINT = "controlsetpoint2requested";
-    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT = "controlsetpoint2override";
-    public static final String CHANNEL_CENTRAL_HEATING_WATER_PRESSURE = "waterpressure";
-    public static final String CHANNEL_CENTRAL_HEATING_ENABLED = "ch_enable";
-    public static final String CHANNEL_REQUESTED_CENTRAL_HEATING_ENABLED = "ch_enablerequested";
-    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED = "ch_enableoverride";
-    public static final String CHANNEL_CENTRAL_HEATING2_ENABLED = "ch2_enable";
-    public static final String CHANNEL_REQUESTED_CENTRAL_HEATING2_ENABLED = "ch2_enablerequested";
-    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED = "ch2_enableoverride";
-    public static final String CHANNEL_CENTRAL_HEATING_MODE = "ch_mode";
-    public static final String CHANNEL_DOMESTIC_HOT_WATER_TEMPERATURE = "dhwtemp";
-    public static final String CHANNEL_DOMESTIC_HOT_WATER_ENABLED = "dhw_enable";
-    public static final String CHANNEL_DOMESTIC_HOT_WATER_MODE = "dhw_mode";
-    public static final String CHANNEL_DOMESTIC_HOT_WATER_SETPOINT = "tdhwset";
-    public static final String CHANNEL_FLAME = "flame";
-    public static final String CHANNEL_RELATIVE_MODULATION_LEVEL = "modulevel";
-    public static final String CHANNEL_MAXIMUM_MODULATION_LEVEL = "maxrelmdulevel";
-    public static final String CHANNEL_FAULT = "fault";
-    public static final String CHANNEL_SERVICEREQUEST = "servicerequest";
-    public static final String CHANNEL_REMOTE_RESET = "lockout-reset";
-    public static final String CHANNEL_LOW_WATER_PRESSURE = "lowwaterpress";
-    public static final String CHANNEL_GAS_FLAME_FAULT = "gasflamefault";
-    public static final String CHANNEL_AIR_PRESSURE_FAULT = "airpressfault";
-    public static final String CHANNEL_WATER_OVER_TEMP = "waterovtemp";
-    public static final String CHANNEL_OEM_FAULTCODE = "oemfaultcode";
-    public static final String CHANNEL_DIAGNOSTICS_INDICATION = "diag";
-    public static final String CHANNEL_UNSUCCESSFUL_BURNER_STARTS = "unsuccessfulburnerstarts";
-    public static final String CHANNEL_BURNER_STARTS = "burnerstarts";
-    public static final String CHANNEL_CH_PUMP_STARTS = "chpumpstarts";
-    public static final String CHANNEL_DHW_PV_STARTS = "dhwpvstarts";
-    public static final String CHANNEL_DHW_BURNER_STARTS = "dhwburnerstarts";
-    public static final String CHANNEL_BURNER_HOURS = "burnerhours";
-    public static final String CHANNEL_CH_PUMP_HOURS = "chpumphours";
-    public static final String CHANNEL_DHW_PV_HOURS = "dhwpvhours";
-    public static final String CHANNEL_DHW_BURNER_HOURS = "dhwburnerhours";
-
-    public static final Set<String> SUPPORTED_CHANNEL_IDS = Set.of(CHANNEL_ROOM_TEMPERATURE, CHANNEL_ROOM_SETPOINT,
-            CHANNEL_FLOW_TEMPERATURE, CHANNEL_RETURN_TEMPERATURE, CHANNEL_OUTSIDE_TEMPERATURE,
-            CHANNEL_CENTRAL_HEATING_WATER_PRESSURE, CHANNEL_CENTRAL_HEATING_ENABLED,
-            CHANNEL_REQUESTED_CENTRAL_HEATING_ENABLED, CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
-            CHANNEL_CENTRAL_HEATING2_ENABLED, CHANNEL_REQUESTED_CENTRAL_HEATING2_ENABLED,
-            CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED, CHANNEL_CENTRAL_HEATING_MODE,
-            CHANNEL_CENTRAL_HEATING_WATER_SETPOINT, CHANNEL_REQUESTED_CENTRAL_HEATING_WATER_SETPOINT,
-            CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT, CHANNEL_CENTRAL_HEATING2_WATER_SETPOINT,
-            CHANNEL_REQUESTED_CENTRAL_HEATING2_WATER_SETPOINT, CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
-            CHANNEL_DOMESTIC_HOT_WATER_TEMPERATURE, CHANNEL_DOMESTIC_HOT_WATER_ENABLED, CHANNEL_DOMESTIC_HOT_WATER_MODE,
-            CHANNEL_DOMESTIC_HOT_WATER_SETPOINT, CHANNEL_FLAME, CHANNEL_RELATIVE_MODULATION_LEVEL,
-            CHANNEL_MAXIMUM_MODULATION_LEVEL, CHANNEL_FAULT, CHANNEL_SERVICEREQUEST, CHANNEL_REMOTE_RESET,
-            CHANNEL_LOW_WATER_PRESSURE, CHANNEL_GAS_FLAME_FAULT, CHANNEL_AIR_PRESSURE_FAULT, CHANNEL_WATER_OVER_TEMP,
-            CHANNEL_OEM_FAULTCODE, CHANNEL_DIAGNOSTICS_INDICATION, CHANNEL_UNSUCCESSFUL_BURNER_STARTS,
-            CHANNEL_BURNER_STARTS, CHANNEL_CH_PUMP_STARTS, CHANNEL_DHW_PV_STARTS, CHANNEL_DHW_BURNER_STARTS,
-            CHANNEL_BURNER_HOURS, CHANNEL_CH_PUMP_HOURS, CHANNEL_DHW_PV_HOURS, CHANNEL_DHW_BURNER_HOURS);
-}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BaseDeviceHandler.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BaseDeviceHandler.java
new file mode 100644 (file)
index 0000000..9987ba2
--- /dev/null
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.handler;
+
+import static org.openhab.binding.openthermgateway.internal.OpenThermGatewayBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.openthermgateway.internal.DataItem;
+import org.openhab.binding.openthermgateway.internal.DataItemGroup;
+import org.openhab.binding.openthermgateway.internal.Message;
+import org.openhab.binding.openthermgateway.internal.TspFhbSizeDataItem;
+import org.openhab.binding.openthermgateway.internal.TspFhbValueDataItem;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Channel;
+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.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link BaseDeviceHandler} is a base class for actual Things.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public abstract class BaseDeviceHandler extends BaseThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(BaseDeviceHandler.class);
+
+    public BaseDeviceHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void initialize() {
+        if (getBridge() == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
+        } else {
+            // note: the framework handles bridge configuration resp. offline errors
+            updateStatus(ThingStatus.ONLINE);
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        Bridge bridge = getBridge();
+
+        if (bridge != null) {
+            OpenThermGatewayHandler handler = (OpenThermGatewayHandler) bridge.getHandler();
+            if (handler != null) {
+                handler.handleCommand(channelUID, command);
+            }
+        } else {
+            logger.debug("Bridge is missing");
+        }
+    }
+
+    public void receiveMessage(Message message) {
+        DataItem[] dataItems = DataItemGroup.DATAITEMGROUPS.get(message.getID());
+
+        if (dataItems == null) {
+            logger.debug("No DataItem found for message id {}", message.getID());
+            return;
+        }
+
+        for (DataItem dataItem : dataItems) {
+            if (dataItem instanceof TspFhbSizeDataItem) {
+                logger.debug("Received TSP or FHB size message {} ({})", message.getID(), dataItem.getSubject());
+
+                verifyTspFhbChannels(((TspFhbSizeDataItem) dataItem).getValueId(),
+                        message.getUInt(dataItem.getByteType()));
+            } else {
+                String channelId = dataItem.getChannelId(message);
+
+                if (thing.getChannel(channelId) == null || !dataItem.hasValidCodeType(message)) {
+                    continue;
+                }
+
+                State state = dataItem.createState(message);
+
+                logger.debug("Received update for channel {}: {}", channelId, state);
+                updateState(channelId, state);
+            }
+        }
+    }
+
+    private void verifyTspFhbChannels(int id, int size) {
+        // Dynamically create TSP or FHB value channels based on TSP or FHB size message
+        ThingHandlerCallback callback = getCallback();
+
+        if (callback == null) {
+            logger.debug("Unable to get thing handler callback");
+            return;
+        }
+
+        DataItem[] dataItems = DataItemGroup.DATAITEMGROUPS.get(id);
+
+        if (dataItems == null) {
+            logger.debug("Unable to find dataItem for id {}", id);
+            return;
+        }
+
+        if (dataItems.length != 1) {
+            logger.debug("Found zero or multiple dataItems for id {}", id);
+            return;
+        }
+
+        TspFhbValueDataItem dataItem = (TspFhbValueDataItem) dataItems[0];
+
+        logger.debug("Checking number of TSP or FHB channels for DATA-ID {}: {}", id, size);
+
+        // A generic Number:Dimensionless channel type for TSP and FHB values
+        ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TSPFHB);
+
+        List<Channel> channels = new ArrayList<>(getThing().getChannels());
+
+        boolean changed = false;
+        for (int i = 0; i < size; i++) {
+            String channelId = dataItem.getChannelId(i);
+            ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
+
+            if (!channels.stream().map(Channel::getUID).anyMatch(channelUID::equals)) {
+                String label = dataItem.getLabel(i);
+
+                logger.debug("Adding channel {}", channelId);
+
+                channels.add(callback.createChannelBuilder(channelUID, channelTypeUID).withKind(ChannelKind.STATE)
+                        .withLabel(label).build());
+                changed = true;
+            } else {
+                logger.debug("Channel {} already exists", channelId);
+            }
+        }
+
+        if (changed) {
+            logger.debug("Updating Thing with new channels");
+            updateThing(editThing().withChannels(channels).build());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BoilerHandler.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/BoilerHandler.java
new file mode 100644 (file)
index 0000000..5946ba6
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link BoilerHandler} represemts a boiler with thermostat.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class BoilerHandler extends BaseDeviceHandler {
+
+    public BoilerHandler(Thing thing) {
+        super(thing);
+    }
+}
index f27f9f5bd71254c2cff5fd774d573a3856811275..33b7ae7a3cd73b177d22aaeb955e0301268893dc 100644 (file)
@@ -15,32 +15,29 @@ package org.openhab.binding.openthermgateway.handler;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import javax.measure.Unit;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openthermgateway.OpenThermGatewayBindingConstants;
-import org.openhab.binding.openthermgateway.internal.DataItem;
+import org.openhab.binding.openthermgateway.internal.ConnectionState;
 import org.openhab.binding.openthermgateway.internal.DataItemGroup;
 import org.openhab.binding.openthermgateway.internal.GatewayCommand;
 import org.openhab.binding.openthermgateway.internal.GatewayCommandCode;
 import org.openhab.binding.openthermgateway.internal.Message;
+import org.openhab.binding.openthermgateway.internal.OpenThermGatewayBindingConstants;
 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayCallback;
 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConfiguration;
 import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConnector;
 import org.openhab.binding.openthermgateway.internal.OpenThermGatewaySocketConnector;
-import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.SIUnits;
+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.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,38 +49,37 @@ import org.slf4j.LoggerFactory;
  * @author Arjen Korevaar - Initial contribution
  */
 @NonNullByDefault
-public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThermGatewayCallback {
+public class OpenThermGatewayHandler extends BaseBridgeHandler implements OpenThermGatewayCallback {
 
     private final Logger logger = LoggerFactory.getLogger(OpenThermGatewayHandler.class);
 
-    private @Nullable OpenThermGatewayConfiguration config;
+    private @Nullable OpenThermGatewayConfiguration configuration;
     private @Nullable OpenThermGatewayConnector connector;
     private @Nullable ScheduledFuture<?> reconnectTask;
 
-    private boolean connecting = false;
-    private boolean explicitDisconnect = false;
+    private @Nullable ConnectionState state;
+    private boolean autoReconnect = true;
+    private boolean disposing = false;
 
-    public OpenThermGatewayHandler(Thing thing) {
-        super(thing);
+    public OpenThermGatewayHandler(Bridge bridge) {
+        super(bridge);
     }
 
     @Override
     public void initialize() {
-        logger.debug("Initializing OpenTherm Gateway handler for uid '{}'", getThing().getUID());
-
-        updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Initializing");
+        logger.debug("Initializing OpenThermGateway handler for uid {}", getThing().getUID());
 
-        config = getConfigAs(OpenThermGatewayConfiguration.class);
+        configuration = getConfigAs(OpenThermGatewayConfiguration.class);
+        logger.debug("Using configuration: {}", configuration);
 
+        disposing = false;
+        updateStatus(ThingStatus.UNKNOWN);
         connect();
     }
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
-        @Nullable
-        OpenThermGatewayConnector conn = connector;
-
-        logger.debug("Received channel: {}, command: {}", channelUID, command);
+        logger.debug("Received command {} for channel {}", command, channelUID);
 
         if (!(command instanceof RefreshType)) {
             String channel = channelUID.getId();
@@ -108,146 +104,121 @@ public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThe
                 gatewayCommand = GatewayCommand.parse(code, command.toFullString());
             }
 
-            if (conn != null && conn.isConnected()) {
-                conn.sendCommand(gatewayCommand);
+            sendCommand(gatewayCommand);
 
-                if (GatewayCommandCode.ControlSetpoint.equals(code)) {
-                    if (gatewayCommand.getMessage().equals("0.0")) {
-                        updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
-                                UnDefType.UNDEF);
-                    }
-                    updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
-                            OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
-                } else if (GatewayCommandCode.ControlSetpoint2.equals(code)) {
-                    if (gatewayCommand.getMessage().equals("0.0")) {
-                        updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
-                                UnDefType.UNDEF);
-                    }
-                    updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED,
-                            OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
+            if (GatewayCommandCode.CONTROLSETPOINT.equals(code)) {
+                if (gatewayCommand.getMessage().equals("0.0")) {
+                    updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
+                            UnDefType.UNDEF);
                 }
-            } else {
-                connect();
+                updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
+                        OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
+            } else if (GatewayCommandCode.CONTROLSETPOINT2.equals(code)) {
+                if (gatewayCommand.getMessage().equals("0.0")) {
+                    updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT,
+                            UnDefType.UNDEF);
+                }
+                updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED,
+                        OnOffType.from(!gatewayCommand.getMessage().equals("0.0")));
             }
         }
     }
 
-    @Override
-    public void connecting() {
-        connecting = true;
-        updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Connecting");
+    public void sendCommand(GatewayCommand gatewayCommand) {
+        @Nullable
+        OpenThermGatewayConnector conn = connector;
+
+        if (conn != null && conn.isConnected()) {
+            conn.sendCommand(gatewayCommand);
+        } else {
+            logger.debug("Unable to send command {}: connector not connected", gatewayCommand.toFullString());
+        }
     }
 
     @Override
-    public void connected() {
-        connecting = false;
-        updateStatus(ThingStatus.ONLINE);
+    public void receiveMessage(Message message) {
+        scheduler.submit(() -> receiveMessageTask(message));
     }
 
-    @Override
-    public void disconnected() {
-        @Nullable
-        OpenThermGatewayConfiguration conf = config;
+    private void receiveMessageTask(Message message) {
+        int msgId = message.getID();
 
-        connecting = false;
+        if (!DataItemGroup.DATAITEMGROUPS.containsKey(msgId)) {
+            logger.debug("Unsupported message id {}", msgId);
+            return;
+        }
 
-        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disconnected");
+        for (Thing thing : getThing().getThings()) {
+            BaseDeviceHandler handler = (BaseDeviceHandler) thing.getHandler();
 
-        // retry connection if disconnect is not explicitly requested
-        if (!explicitDisconnect && conf != null && conf.connectionRetryInterval > 0) {
-            logger.debug("Scheduling to reconnect in {} seconds.", conf.connectionRetryInterval);
-            reconnectTask = scheduler.schedule(this::connect, conf.connectionRetryInterval, TimeUnit.SECONDS);
+            if (handler != null) {
+                handler.receiveMessage(message);
+            }
         }
     }
 
     @Override
-    public void receiveMessage(Message message) {
-        if (DataItemGroup.dataItemGroups.containsKey(message.getID())) {
-            DataItem[] dataItems = DataItemGroup.dataItemGroups.get(message.getID());
-
-            for (DataItem dataItem : dataItems) {
-                String channelId = dataItem.getSubject();
-
-                if (!OpenThermGatewayBindingConstants.SUPPORTED_CHANNEL_IDS.contains(channelId)
-                        || (dataItem.getFilteredCode() != null && dataItem.getFilteredCode() != message.getCode())) {
-                    continue;
-                }
-
-                State state = null;
-
-                switch (dataItem.getDataType()) {
-                    case FLAGS:
-                        state = OnOffType.from(message.getBit(dataItem.getByteType(), dataItem.getBitPos()));
-                        break;
-                    case UINT8:
-                    case UINT16:
-                        state = new DecimalType(message.getUInt(dataItem.getByteType()));
-                        break;
-                    case INT8:
-                    case INT16:
-                        state = new DecimalType(message.getInt(dataItem.getByteType()));
-                        break;
-                    case FLOAT:
-                        float value = message.getFloat();
-                        @Nullable
-                        Unit<?> unit = dataItem.getUnit();
-                        state = (unit == null) ? new DecimalType(value) : new QuantityType<>(value, unit);
-                        break;
-                    case DOWTOD:
-                        break;
-                }
+    public void connectionStateChanged(ConnectionState state) {
+        scheduler.submit(() -> connectionStateChangedTask(state));
+    }
 
-                if (state != null) {
-                    logger.debug("Received update for channel '{}': {}", channelId, state);
-                    updateState(channelId, state);
-                }
+    private void connectionStateChangedTask(ConnectionState state) {
+        if (this.state != state) {
+            this.state = state;
+
+            switch (state) {
+                case CONNECTED:
+                    updateStatus(ThingStatus.ONLINE);
+                    cancelAutoReconnect();
+                    break;
+                case DISCONNECTED:
+                    if (!disposing) {
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+                        autoReconnect();
+                    }
+                default:
             }
         }
     }
 
     @Override
     public void handleRemoval() {
-        logger.debug("Removing OpenTherm Gateway handler");
+        logger.debug("Removing OpenThermGateway handler");
         disconnect();
         super.handleRemoval();
     }
 
     @Override
     public void dispose() {
+        logger.debug("Disposing OpenThermGateway handler");
+        disposing = true;
         disconnect();
-
-        ScheduledFuture<?> localReconnectTask = reconnectTask;
-        if (localReconnectTask != null) {
-            localReconnectTask.cancel(true);
-            reconnectTask = null;
-        }
-
         super.dispose();
     }
 
     private void connect() {
         @Nullable
-        OpenThermGatewayConfiguration conf = config;
-
-        explicitDisconnect = false;
+        OpenThermGatewayConfiguration config = configuration;
 
-        if (connecting) {
-            logger.debug("OpenTherm Gateway connector is already connecting ...");
+        if (this.state == ConnectionState.CONNECTING) {
+            logger.debug("OpenThermGateway connector is already connecting");
             return;
         }
 
+        // Make sure everything is cleaned up before creating a new connection
         disconnect();
 
-        if (conf != null) {
-            logger.debug("Starting OpenTherm Gateway connector");
+        if (config != null) {
+            connectionStateChanged(ConnectionState.INITIALIZING);
+
+            logger.debug("Starting OpenThermGateway connector");
 
-            connector = new OpenThermGatewaySocketConnector(this, conf.ipaddress, conf.port);
+            autoReconnect = true;
 
-            Thread thread = new Thread(connector, "OpenTherm Gateway Binding - socket listener thread");
-            thread.setDaemon(true);
-            thread.start();
+            OpenThermGatewayConnector conn = connector = new OpenThermGatewaySocketConnector(this, config);
+            conn.start();
 
-            logger.debug("OpenTherm Gateway connector started");
+            logger.debug("OpenThermGateway connector started");
         }
     }
 
@@ -255,36 +226,59 @@ public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThe
         @Nullable
         OpenThermGatewayConnector conn = connector;
 
-        explicitDisconnect = true;
+        autoReconnect = false;
+
+        cancelAutoReconnect();
 
         if (conn != null) {
-            if (conn.isConnected()) {
-                logger.debug("Stopping OpenTherm Gateway connector");
-                conn.stop();
+            conn.stop();
+            connector = null;
+        }
+    }
+
+    private void autoReconnect() {
+        @Nullable
+        OpenThermGatewayConfiguration config = configuration;
+
+        if (autoReconnect && config != null && config.connectionRetryInterval > 0) {
+            logger.debug("Scheduling to auto reconnect in {} seconds", config.connectionRetryInterval);
+            reconnectTask = scheduler.schedule(this::connect, config.connectionRetryInterval, TimeUnit.SECONDS);
+        }
+    }
+
+    private void cancelAutoReconnect() {
+        ScheduledFuture<?> localReconnectTask = reconnectTask;
+
+        if (localReconnectTask != null) {
+            if (!localReconnectTask.isDone()) {
+                logger.debug("Cancelling auto reconnect task");
+                localReconnectTask.cancel(true);
             }
 
-            connector = null;
+            reconnectTask = null;
         }
     }
 
     private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
         switch (channel) {
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
-                return GatewayCommandCode.TemperatureTemporary;
+                return GatewayCommandCode.TEMPERATURETEMPORARY;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
-                return GatewayCommandCode.TemperatureConstant;
+                return GatewayCommandCode.TEMPERATURECONSTANT;
             case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
-                return GatewayCommandCode.TemperatureOutside;
+                return GatewayCommandCode.TEMPERATUREOUTSIDE;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
-                return GatewayCommandCode.SetpointWater;
+                return GatewayCommandCode.SETPOINTWATER;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
-                return GatewayCommandCode.ControlSetpoint;
+                return GatewayCommandCode.CONTROLSETPOINT;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
-                return GatewayCommandCode.CentralHeating;
+                return GatewayCommandCode.CENTRALHEATING;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT:
-                return GatewayCommandCode.ControlSetpoint2;
+                return GatewayCommandCode.CONTROLSETPOINT2;
             case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED:
-                return GatewayCommandCode.CentralHeating2;
+                return GatewayCommandCode.CENTRALHEATING2;
+            case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_VENTILATION_SETPOINT:
+                return GatewayCommandCode.VENTILATIONSETPOINT;
             case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
                 return null;
             default:
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/VentilationHeatRecoveryHandler.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/handler/VentilationHeatRecoveryHandler.java
new file mode 100644 (file)
index 0000000..3719f08
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link VentilationHeatRecoveryHandler} represents a Ventilation/Heat Recovery unit.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class VentilationHeatRecoveryHandler extends BaseDeviceHandler {
+
+    public VentilationHeatRecoveryHandler(Thing thing) {
+        super(thing);
+    }
+}
index 4e47356c0428dbcb94f5cb12792a4f4ab8f32a2a..69437c0e4c6fe2f129873ab0f2181f343e3488ad 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link ByteType} enum specifies whether the upper, lower or both bytes are used
  * 
  * @author Arjen Korevaar - Initial contribution
  */
+@NonNullByDefault
 public enum ByteType {
     HIGHBYTE,
     LOWBYTE,
index 70f64c2ae1433b0a8741d7c1211773cf4922b15d..8bb1d6819df8e157fb9b939ad084260788e8500c 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link CodeType} field is not part of OpenTherm specification, but added by OpenTherm Gateway.
  * It can be any of the following:
@@ -25,6 +27,7 @@ package org.openhab.binding.openthermgateway.internal;
  * @author Arjen Korevaar - Initial contribution
  * @author James Melville - Introduced code filtering functionality
  */
+@NonNullByDefault
 public enum CodeType {
     /**
      * Message received from the thermostat
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/ConnectionState.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/ConnectionState.java
new file mode 100644 (file)
index 0000000..58dbc41
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ConnectionState} is used to indicate changes in connection state.
+ * 
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public enum ConnectionState {
+    INITIALIZING,
+    DISCONNECTED,
+    CONNECTING,
+    CONNECTED
+}
index 4eb89453aff43a64b821507eda90c67deb2dd47f..d16eae7138636db65d4b96f1f9cd4304288b0705 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
-import javax.measure.Unit;
-
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.types.State;
 
 /**
- * The {@link DataItem} holds the internal OpenTherm message and meta data.
+ * The {@link DataItem} represents the base dataitem.
  *
  * @author Arjen Korevaar - Initial contribution
  */
+
 @NonNullByDefault
-public class DataItem {
-    private int id;
+public abstract class DataItem {
     private Msg msg;
     private ByteType byteType;
-    private DataType dataType;
-    private int bitpos;
     private String subject;
-    private @Nullable Unit<?> unit;
-    private @Nullable CodeType filteredCode;
-
-    public int getID() {
-        return id;
-    }
+    private @Nullable CodeType codeType;
 
     public Msg getMsg() {
         return msg;
     }
 
     public ByteType getByteType() {
-        return this.byteType;
-    }
-
-    public DataType getDataType() {
-        return dataType;
-    }
-
-    public int getBitPos() {
-        return bitpos;
+        return byteType;
     }
 
     public String getSubject() {
         return subject;
     }
 
-    public @Nullable Unit<?> getUnit() {
-        return unit;
+    public @Nullable CodeType getCodeType() {
+        return codeType;
     }
 
-    public @Nullable CodeType getFilteredCode() {
-        return filteredCode;
+    public DataItem(Msg msg, ByteType byteType, String subject, @Nullable CodeType codeType) {
+        this.msg = msg;
+        this.byteType = byteType;
+        this.subject = subject;
+        this.codeType = codeType;
     }
 
-    public DataItem(int id, Msg msg, ByteType byteType, DataType dataType, int bit, String subject) {
-        this(id, msg, byteType, dataType, bit, subject, null, null);
-    }
+    public boolean hasValidCodeType(Message message) {
+        // Used to bind a dataitem to a specific TBRA code
+        @Nullable
+        CodeType code = this.getCodeType();
 
-    public DataItem(int id, Msg msg, ByteType byteType, DataType dataType, int bit, String subject, Unit<?> unit) {
-        this(id, msg, byteType, dataType, bit, subject, unit, null);
+        return (code == null || code == message.getCodeType());
     }
 
-    public DataItem(int id, Msg msg, ByteType byteType, DataType dataType, int bit, String subject,
-            CodeType filteredCode) {
-        this(id, msg, byteType, dataType, bit, subject, null, filteredCode);
+    /**
+     * @param message unused in this default implementation
+     * @return the channel id
+     */
+    public String getChannelId(Message message) {
+        // Default implementation
+        return subject;
     }
 
-    public DataItem(int id, Msg msg, ByteType byteType, DataType dataType, int bit, String subject,
-            @Nullable Unit<?> unit, @Nullable CodeType filteredCode) {
-        this.id = id;
-        this.msg = msg;
-        this.byteType = byteType;
-        this.dataType = dataType;
-        this.bitpos = bit;
-        this.subject = subject;
-        this.unit = unit;
-        this.filteredCode = filteredCode;
-    }
+    public abstract State createState(Message message);
 }
index a700bddf57e100e0f8caa8041f6cbafcfb137173..62a5b7dbe655980ad5670c2b1ee041ef0ad22d00 100644 (file)
@@ -27,177 +27,206 @@ import org.openhab.core.library.unit.Units;
 @NonNullByDefault
 public class DataItemGroup {
 
-    public static final Map<Integer, DataItem[]> dataItemGroups = createDataItemGroups();
+    public static final Map<Integer, DataItem[]> DATAITEMGROUPS = createDataItemGroups();
 
     private static Map<Integer, DataItem[]> createDataItemGroups() {
         HashMap<Integer, DataItem[]> g = new HashMap<>();
 
-        g.put(0, new DataItem[] {
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enable", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enablerequested", CodeType.T),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enableoverride", CodeType.R),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "dhw_enable", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "cooling_enabled", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "otc_active", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enable", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enablerequested", CodeType.T),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enableoverride", CodeType.R),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x00:5", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x00:6", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x00:7", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 0, "fault", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 1, "ch_mode", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 2, "dhw_mode", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 3, "flame", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 4, "cooling", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 5, "ch2E", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 6, "diag", CodeType.B),
-                new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 7, "0x00:7", CodeType.B) });
-        g.put(1, new DataItem[] {
-                new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint", SIUnits.CELSIUS,
-                        CodeType.B),
-                new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpointrequested",
-                        SIUnits.CELSIUS, CodeType.T),
-                new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpointoverride", SIUnits.CELSIUS,
-                        CodeType.R) });
-        g.put(2, new DataItem[] { new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 0, "0x02:0"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 1, "0x02:1"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 2, "0x02:2"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 3, "0x02:3"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 4, "0x02:4"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x02:5"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x02:6"),
-                new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x02:7"),
-                new DataItem(2, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "mastermemberid") });
-        g.put(3, new DataItem[] { new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "dhwpresent"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "controltype"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "coolingsupport"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "dhwconfig"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "masterlowoff"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "ch2present"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x03:6"),
-                new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x03:7"),
-                new DataItem(3, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "slavememberid") });
-        g.put(4, new DataItem[] { new DataItem(4, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "commandcode"),
-                new DataItem(4, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "commandresponse") });
-        g.put(5, new DataItem[] { new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "servicerequest"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "lockout-reset"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "lowwaterpress"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "gasflamefault"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "airpressfault"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "waterovtemp"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x05:6"),
-                new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x05:7"),
-                new DataItem(5, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "oemfaultcode") });
-        g.put(6, new DataItem[] { new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 0, "0x06:l0"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 1, "0x06:l1"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 2, "0x06:l2"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 3, "0x06:l3"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 4, "0x06:l4"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 5, "0x06:l5"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 6, "0x06:l6"),
-                new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 7, "0x06:l7"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "0x06:h0"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "0x06:h1"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "0x06:h2"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "0x06:h3"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "0x06:h4"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x06:h5"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x06:h6"),
-                new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x06:h7") });
-        g.put(7, new DataItem[] { new DataItem(7, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "0x07") });
-        g.put(8, new DataItem[] {
-                new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2", SIUnits.CELSIUS,
-                        CodeType.B),
-                new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2requested",
-                        SIUnits.CELSIUS, CodeType.T),
-                new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2override",
-                        SIUnits.CELSIUS, CodeType.R) });
-        g.put(9, new DataItem[] { new DataItem(9, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "overridesetpoint") });
-        g.put(10, new DataItem[] { new DataItem(10, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0a:h"),
-                new DataItem(10, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0a:l") });
-        g.put(11, new DataItem[] { new DataItem(11, Msg.READWRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "tspindex"),
-                new DataItem(11, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "tspvalue") });
-        g.put(12, new DataItem[] { new DataItem(12, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0c:h"),
-                new DataItem(12, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0c:l") });
-        g.put(13, new DataItem[] { new DataItem(13, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0d:h"),
-                new DataItem(13, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0d:l") });
-        g.put(14, new DataItem[] {
-                new DataItem(14, Msg.READ, ByteType.LOWBYTE, DataType.FLOAT, 0, "maxrelmdulevel", Units.PERCENT) });
-        g.put(15, new DataItem[] { new DataItem(15, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "maxcapkw"),
-                new DataItem(15, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "maxcapprc") });
-        g.put(16, new DataItem[] {
-                new DataItem(16, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "roomsetpoint", SIUnits.CELSIUS) });
-        g.put(17, new DataItem[] {
-                new DataItem(17, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "modulevel", Units.PERCENT) });
-        g.put(18, new DataItem[] {
-                new DataItem(18, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "waterpressure", Units.BAR) });
-        g.put(19, new DataItem[] { new DataItem(19, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhwflow") });
-        g.put(20, new DataItem[] { new DataItem(20, Msg.READWRITE, ByteType.BOTH, DataType.DOWTOD, 0, "dowtod") });
-        g.put(21, new DataItem[] { new DataItem(21, Msg.READWRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "month"),
-                new DataItem(21, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "dom") });
-        g.put(22, new DataItem[] { new DataItem(22, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "year") });
-        g.put(23, new DataItem[] { new DataItem(23, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "setpointch2") });
-        g.put(24, new DataItem[] {
-                new DataItem(24, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "roomtemp", SIUnits.CELSIUS) });
-        g.put(25, new DataItem[] {
-                new DataItem(25, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "flowtemp", SIUnits.CELSIUS) });
-        g.put(26, new DataItem[] {
-                new DataItem(26, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhwtemp", SIUnits.CELSIUS) });
-        g.put(27, new DataItem[] {
-                new DataItem(27, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "outsidetemp", SIUnits.CELSIUS) });
-        g.put(28, new DataItem[] {
-                new DataItem(28, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "returntemp", SIUnits.CELSIUS) });
-        g.put(29, new DataItem[] { new DataItem(29, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "solstortemp") });
-        g.put(30, new DataItem[] { new DataItem(30, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "solcolltemp") });
-        g.put(31, new DataItem[] { new DataItem(31, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "flowtemp2") });
-        g.put(32, new DataItem[] { new DataItem(32, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhw2temp") });
-        g.put(33, new DataItem[] { new DataItem(33, Msg.READ, ByteType.BOTH, DataType.INT16, 0, "exhausttemp") });
-        g.put(48, new DataItem[] { new DataItem(48, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "tdhwsetu"),
-                new DataItem(48, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "tdhwsetl") });
-        g.put(49, new DataItem[] { new DataItem(49, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "maxchu"),
-                new DataItem(49, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "maxchl") });
-        g.put(50, new DataItem[] { new DataItem(50, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "otcu"),
-                new DataItem(50, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "otcl") });
-        g.put(56, new DataItem[] {
-                new DataItem(56, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "tdhwset", SIUnits.CELSIUS) });
-        g.put(57, new DataItem[] { new DataItem(57, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "tchmax") });
-        g.put(58, new DataItem[] { new DataItem(58, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "otchcratio") });
+        g.put(0, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enable", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enablerequested", CodeType.T),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enableoverride", CodeType.R),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "dhw_enable", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "cooling_enabled", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "otc_active", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enable", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enablerequested", CodeType.T),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enableoverride", CodeType.R),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "0x00:5", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x00:6", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x00:7", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "fault", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "ch_mode", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "dhw_mode", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "flame", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "cooling", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "ch2E", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "diag", CodeType.B),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 7, "0x00:7", CodeType.B) });
+        g.put(1, new DataItem[] { new FloatDataItem(Msg.WRITE, "controlsetpoint", SIUnits.CELSIUS, CodeType.B),
+                new FloatDataItem(Msg.WRITE, "controlsetpointrequested", SIUnits.CELSIUS, CodeType.T),
+                new FloatDataItem(Msg.WRITE, "controlsetpointoverride", SIUnits.CELSIUS, CodeType.R) });
+        g.put(2, new DataItem[] { new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 0, "0x02:0"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 1, "0x02:1"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 2, "0x02:2"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 3, "0x02:3"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 4, "0x02:4"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 5, "0x02:5"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 6, "0x02:6"),
+                new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 7, "0x02:7"),
+                new UIntDataItem(Msg.WRITE, ByteType.LOWBYTE, "mastermemberid") });
+        g.put(3, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "dhwpresent"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "controltype"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "coolingsupport"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "dhwconfig"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "masterlowoff"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "ch2present"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x03:6"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x03:7"),
+                new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "slavememberid") });
+        g.put(4, new DataItem[] { new UIntDataItem(Msg.WRITE, ByteType.HIGHBYTE, "commandcode"),
+                new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "commandresponse") });
+        g.put(5, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "servicerequest"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "lockout-reset"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "lowwaterpress"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "gasflamefault"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "airpressfault"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "waterovtemp"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x05:6"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x05:7"),
+                new IntDataItem(Msg.READ, ByteType.LOWBYTE, "oemfaultcode") });
+        g.put(6, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "0x06:h0"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "0x06:h1"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "0x06:h2"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "0x06:h3"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "0x06:h4"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "0x06:h5"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x06:h6"),
+                new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x06:h7"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "0x06:l0"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "0x06:l1"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "0x06:l2"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "0x06:l3"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "0x06:l4"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "0x06:l5"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "0x06:l6"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 7, "0x06:l7") });
+        g.put(7, new DataItem[] { new FloatDataItem(Msg.WRITE, "coolingcontrolsignal") });
+        g.put(8, new DataItem[] { new FloatDataItem(Msg.WRITE, "controlsetpoint2", SIUnits.CELSIUS, CodeType.B),
+                new FloatDataItem(Msg.WRITE, "controlsetpoint2requested", SIUnits.CELSIUS, CodeType.T),
+                new FloatDataItem(Msg.WRITE, "controlsetpoint2override", SIUnits.CELSIUS, CodeType.R) });
+        g.put(9, new DataItem[] { new FloatDataItem(Msg.READ, "overridesetpoint") });
+        g.put(10, new DataItem[] { new TspFhbSizeDataItem(Msg.WRITE, ByteType.HIGHBYTE, 11, "tspnumber") });
+        g.put(11, new DataItem[] { new TspFhbValueDataItem(Msg.READWRITE, "tspentry") });
+        g.put(12, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 13, "fhbnumber") });
+        g.put(13, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "fhbentry") });
+        g.put(14, new DataItem[] { new FloatDataItem(Msg.READ, "maxrelmdulevel", Units.PERCENT) });
+        g.put(15, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.HIGHBYTE, "maxcapkw"),
+                new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "maxcapprc") });
+        g.put(16, new DataItem[] { new FloatDataItem(Msg.WRITE, "roomsetpoint", SIUnits.CELSIUS) });
+        g.put(17, new DataItem[] { new FloatDataItem(Msg.READ, "modulevel", Units.PERCENT) });
+        g.put(18, new DataItem[] { new FloatDataItem(Msg.READ, "waterpressure", Units.BAR) });
+        g.put(19, new DataItem[] { new FloatDataItem(Msg.READ, "dhwflow") });
+        g.put(20, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.BOTH, "dowtod") });
+        g.put(21, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.HIGHBYTE, "month"),
+                new UIntDataItem(Msg.READWRITE, ByteType.LOWBYTE, "day") });
+        g.put(22, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.BOTH, "year") });
+        g.put(23, new DataItem[] { new FloatDataItem(Msg.WRITE, "setpointch2") });
+        g.put(24, new DataItem[] { new FloatDataItem(Msg.WRITE, "roomtemp", SIUnits.CELSIUS) });
+        g.put(25, new DataItem[] { new FloatDataItem(Msg.READ, "flowtemp", SIUnits.CELSIUS) });
+        g.put(26, new DataItem[] { new FloatDataItem(Msg.READ, "dhwtemp", SIUnits.CELSIUS) });
+        g.put(27, new DataItem[] { new FloatDataItem(Msg.READ, "outsidetemp", SIUnits.CELSIUS) });
+        g.put(28, new DataItem[] { new FloatDataItem(Msg.READ, "returntemp", SIUnits.CELSIUS) });
+
+        g.put(29, new DataItem[] { new FloatDataItem(Msg.READ, "ss_temperature") });
+        g.put(30, new DataItem[] { new FloatDataItem(Msg.READ, "ss_collectortemperature") });
+
+        g.put(31, new DataItem[] { new FloatDataItem(Msg.READ, "flowtemp2") });
+        g.put(32, new DataItem[] { new FloatDataItem(Msg.READ, "dhw2temp") });
+        g.put(33, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "exhausttemp") });
+        g.put(48, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "tdhwsetu"),
+                new IntDataItem(Msg.READ, ByteType.LOWBYTE, "tdhwsetl") });
+        g.put(49, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "maxchu"),
+                new IntDataItem(Msg.READ, ByteType.LOWBYTE, "maxchl") });
+        g.put(50, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "otcu"),
+                new IntDataItem(Msg.READ, ByteType.LOWBYTE, "otcl") });
+        g.put(56, new DataItem[] { new FloatDataItem(Msg.READWRITE, "tdhwset", SIUnits.CELSIUS) });
+        g.put(57, new DataItem[] { new FloatDataItem(Msg.READWRITE, "tchmax") });
+        g.put(58, new DataItem[] { new FloatDataItem(Msg.READWRITE, "otchcratio") });
+        g.put(70,
+                new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_ventilationenable"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "vh_bypassposition"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "vh_bypassmode"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "vh_freeventilationmode"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "vh_faultindication"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "vh_ventilationmode"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "vh_bypassstatus"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "vh_bypassautomaticstatus"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "vh_freeventilationstatus"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "vh_filtercheck"),
+                        new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "vh_diagnosticindication") });
+        g.put(71, new DataItem[] { new UIntDataItem(Msg.WRITE, ByteType.LOWBYTE, "vh_controlsetpoint") });
+        g.put(72,
+                new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_servicerequest"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "vh_exhaustfanfault"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "vh_inletfanfault"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "vh_frostprotection"),
+                        new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "vh_faultcode") });
+        g.put(73, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_diagnosticcode") });
+        g.put(74,
+                new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_systemtype"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "vh_bypass"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "vh_speedcontrol"),
+                        new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "vh_memberid") });
+        g.put(75, new DataItem[] { new FloatDataItem(Msg.READ, "vh_openthermversion") });
+        g.put(76, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_versiontype") });
+        g.put(77, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_relativeventilation") });
+        g.put(78, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_relativehumidity") });
+        g.put(79, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_co2level") });
+        g.put(80, new DataItem[] { new FloatDataItem(Msg.READ, "vh_supplyinlettemp") });
+        g.put(81, new DataItem[] { new FloatDataItem(Msg.READ, "vh_supplyoutlettemp") });
+        g.put(82, new DataItem[] { new FloatDataItem(Msg.READ, "vh_exhaustinlettemp") });
+        g.put(83, new DataItem[] { new FloatDataItem(Msg.READ, "vh_exhaustoutlettemp") });
+        g.put(84, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_actualexhaustfanspeed") });
+        g.put(85, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_actualinletfanspeed") });
+        g.put(86, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_nominalventenable"),
+                new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "vh_nominalventrw") });
+        g.put(87, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.HIGHBYTE, "vh_nominalventilationvalue") });
+        g.put(88, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 89, "vh_tspnumber") });
+        g.put(89, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "vh_tspentry") });
+        g.put(90, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 91, "vh_fhbnumber") });
+        g.put(91, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "vh_fhbentry") });
         g.put(100,
-                new DataItem[] { new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "rof0"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "rof1"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "rof2"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "rof3"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "rof4"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "rof5"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "rof6"),
-                        new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "rof7") });
-        g.put(113, new DataItem[] {
-                new DataItem(113, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "unsuccessfulburnerstarts") });
-        g.put(115, new DataItem[] { new DataItem(115, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "oemdiagcode") });
-        g.put(116, new DataItem[] { new DataItem(116, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "burnerstarts") });
-        g.put(117, new DataItem[] { new DataItem(117, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "chpumpstarts") });
-        g.put(118, new DataItem[] { new DataItem(118, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwpvstarts") });
-        g.put(119,
-                new DataItem[] { new DataItem(119, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwburnerstarts") });
-        g.put(120, new DataItem[] {
-                new DataItem(120, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "burnerhours", Units.HOUR) });
-        g.put(121, new DataItem[] {
-                new DataItem(121, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "chpumphours", Units.HOUR) });
-        g.put(122, new DataItem[] {
-                new DataItem(122, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwpvhours", Units.HOUR) });
-        g.put(123, new DataItem[] {
-                new DataItem(123, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwburnerhours", Units.HOUR) });
-        g.put(124,
-                new DataItem[] { new DataItem(124, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "masterotversion") });
-        g.put(125, new DataItem[] { new DataItem(125, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "slaveotversion") });
-        g.put(126,
-                new DataItem[] {
-                        new DataItem(126, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "masterproducttype"),
-                        new DataItem(126, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "masterproductversion") });
-        g.put(127,
-                new DataItem[] { new DataItem(127, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "slaveproducttype"),
-                        new DataItem(127, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "slaveproductversion") });
+                new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "rof0"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "rof1"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "rof2"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "rof3"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "rof4"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "rof5"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "rof6"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "rof7") });
+
+        g.put(101,
+                new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "rof0"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "rof1"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "rof2"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "rof3"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "rof4"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "rof5"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "rof6"),
+                        new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "rof7") });
+        g.put(102, new DataItem[] {});
+
+        g.put(105, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 106, "ss_tspnumber") });
+        g.put(106, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "ss_tspentry") });
+
+        g.put(107, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 108, "ss_fhbnumber") });
+        g.put(108, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "ss_fhbentry") });
+        g.put(113, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "unsuccessfulburnerstarts") });
+        g.put(115, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "oemdiagcode") });
+        g.put(116, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "burnerstarts") });
+        g.put(117, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "chpumpstarts") });
+        g.put(118, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwpvstarts") });
+        g.put(119, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwburnerstarts") });
+        g.put(120, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "burnerhours", Units.HOUR) });
+        g.put(121, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "chpumphours", Units.HOUR) });
+        g.put(122, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwpvhours", Units.HOUR) });
+        g.put(123, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwburnerhours", Units.HOUR) });
+        g.put(124, new DataItem[] { new FloatDataItem(Msg.WRITE, "masterotversion") });
+        g.put(125, new DataItem[] { new FloatDataItem(Msg.READ, "slaveotversion") });
+        g.put(126, new DataItem[] { new UIntDataItem(Msg.WRITE, ByteType.HIGHBYTE, "masterproducttype"),
+                new UIntDataItem(Msg.WRITE, ByteType.LOWBYTE, "masterproductversion") });
+        g.put(127, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.HIGHBYTE, "slaveproducttype"),
+                new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "slaveproductversion") });
 
         return g;
     }
index 442355598c95314582613e732ebb5ec00461cc24..40b7b65f869c51725d4d33773c9e217f75b5da4f 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link DataType} enum indicates the type of data from a DataItem.
- * 
+ *
  * @author Arjen Korevaar - Initial contribution
  */
+@NonNullByDefault
 public enum DataType {
     FLAGS,
     UINT8,
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FlagDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FlagDataItem.java
new file mode 100644 (file)
index 0000000..dddb2b4
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link DataItem} holds the internal OpenTherm message and meta data.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class FlagDataItem extends DataItem {
+    private int bitpos;
+
+    public int getBitPos() {
+        return bitpos;
+    }
+
+    public FlagDataItem(Msg msg, ByteType byteType, int bitpos, String subject) {
+        this(msg, byteType, bitpos, subject, null);
+    }
+
+    public FlagDataItem(Msg msg, ByteType byteType, int bitpos, String subject, @Nullable CodeType codeType) {
+        super(msg, byteType, subject, codeType);
+
+        this.bitpos = bitpos;
+    }
+
+    @Override
+    public State createState(Message message) {
+        return OnOffType.from(message.getBit(super.getByteType(), this.getBitPos()));
+    }
+}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FloatDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/FloatDataItem.java
new file mode 100644 (file)
index 0000000..f090621
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link FloatDataItem} represents a 2-byte float value in a 2's complement format.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class FloatDataItem extends DataItem {
+
+    private @Nullable Unit<?> unit;
+
+    public @Nullable Unit<?> getUnit() {
+        return unit;
+    }
+
+    public FloatDataItem(Msg msg, String subject) {
+        this(msg, subject, null, null);
+    }
+
+    public FloatDataItem(Msg msg, String subject, Unit<?> unit) {
+        this(msg, subject, unit, null);
+    }
+
+    public FloatDataItem(Msg msg, String subject, CodeType codetype) {
+        this(msg, subject, null, codetype);
+    }
+
+    public FloatDataItem(Msg msg, String subject, @Nullable Unit<?> unit, @Nullable CodeType codetype) {
+        super(msg, ByteType.BOTH, subject, codetype);
+
+        this.unit = unit;
+    }
+
+    @Override
+    public State createState(Message message) {
+        @Nullable
+        Unit<?> unit = this.getUnit();
+        float value = message.getFloat();
+        return (unit == null) ? new DecimalType(value) : new QuantityType<>(value, unit);
+    }
+}
index 2df7da8963c675d68e132d481d2267c3262e4a38..1419620197f48e35c6da163e54a1d76952a6fe8a 100644 (file)
@@ -26,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
  */
 @NonNullByDefault
 public class GatewayCommand {
-    private static final Map<String, @Nullable String> supportedCommands = getSupportedCommands();
+    private static final Map<String, @Nullable String> SUPPORTEDCOMMANDS = getSupportedCommands();
 
     private String code;
     private String validationSet;
@@ -83,19 +83,17 @@ public class GatewayCommand {
         if (code != null && code.length() == 2) {
             String codeUpperCase = code.toUpperCase();
 
-            if (supportedCommands.containsKey(codeUpperCase)) {
-                String validateSet = supportedCommands.get(codeUpperCase);
+            if (SUPPORTEDCOMMANDS.containsKey(codeUpperCase)) {
+                String validateSet = SUPPORTEDCOMMANDS.get(codeUpperCase);
 
                 if (validateSet == null) {
                     validateSet = "";
                 }
 
                 return new GatewayCommand(codeUpperCase, message, validateSet);
-            } else {
-                throw new IllegalArgumentException(String.format("Unsupported gateway code '%s'", code.toUpperCase()));
             }
+            throw new IllegalArgumentException(String.format("Unsupported gateway code '%s'", code.toUpperCase()));
         }
-
         throw new IllegalArgumentException(
                 String.format("Unable to parse gateway command with code '%s' and message '%s'", code, message));
     }
@@ -103,45 +101,45 @@ public class GatewayCommand {
     private static Map<String, @Nullable String> getSupportedCommands() {
         Map<String, @Nullable String> c = new HashMap<>();
 
-        c.put(GatewayCommandCode.TemperatureTemporary, null);
-        c.put(GatewayCommandCode.TemperatureConstant, null);
-        c.put(GatewayCommandCode.TemperatureOutside, null);
-        c.put(GatewayCommandCode.SetClock, null);
-        c.put(GatewayCommandCode.HotWater, null);
-        c.put(GatewayCommandCode.PrintReport, "A,B,C,G,I,L,M,O,P,R,S,T,V,W");
-        c.put(GatewayCommandCode.PrintSummary, "0,1");
-        c.put(GatewayCommandCode.GateWay, "0,1,R");
-        c.put(GatewayCommandCode.LedA, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.LedB, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.LedC, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.LedD, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.LedE, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.LedF, "R,X,T,B,O,F,H,W,C,E,M,P");
-        c.put(GatewayCommandCode.GpioA, "0,1,2,3,4,5,6,7");
-        c.put(GatewayCommandCode.GpioB, "0,1,2,3,4,5,6,7");
-        c.put(GatewayCommandCode.SetBack, null);
-        c.put(GatewayCommandCode.TemperatureSensor, "O,R");
-        c.put(GatewayCommandCode.AddAlternative, null);
-        c.put(GatewayCommandCode.DeleteAlternative, null);
-        c.put(GatewayCommandCode.UnknownID, null);
-        c.put(GatewayCommandCode.KnownID, null);
-        c.put(GatewayCommandCode.PriorityMessage, null);
-        c.put(GatewayCommandCode.SetResponse, null);
-        c.put(GatewayCommandCode.ClearResponse, null);
-        c.put(GatewayCommandCode.SetpointHeating, null);
-        c.put(GatewayCommandCode.SetpointWater, null);
-        c.put(GatewayCommandCode.MaximumModulation, null);
-        c.put(GatewayCommandCode.ControlSetpoint, null);
-        c.put(GatewayCommandCode.ControlSetpoint2, null);
-        c.put(GatewayCommandCode.CentralHeating, "0,1");
-        c.put(GatewayCommandCode.CentralHeating2, "0,1");
-        c.put(GatewayCommandCode.VentilationSetpoint, null);
-        c.put(GatewayCommandCode.Reset, null);
-        c.put(GatewayCommandCode.IgnoreTransition, "0,1");
-        c.put(GatewayCommandCode.OverrideHighbyte, "0,1");
-        c.put(GatewayCommandCode.ForceThermostat, "0,1");
-        c.put(GatewayCommandCode.VoltageReference, "0,1,2,3,4,5,6,7,8,9");
-        c.put(GatewayCommandCode.DebugPointer, null);
+        c.put(GatewayCommandCode.TEMPERATURETEMPORARY, null);
+        c.put(GatewayCommandCode.TEMPERATURECONSTANT, null);
+        c.put(GatewayCommandCode.TEMPERATUREOUTSIDE, null);
+        c.put(GatewayCommandCode.SETCLOCK, null);
+        c.put(GatewayCommandCode.HOTWATER, null);
+        c.put(GatewayCommandCode.PRINTREPORT, "A,B,C,G,I,L,M,O,P,R,S,T,V,W");
+        c.put(GatewayCommandCode.PRINTSUMMARY, "0,1");
+        c.put(GatewayCommandCode.GATEWAY, "0,1,R");
+        c.put(GatewayCommandCode.LEDA, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.LEDB, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.LEDC, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.LEDD, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.LEDE, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.LEDF, "R,X,T,B,O,F,H,W,C,E,M,P");
+        c.put(GatewayCommandCode.GPIOA, "0,1,2,3,4,5,6,7");
+        c.put(GatewayCommandCode.GPIOB, "0,1,2,3,4,5,6,7");
+        c.put(GatewayCommandCode.SETBACK, null);
+        c.put(GatewayCommandCode.TEMPERATURESENSOR, "O,R");
+        c.put(GatewayCommandCode.ADDALTERNATIVE, null);
+        c.put(GatewayCommandCode.DELETEALTERNATIVE, null);
+        c.put(GatewayCommandCode.UNKNOWNID, null);
+        c.put(GatewayCommandCode.KNOWNID, null);
+        c.put(GatewayCommandCode.PRIORITYMESSAGE, null);
+        c.put(GatewayCommandCode.SETRESPONSE, null);
+        c.put(GatewayCommandCode.CLEARRESPONSE, null);
+        c.put(GatewayCommandCode.SETPOINTHEATING, null);
+        c.put(GatewayCommandCode.SETPOINTWATER, null);
+        c.put(GatewayCommandCode.MAXIMUMMODULATION, null);
+        c.put(GatewayCommandCode.CONTROLSETPOINT, null);
+        c.put(GatewayCommandCode.CONTROLSETPOINT2, null);
+        c.put(GatewayCommandCode.CENTRALHEATING, "0,1");
+        c.put(GatewayCommandCode.CENTRALHEATING2, "0,1");
+        c.put(GatewayCommandCode.VENTILATIONSETPOINT, null);
+        c.put(GatewayCommandCode.RESET, null);
+        c.put(GatewayCommandCode.IGNORETRANSITION, "0,1");
+        c.put(GatewayCommandCode.OVERRIDEHIGHBYTE, "0,1");
+        c.put(GatewayCommandCode.FORCETHERMOSTAT, "0,1");
+        c.put(GatewayCommandCode.VOLTAGEREFERENCE, "0,1,2,3,4,5,6,7,8,9");
+        c.put(GatewayCommandCode.DEBUGPOINTER, null);
 
         return c;
     }
index 7e6e87f2201877abbcfeef0f0e39862be9d3d25e..bd870d8f234d2bb68db15a6d15047d2009553c3e 100644 (file)
@@ -21,43 +21,43 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  */
 @NonNullByDefault
 public class GatewayCommandCode {
-    public static final String TemperatureTemporary = "TT";
-    public static final String TemperatureConstant = "TC";
-    public static final String TemperatureOutside = "OT";
-    public static final String SetClock = "ST";
-    public static final String HotWater = "HW";
-    public static final String PrintReport = "PR";
-    public static final String PrintSummary = "PS";
-    public static final String GateWay = "GW";
-    public static final String LedA = "LA";
-    public static final String LedB = "LB";
-    public static final String LedC = "LC";
-    public static final String LedD = "LD";
-    public static final String LedE = "LE";
-    public static final String LedF = "LF";
-    public static final String GpioA = "GA";
-    public static final String GpioB = "GB";
-    public static final String SetBack = "SB";
-    public static final String TemperatureSensor = "TS";
-    public static final String AddAlternative = "AA";
-    public static final String DeleteAlternative = "DA";
-    public static final String UnknownID = "UI";
-    public static final String KnownID = "KI";
-    public static final String PriorityMessage = "PM";
-    public static final String SetResponse = "SR";
-    public static final String ClearResponse = "CR";
-    public static final String SetpointHeating = "SH";
-    public static final String SetpointWater = "SW";
-    public static final String MaximumModulation = "MM";
-    public static final String ControlSetpoint = "CS";
-    public static final String ControlSetpoint2 = "C2";
-    public static final String CentralHeating = "CH";
-    public static final String CentralHeating2 = "H2";
-    public static final String VentilationSetpoint = "VS";
-    public static final String Reset = "RS";
-    public static final String IgnoreTransition = "IT";
-    public static final String OverrideHighbyte = "OH";
-    public static final String ForceThermostat = "FT";
-    public static final String VoltageReference = "VR";
-    public static final String DebugPointer = "DP";
+    public static final String TEMPERATURETEMPORARY = "TT";
+    public static final String TEMPERATURECONSTANT = "TC";
+    public static final String TEMPERATUREOUTSIDE = "OT";
+    public static final String SETCLOCK = "ST";
+    public static final String HOTWATER = "HW";
+    public static final String PRINTREPORT = "PR";
+    public static final String PRINTSUMMARY = "PS";
+    public static final String GATEWAY = "GW";
+    public static final String LEDA = "LA";
+    public static final String LEDB = "LB";
+    public static final String LEDC = "LC";
+    public static final String LEDD = "LD";
+    public static final String LEDE = "LE";
+    public static final String LEDF = "LF";
+    public static final String GPIOA = "GA";
+    public static final String GPIOB = "GB";
+    public static final String SETBACK = "SB";
+    public static final String TEMPERATURESENSOR = "TS";
+    public static final String ADDALTERNATIVE = "AA";
+    public static final String DELETEALTERNATIVE = "DA";
+    public static final String UNKNOWNID = "UI";
+    public static final String KNOWNID = "KI";
+    public static final String PRIORITYMESSAGE = "PM";
+    public static final String SETRESPONSE = "SR";
+    public static final String CLEARRESPONSE = "CR";
+    public static final String SETPOINTHEATING = "SH";
+    public static final String SETPOINTWATER = "SW";
+    public static final String MAXIMUMMODULATION = "MM";
+    public static final String CONTROLSETPOINT = "CS";
+    public static final String CONTROLSETPOINT2 = "C2";
+    public static final String CENTRALHEATING = "CH";
+    public static final String CENTRALHEATING2 = "H2";
+    public static final String VENTILATIONSETPOINT = "VS";
+    public static final String RESET = "RS";
+    public static final String IGNORETRANSITION = "IT";
+    public static final String OVERRIDEHIGHBYTE = "OH";
+    public static final String FORCETHERMOSTAT = "FT";
+    public static final String VOLTAGEREFERENCE = "VR";
+    public static final String DEBUGPOINTER = "DP";
 }
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/IntDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/IntDataItem.java
new file mode 100644 (file)
index 0000000..51cad88
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link IntDataItem} represents an 8 or 16 bit signed integer.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class IntDataItem extends DataItem {
+
+    public IntDataItem(Msg msg, ByteType byteType, String subject) {
+        super(msg, byteType, subject, null);
+    }
+
+    @Override
+    public State createState(Message message) {
+        return new DecimalType(message.getInt(super.getByteType()));
+    }
+}
index b7edd35eaaea14908f8d90f689a6fe1c2da569f1..8a48ae11c78ad68f17c0797503b6469440c891c4 100644 (file)
@@ -25,15 +25,15 @@ import org.eclipse.jdt.annotation.Nullable;
 @NonNullByDefault
 public class Message {
 
-    private static final Pattern messagePattern = Pattern.compile("[TBRA]{1}[A-F0-9]{8}");
+    private static final Pattern MESSAGEPATTERN = Pattern.compile("[TBRA]{1}[A-F0-9]{8}");
 
-    private CodeType code;
+    private CodeType codeType;
     private MessageType messageType;
     private int id;
     private String data;
 
-    public CodeType getCode() {
-        return this.code;
+    public CodeType getCodeType() {
+        return codeType;
     }
 
     public MessageType getMessageType() {
@@ -129,30 +129,30 @@ public class Message {
         // If the message is a Request sent to the boiler or an Answer returned to the
         // thermostat, and it's ID is equal to the previous message, then this is an
         // override sent by the OpenTherm Gateway
-        return other != null && this.getID() == other.getID() && (this.code == CodeType.R || this.code == CodeType.A);
+        return other != null && this.getID() == other.getID() && (codeType == CodeType.R || codeType == CodeType.A);
     }
 
     @Override
     public String toString() {
-        return String.format("%s - %s - %s", this.code, this.id, this.data);
+        return String.format("%s - %s - %s", this.codeType, this.id, this.data);
     }
 
-    public Message(CodeType code, MessageType messageType, int id, String data) {
-        this.code = code;
+    public Message(CodeType codeType, MessageType messageType, int id, String data) {
+        this.codeType = codeType;
         this.messageType = messageType;
         this.id = id;
         this.data = data;
     }
 
     public static @Nullable Message parse(String message) {
-        if (messagePattern.matcher(message).matches()) {
+        if (MESSAGEPATTERN.matcher(message).matches()) {
             // For now, only parse TBRA codes
-            CodeType code = CodeType.valueOf(message.substring(0, 1));
+            CodeType codeType = CodeType.valueOf(message.substring(0, 1));
             MessageType messageType = getMessageType(message.substring(1, 3));
             int id = Integer.valueOf(message.substring(3, 5), 16);
             String data = message.substring(5);
 
-            return new Message(code, messageType, id, data);
+            return new Message(codeType, messageType, id, data);
         }
 
         return null;
index 3f7dacc107297003b5a47badcb3d3d6e093a5f6b..d4dbc954cb0468d57057ff6930eb714c3c8f72e8 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link MessageType} indicates the type of message received by the OpenTherm Gateway, based
  * on the OpenTherm specification.
  * 
  * @author Arjen Korevaar - Initial contribution
  */
+@NonNullByDefault
 public enum MessageType {
     READDATA, // 000
     READACK, // 100
index 9c1884b4fcca2b5c78e6cc420fc2f5976d8765bc..a68c9172f7f7f373aa80acc3922d9a13d88a43ce 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link Msg} flag is used to indicate whether the message is sent for Reading, Writing
  * or both, based on the OpenTherm specification.
  * 
  * @author Arjen Korevaar - Initial contribution
  */
+@NonNullByDefault
 public enum Msg {
     READ,
     WRITE,
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayBindingConstants.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/OpenThermGatewayBindingConstants.java
new file mode 100644 (file)
index 0000000..d33667c
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link OpenThermGatewayBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class OpenThermGatewayBindingConstants {
+
+    // Binding Id
+    public static final String BINDING_ID = "openthermgateway";
+
+    // List of all the ThingType UID's
+    public static final ThingTypeUID OPENTHERM_GATEWAY_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
+            "openthermgateway");
+    public static final ThingTypeUID BOILER_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "boiler");
+    public static final ThingTypeUID VENTILATION_HEATRECOVERY_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
+            "ventilationheatrecovery");
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(OPENTHERM_GATEWAY_THING_TYPE_UID,
+            BOILER_THING_TYPE_UID, VENTILATION_HEATRECOVERY_THING_TYPE_UID);
+
+    // List of id's for writeable channels
+    public static final String CHANNEL_SEND_COMMAND = "sendcommand";
+    public static final String CHANNEL_OVERRIDE_SETPOINT_TEMPORARY = "temperaturetemporary";
+    public static final String CHANNEL_OVERRIDE_SETPOINT_CONSTANT = "temperatureconstant";
+    public static final String CHANNEL_OVERRIDE_DHW_SETPOINT = "overridedhwsetpoint";
+    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT = "controlsetpointoverride";
+    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT = "controlsetpoint2override";
+    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED = "ch_enableoverride";
+    public static final String CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED = "ch2_enableoverride";
+    public static final String CHANNEL_OUTSIDE_TEMPERATURE = "outsidetemp";
+    public static final String CHANNEL_OVERRIDE_VENTILATION_SETPOINT = "vh_ventilationsetpoint";
+
+    // Generic channel type for Transparent Slave Parameter and Fault History Buffer values
+    public static final String CHANNEL_TSPFHB = "tspfhb";
+}
index ad6969d406c28157c19ef18cc5e57eb77c8a9824..d78a9a35a11e264977870d33640b97e3f647b07a 100644 (file)
@@ -22,11 +22,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  */
 @NonNullByDefault
 public interface OpenThermGatewayCallback {
-    void connecting();
 
-    void connected();
-
-    void disconnected();
+    void connectionStateChanged(ConnectionState state);
 
     void receiveMessage(Message message);
 }
index 01af7b4da3e27d1d5a210b9dcccc07860c0a6da5..9fc7aaf8a6013c77b85c55634eae70b06e49861b 100644 (file)
@@ -27,4 +27,8 @@ public class OpenThermGatewayConfiguration {
     public int port = 0;
 
     public int connectionRetryInterval = 60;
+
+    public int connectTimeoutSeconds = 5;
+
+    public int readTimeoutSeconds = 20;
 }
index f98eafac9290ee25c0d620ed2a4b425f915e737a..980a9ef9d13de7852e7078c92f047b8f028e9d7f 100644 (file)
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import java.util.concurrent.Callable;
+
 import org.eclipse.jdt.annotation.NonNullByDefault;
 
 /**
  * The {@link OpenThermGatewayConnector} interface is used to allow multiple types of connectors
  * to be implemented and used to connect to the OpenTherm Gateway.
- * 
+ *
  * @author Arjen Korevaar - Initial contribution
  */
 @NonNullByDefault
-public interface OpenThermGatewayConnector extends Runnable {
+public interface OpenThermGatewayConnector extends Callable<Boolean> {
     void sendCommand(GatewayCommand command);
 
     boolean isConnected();
 
     void stop();
+
+    void start();
 }
index 7aeb4402e0ebf6083d8e451efa56da1dc25a0c8a..9374572dca2dc0f4530341f3b486e004759c2c30 100644 (file)
@@ -14,8 +14,10 @@ package org.openhab.binding.openthermgateway.internal;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openthermgateway.OpenThermGatewayBindingConstants;
+import org.openhab.binding.openthermgateway.handler.BoilerHandler;
 import org.openhab.binding.openthermgateway.handler.OpenThermGatewayHandler;
+import org.openhab.binding.openthermgateway.handler.VentilationHeatRecoveryHandler;
+import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@@ -35,15 +37,19 @@ public class OpenThermGatewayHandlerFactory extends BaseThingHandlerFactory {
 
     @Override
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return thingTypeUID.equals(OpenThermGatewayBindingConstants.MAIN_THING_TYPE);
+        return OpenThermGatewayBindingConstants.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID);
     }
 
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
-        if (thingTypeUID.equals(OpenThermGatewayBindingConstants.MAIN_THING_TYPE)) {
-            return new OpenThermGatewayHandler(thing);
+        if (thingTypeUID.equals(OpenThermGatewayBindingConstants.OPENTHERM_GATEWAY_THING_TYPE_UID)) {
+            return new OpenThermGatewayHandler((Bridge) thing);
+        } else if (thingTypeUID.equals(OpenThermGatewayBindingConstants.BOILER_THING_TYPE_UID)) {
+            return new BoilerHandler(thing);
+        } else if (thingTypeUID.equals(OpenThermGatewayBindingConstants.VENTILATION_HEATRECOVERY_THING_TYPE_UID)) {
+            return new VentilationHeatRecoveryHandler(thing);
         }
 
         return null;
index 6968a102dd97ecb82e8125d6a4076fee0cabd09e..7e8f0942dbadc8b83a3bbd1625cc140e127b49b5 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.openthermgateway.internal;
 
+import static org.openhab.binding.openthermgateway.internal.OpenThermGatewayBindingConstants.BINDING_ID;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -22,12 +24,16 @@ import java.util.AbstractMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.types.State;
+import org.openhab.core.common.NamedThreadFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,107 +45,138 @@ import org.slf4j.LoggerFactory;
  */
 @NonNullByDefault
 public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnector {
-    private static final int COMMAND_RESPONSE_TIME_MILLISECONDS = 100;
-    private static final int COMMAND_TIMEOUT_MILLISECONDS = 5000;
+    private static final int COMMAND_RESPONSE_MIN_WAIT_TIME_MILLISECONDS = 100;
+    private static final int COMMAND_RESPONSE_MAX_WAIT_TIME_MILLISECONDS = 5000;
 
     private final Logger logger = LoggerFactory.getLogger(OpenThermGatewaySocketConnector.class);
 
     private final OpenThermGatewayCallback callback;
     private final String ipaddress;
     private final int port;
+    private final int connectTimeoutMilliseconds;
+    private final int readTimeoutMilliSeconds;
 
-    private @Nullable PrintWriter writer;
-
-    private volatile boolean stopping;
-    private boolean connected;
+    private @Nullable volatile PrintWriter writer;
+    private @Nullable volatile Thread thread;
+    private @Nullable Future<Boolean> future;
+    private @Nullable ExecutorService executor;
 
     private Map<String, Entry<Long, GatewayCommand>> pendingCommands = new ConcurrentHashMap<>();
 
-    public OpenThermGatewaySocketConnector(OpenThermGatewayCallback callback, String ipaddress, int port) {
+    public OpenThermGatewaySocketConnector(OpenThermGatewayCallback callback, OpenThermGatewayConfiguration config) {
         this.callback = callback;
-        this.ipaddress = ipaddress;
-        this.port = port;
+        ipaddress = config.ipaddress;
+        port = config.port;
+        connectTimeoutMilliseconds = config.connectTimeoutSeconds * 1000;
+        readTimeoutMilliSeconds = config.readTimeoutSeconds * 1000;
     }
 
     @Override
-    public void run() {
-        stopping = false;
-        connected = false;
-
-        logger.debug("Connecting OpenThermGatewaySocketConnector to {}:{}", this.ipaddress, this.port);
-
-        callback.connecting();
-
+    public Boolean call() throws Exception {
+        thread = Thread.currentThread();
         try (Socket socket = new Socket()) {
-            socket.connect(new InetSocketAddress(this.ipaddress, this.port), COMMAND_TIMEOUT_MILLISECONDS);
-            socket.setSoTimeout(COMMAND_TIMEOUT_MILLISECONDS);
+            logger.debug("Connecting OpenThermGatewaySocketConnector to {}:{}", this.ipaddress, this.port);
+            callback.connectionStateChanged(ConnectionState.CONNECTING);
 
-            connected = true;
-
-            callback.connected();
+            socket.connect(new InetSocketAddress(ipaddress, port), connectTimeoutMilliseconds);
+            socket.setSoTimeout(readTimeoutMilliSeconds);
 
             logger.debug("OpenThermGatewaySocketConnector connected");
+            callback.connectionStateChanged(ConnectionState.CONNECTED);
 
             try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter wrt = new PrintWriter(socket.getOutputStream(), true)) {
                 // Make writer accessible on class level
                 writer = wrt;
 
-                sendCommand(GatewayCommand.parse(GatewayCommandCode.PrintReport, "A"));
+                sendCommand(GatewayCommand.parse(GatewayCommandCode.PRINTREPORT, "A"));
                 // Set the OTGW to report every message it receives and transmits
-                sendCommand(GatewayCommand.parse(GatewayCommandCode.PrintSummary, "0"));
+                sendCommand(GatewayCommand.parse(GatewayCommandCode.PRINTSUMMARY, "0"));
 
-                while (!stopping && !Thread.currentThread().isInterrupted()) {
+                while (!Thread.currentThread().isInterrupted()) {
                     @Nullable
                     String message = reader.readLine();
 
                     if (message != null) {
                         handleMessage(message);
                     } else {
-                        logger.debug("Connection closed by OpenTherm Gateway");
+                        logger.debug("Received NULL message from OpenTherm Gateway (EOF)");
                         break;
                     }
                 }
-
-                logger.debug("Stopping OpenThermGatewaySocketConnector");
-            } finally {
-                connected = false;
-
-                logger.debug("OpenThermGatewaySocketConnector disconnected");
-                callback.disconnected();
+            } catch (IOException ex) {
+                logger.warn("Error communicating with OpenTherm Gateway: '{}'", ex.getMessage());
             }
         } catch (IOException ex) {
-            logger.warn("Unable to connect to the OpenTherm Gateway.", ex);
-            callback.disconnected();
+            logger.warn("Unable to connect to the OpenTherm Gateway: '{}'", ex.getMessage());
         }
+        thread = null;
+        writer = null;
+        logger.debug("OpenThermGatewaySocketConnector disconnected");
+        callback.connectionStateChanged(ConnectionState.DISCONNECTED);
+        return true;
     }
 
     @Override
     public void stop() {
         logger.debug("Stopping OpenThermGatewaySocketConnector");
-        stopping = true;
+
+        Thread thread = this.thread;
+        Future<Boolean> future = this.future;
+        ExecutorService executor = this.executor;
+
+        if (executor != null) {
+            executor.shutdown();
+        }
+        if ((thread != null) && thread.isAlive()) {
+            thread.interrupt();
+        }
+        if (future != null) {
+            try {
+                future.get(readTimeoutMilliSeconds, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException e) {
+                // expected exception due to e.g. IOException on socket close
+            } catch (TimeoutException | InterruptedException e) {
+                // unexpected exception
+                logger.warn("stop() exception '{}' => PLEASE REPORT !!", e.getMessage());
+            }
+        }
+
+        this.thread = null;
+        this.future = null;
+        this.executor = null;
     }
 
     @Override
-    public boolean isConnected() {
-        return connected;
+    public void start() {
+        logger.debug("Starting OpenThermGatewaySocketConnector");
+        ExecutorService executor = this.executor = Executors
+                .newSingleThreadExecutor(new NamedThreadFactory("binding-" + BINDING_ID));
+        future = executor.submit(this);
     }
 
     @Override
-    public void sendCommand(GatewayCommand command) {
-        @Nullable
-        PrintWriter wrtr = writer;
+    public synchronized boolean isConnected() {
+        Thread thread = this.thread;
+        return (thread != null) && thread.isAlive();
+    }
 
-        String msg = command.toFullString();
+    @Override
+    public synchronized void sendCommand(GatewayCommand command) {
+        PrintWriter wrt = writer;
 
         pendingCommands.put(command.getCode(),
                 new AbstractMap.SimpleImmutableEntry<>(System.currentTimeMillis(), command));
 
-        if (connected) {
+        String msg = command.toFullString();
+
+        if (isConnected() && (wrt != null)) {
             logger.debug("Sending message: {}", msg);
-            if (wrtr != null) {
-                wrtr.print(msg + "\r\n");
-                wrtr.flush();
+            wrt.print(msg + "\r\n");
+            wrt.flush();
+            if (wrt.checkError()) {
+                logger.warn("sendCommand() error sending message to OpenTherm Gateway => PLEASE REPORT !!");
+                stop();
             }
         } else {
             logger.debug("Unable to send message: {}. OpenThermGatewaySocketConnector is not connected.", msg);
@@ -159,8 +196,8 @@ public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnecto
         long currentTime = System.currentTimeMillis();
 
         for (Entry<Long, GatewayCommand> timeAndCommand : pendingCommands.values()) {
-            long responseTime = timeAndCommand.getKey() + COMMAND_RESPONSE_TIME_MILLISECONDS;
-            long timeoutTime = timeAndCommand.getKey() + COMMAND_TIMEOUT_MILLISECONDS;
+            long responseTime = timeAndCommand.getKey() + COMMAND_RESPONSE_MIN_WAIT_TIME_MILLISECONDS;
+            long timeoutTime = timeAndCommand.getKey() + COMMAND_RESPONSE_MAX_WAIT_TIME_MILLISECONDS;
 
             if (currentTime > responseTime && currentTime <= timeoutTime) {
                 logger.debug("Resending command: {}", timeAndCommand.getValue());
@@ -175,47 +212,11 @@ public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnecto
         if (msg == null) {
             logger.trace("Received message: {}, (unknown)", message);
             return;
-        } else {
-            logger.trace("Received message: {}, {} {} {}", message, msg.getID(), msg.getCode(), msg.getMessageType());
-        }
-
-        if (DataItemGroup.dataItemGroups.containsKey(msg.getID())) {
-            DataItem[] dataItems = DataItemGroup.dataItemGroups.get(msg.getID());
-
-            for (DataItem dataItem : dataItems) {
-                State state = null;
-
-                switch (dataItem.getDataType()) {
-                    case FLAGS:
-                        state = OnOffType.from(msg.getBit(dataItem.getByteType(), dataItem.getBitPos()));
-                        break;
-                    case UINT8:
-                    case UINT16:
-                        state = new DecimalType(msg.getUInt(dataItem.getByteType()));
-                        break;
-                    case INT8:
-                    case INT16:
-                        state = new DecimalType(msg.getInt(dataItem.getByteType()));
-                        break;
-                    case FLOAT:
-                        state = new DecimalType(msg.getFloat());
-                        break;
-                    case DOWTOD:
-                        break;
-                }
-
-                logger.trace("  Data: {} {} {} {}", dataItem.getID(), dataItem.getSubject(), dataItem.getDataType(),
-                        state == null ? "" : state);
-            }
         }
-
+        logger.trace("Received message: {}, {} {} {}", message, msg.getID(), msg.getCodeType(), msg.getMessageType());
         if (msg.getMessageType() == MessageType.READACK || msg.getMessageType() == MessageType.WRITEDATA
                 || msg.getID() == 0 || msg.getID() == 1) {
-            receiveMessage(msg);
+            callback.receiveMessage(msg);
         }
     }
-
-    private void receiveMessage(Message message) {
-        callback.receiveMessage(message);
-    }
 }
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbSizeDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbSizeDataItem.java
new file mode 100644 (file)
index 0000000..17c5932
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link DataItem} represents a transparent slave parameter or fault history buffer size.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class TspFhbSizeDataItem extends DataItem {
+    private int valueId;
+
+    public int getValueId() {
+        return valueId;
+    }
+
+    public TspFhbSizeDataItem(Msg msg, ByteType byteType, int valueId, String subject) {
+        super(msg, byteType, subject, null);
+
+        this.valueId = valueId;
+    }
+
+    @Override
+    public State createState(Message message) {
+        return new DecimalType(message.getUInt(super.getByteType()));
+    }
+}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbValueDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/TspFhbValueDataItem.java
new file mode 100644 (file)
index 0000000..21ccdf4
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link TspFhbValueDataItem} represents a transparent slave parameter or fault history buffer value.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class TspFhbValueDataItem extends DataItem {
+
+    public TspFhbValueDataItem(Msg msg, String subject) {
+        super(msg, ByteType.BOTH, subject, null);
+    }
+
+    @Override
+    public String getChannelId(Message message) {
+        // With TSP or FHB values, the index is HIGHBYTE, the value is LOWBYTE
+        int index = message.getUInt(ByteType.HIGHBYTE);
+        return getChannelId(index);
+    }
+
+    public String getChannelId(int index) {
+        return super.getSubject() + "_" + index;
+    }
+
+    public String getLabel(int index) {
+        return super.getSubject() + " " + index;
+    }
+
+    @Override
+    public State createState(Message message) {
+        // With TSP or FHB values, the index is HIGHBYTE, the value is LOWBYTE
+        // TSP values are treated as Number:Dimensionless
+        return new DecimalType(message.getUInt(ByteType.LOWBYTE));
+    }
+}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/UIntDataItem.java b/bundles/org.openhab.binding.openthermgateway/src/main/java/org/openhab/binding/openthermgateway/internal/UIntDataItem.java
new file mode 100644 (file)
index 0000000..9acfce1
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2010-2022 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.openthermgateway.internal;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.types.State;
+
+/**
+ * The {@link UIntDataItem} represents an 8 or 16 bit unsigned integer.
+ *
+ * @author Arjen Korevaar - Initial contribution
+ */
+@NonNullByDefault
+public class UIntDataItem extends DataItem {
+
+    private @Nullable Unit<?> unit;
+
+    public @Nullable Unit<?> getUnit() {
+        return unit;
+    }
+
+    public UIntDataItem(Msg msg, ByteType byteType, String subject) {
+        this(msg, byteType, subject, null);
+    }
+
+    public UIntDataItem(Msg msg, ByteType byteType, String subject, @Nullable Unit<?> unit) {
+        super(msg, byteType, subject, null);
+
+        this.unit = unit;
+    }
+
+    @Override
+    public State createState(Message message) {
+        @Nullable
+        Unit<?> unit = getUnit();
+        int value = message.getUInt(super.getByteType());
+        return (unit == null) ? new DecimalType(value) : new QuantityType<>(value, unit);
+    }
+}
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/config.xml
deleted file mode 100644 (file)
index db806e8..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<config-description:config-descriptions
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
-
-       <config-description uri="thing-type:openthermgateway:otgw">
-               <parameter-group name="connection">
-                       <label>Connection</label>
-                       <description>Connection settings.</description>
-               </parameter-group>
-
-               <parameter name="ipaddress" type="text" required="true" groupName="connection">
-                       <label>Hostname or IP address</label>
-                       <description>The hostname or IP address to connect to the OpenTherm Gateway.</description>
-                       <context>network-address</context>
-               </parameter>
-
-               <parameter name="port" type="integer" required="true" groupName="connection" min="0" max="65535">
-                       <label>Port</label>
-                       <description>The port used to connect to the OpenTherm Gateway.</description>
-               </parameter>
-
-               <parameter name="connectionRetryInterval" type="integer" required="true" groupName="connection" min="0"
-                       max="3600" unit="s">
-                       <label>Connection Retry Interval</label>
-                       <description>The interval in seconds to retry connecting (0 = disabled).</description>
-                       <default>60</default>
-               </parameter>
-
-       </config-description>
-
-</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/openthermgateway.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/config/openthermgateway.xml
new file mode 100644 (file)
index 0000000..6713e1f
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="thing-type:openthermgateway:openthermgateway">
+               <parameter-group name="connection">
+                       <label>Connection</label>
+                       <description>Connection settings.</description>
+               </parameter-group>
+
+               <parameter name="ipaddress" type="text" required="true" groupName="connection">
+                       <label>Hostname or IP address</label>
+                       <description>The hostname or IP address to connect to the OpenTherm Gateway.</description>
+                       <context>network-address</context>
+               </parameter>
+
+               <parameter name="port" type="integer" required="true" groupName="connection" min="0" max="65535">
+                       <label>Port</label>
+                       <description>The port used to connect to the OpenTherm Gateway.</description>
+               </parameter>
+
+               <parameter name="connectionRetryInterval" type="integer" required="true" groupName="connection" min="0"
+                       max="3600" unit="s">
+                       <label>Connection Retry Interval</label>
+                       <description>The interval in seconds to retry connecting (0 = disabled).</description>
+                       <default>60</default>
+               </parameter>
+
+               <parameter name="connectTimeoutSeconds" type="integer" required="true" groupName="connection" min="5"
+                       max="60" unit="s">
+                       <label>Connect Timeout</label>
+                       <description>The maximum time (seconds) to wait for establishing a connection to the gateway.</description>
+                       <default>5</default>
+               </parameter>
+
+               <parameter name="readTimeoutSeconds" type="integer" required="true" groupName="connection" min="5" max="60"
+                       unit="s">
+                       <label>Read Timeout</label>
+                       <description>The maximum time (seconds) to wait for reading responses from the gateway.</description>
+                       <default>20</default>
+               </parameter>
+
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/boiler.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/boiler.xml
new file mode 100644 (file)
index 0000000..26471ea
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="openthermgateway"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <thing-type id="boiler">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="openthermgateway"/>
+               </supported-bridge-type-refs>
+               <label>Boiler</label>
+               <description>Boiler and thermostat</description>
+               <channels>
+                       <channel id="roomtemp" typeId="roomtemp"/>
+                       <channel id="roomsetpoint" typeId="roomsetpoint"/>
+                       <channel id="temperaturetemporary" typeId="temperaturetemporary"/>
+                       <channel id="temperatureconstant" typeId="temperatureconstant"/>
+                       <channel id="controlsetpoint" typeId="controlsetpoint"/>
+                       <channel id="controlsetpointrequested" typeId="controlsetpointrequested"/>
+                       <channel id="controlsetpointoverride" typeId="controlsetpointoverride"/>
+                       <channel id="controlsetpoint2" typeId="controlsetpoint2"/>
+                       <channel id="controlsetpoint2requested" typeId="controlsetpoint2requested"/>
+                       <channel id="controlsetpoint2override" typeId="controlsetpoint2override"/>
+                       <channel id="dhwtemp" typeId="dhwtemp"/>
+                       <channel id="tdhwset" typeId="tdhwset"/>
+                       <channel id="overridedhwsetpoint" typeId="overridedhwsetpoint"/>
+                       <channel id="flowtemp" typeId="flowtemp"/>
+                       <channel id="returntemp" typeId="returntemp"/>
+                       <channel id="outsidetemp" typeId="outsidetemp"/>
+                       <channel id="waterpressure" typeId="waterpressure"/>
+                       <channel id="ch_enable" typeId="ch_enable"/>
+                       <channel id="ch_enablerequested" typeId="ch_enablerequested"/>
+                       <channel id="ch_enableoverride" typeId="ch_enableoverride"/>
+                       <channel id="ch2_enable" typeId="ch2_enable"/>
+                       <channel id="ch2_enablerequested" typeId="ch2_enablerequested"/>
+                       <channel id="ch2_enableoverride" typeId="ch2_enableoverride"/>
+                       <channel id="ch_mode" typeId="ch_mode"/>
+                       <channel id="dhw_enable" typeId="dhw_enable"/>
+                       <channel id="dhw_mode" typeId="dhw_mode"/>
+                       <channel id="flame" typeId="flame"/>
+                       <channel id="modulevel" typeId="modulevel"/>
+                       <channel id="maxrelmdulevel" typeId="maxrelmdulevel"/>
+                       <channel id="fault" typeId="fault"/>
+                       <channel id="servicerequest" typeId="servicerequest"/>
+                       <channel id="lockout-reset" typeId="lockout-reset"/>
+                       <channel id="lowwaterpress" typeId="lowwaterpress"/>
+                       <channel id="gasflamefault" typeId="gasflamefault"/>
+                       <channel id="airpressfault" typeId="airpressfault"/>
+                       <channel id="waterovtemp" typeId="waterovtemp"/>
+                       <channel id="oemfaultcode" typeId="oemfaultcode"/>
+                       <channel id="diag" typeId="diag"/>
+                       <channel id="unsuccessfulburnerstarts" typeId="unsuccessfulburnerstarts"/>
+                       <channel id="burnerstarts" typeId="burnerstarts"/>
+                       <channel id="chpumpstarts" typeId="chpumpstarts"/>
+                       <channel id="dhwpvstarts" typeId="dhwpvstarts"/>
+                       <channel id="dhwburnerstarts" typeId="dhwburnerstarts"/>
+                       <channel id="burnerhours" typeId="burnerhours"/>
+                       <channel id="chpumphours" typeId="chpumphours"/>
+                       <channel id="dhwpvhours" typeId="dhwpvhours"/>
+                       <channel id="dhwburnerhours" typeId="dhwburnerhours"/>
+                       <channel id="tspnumber" typeId="tspnumber"/>
+                       <channel id="tspentry" typeId="tspentry"/>
+                       <channel id="fhbnumber" typeId="fhbnumber"/>
+                       <channel id="fhbentry" typeId="fhbentry"/>
+               </channels>
+       </thing-type>
+
+</thing:thing-descriptions>
index 5d9700ca3b0b350fd2063b7d487a4496fadf6ecd..4314198f6e1067c9ab39d2290646055010649eb5 100644 (file)
@@ -4,6 +4,26 @@
        xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
        xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
 
+       <!-- generic channel type for dynamic tsp and fhb channels -->
+
+       <channel-type id="tspfhb">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Set Dynamically</label>
+               <description>Transparent slave parameter or Fault history buffer value</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <!-- openthermgateway -->
+
+       <channel-type id="sendcommand">
+               <item-type>String</item-type>
+               <label>Send Command</label>
+               <description>Channel to send commands to the OpenTherm Gateway device</description>
+               <state readOnly="false"/>
+       </channel-type>
+
+       <!-- boiler -->
+
        <channel-type id="roomtemp">
                <item-type>Number:Temperature</item-type>
                <label>Room Temperature</label>
                <state readOnly="true"/>
        </channel-type>
 
-       <channel-type id="sendcommand">
-               <item-type>String</item-type>
-               <label>Send Command</label>
-               <description>Channel to send commands to the OpenTherm Gateway device</description>
-               <state readOnly="false"/>
+       <channel-type id="tspnumber">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Transparent Slave Parameter Number</label>
+               <description>Number of transparant slave parameter entries</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="tspentry">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Transparent Slave Parameter Entry</label>
+               <description>Transparent slave parameter entry</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="fhbnumber">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Fault History Buffer Number</label>
+               <description>Number of fault history buffer entries</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="fhbentry">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Fault History Buffer Entry</label>
+               <description>Fault history buffer entry</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <!-- ventilationheatrecovery -->
+
+       <channel-type id="vh_ventilationenable">
+               <item-type>Switch</item-type>
+               <label>Ventilation</label>
+               <description>Ventilation enabled</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_bypassposition">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Bypass Position</label>
+               <description>Bypass position</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">close</option>
+                               <option value="1">open</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_bypassmode">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Bypass Mode</label>
+               <description>Bypass mode</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">manual</option>
+                               <option value="1">automatic</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_freeventilationmode">
+               <item-type>Switch</item-type>
+               <label>Free Ventilation Mode</label>
+               <description>Free ventilation mode</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_faultindication">
+               <item-type>Switch</item-type>
+               <label>Fault Indication</label>
+               <description>Fault indication</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_ventilationmode">
+               <item-type>Switch</item-type>
+               <label>Ventilation Mode</label>
+               <description>Ventilation mode</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_bypassstatus">
+               <item-type>Switch</item-type>
+               <label>Bypass Status</label>
+               <description>Bypass status</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_bypassautomaticstatus">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Bypass Automatic Status</label>
+               <description>Bypass automatic status</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">manual</option>
+                               <option value="1">automatic</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_freeventilationstatus">
+               <item-type>Switch</item-type>
+               <label>Free Ventilation Status</label>
+               <description>Free ventilation status</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_diagnosticindication">
+               <item-type>Switch</item-type>
+               <label>Diagnostic Indication</label>
+               <description>Diagnostic indication</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_controlsetpoint">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Control Setpoint</label>
+               <description>Control setpoint</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_servicerequest">
+               <item-type>Switch</item-type>
+               <label>Service Request</label>
+               <description>Service request</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_exhaustfanfault">
+               <item-type>Switch</item-type>
+               <label>Exhaust Fan Fault</label>
+               <description>Exhaust fan fault</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_inletfanfault">
+               <item-type>Switch</item-type>
+               <label>Inlet Fan Fault</label>
+               <description>Inlet fan fault</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_frostprotection">
+               <item-type>Switch</item-type>
+               <label>Frost Protection</label>
+               <description>Frost protection</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_faultcode">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Fault Code</label>
+               <description>Fault code</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_diagnosticcode">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Diagnostic Code</label>
+               <description>Diagnostic code</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_systemtype">
+               <item-type>Number:Dimensionless</item-type>
+               <label>System Type</label>
+               <description>System type</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">central exaust</option>
+                               <option value="1">heat-recovery</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_bypass">
+               <item-type>Switch</item-type>
+               <label>Bypass</label>
+               <description>Bypass</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_speedcontrol">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Speed Control</label>
+               <description>Speed control</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">3-speed</option>
+                               <option value="1">variable</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_memberid">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Member ID</label>
+               <description>Member ID</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_openthermversion">
+               <item-type>Number:Dimensionless</item-type>
+               <label>OpenTherm Version</label>
+               <description>OpenTherm version</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_versiontype">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Version Type</label>
+               <description>Version type</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_relativeventilation">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Relative Ventilation</label>
+               <description>Relative ventilation position</description>
+               <state readOnly="true" pattern="%d %%"/>
+       </channel-type>
+
+       <channel-type id="vh_relativehumidity">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Relative Humidity</label>
+               <description>Relative humidity exhaust air</description>
+               <state readOnly="true" pattern="%d %%"/>
+       </channel-type>
+
+       <channel-type id="vh_co2level">
+               <item-type>Number:Dimensionless</item-type>
+               <label>CO2 Level</label>
+               <description>CO2 level exhaust air</description>
+               <state readOnly="true" pattern="%d ppm"/>
+       </channel-type>
+
+       <channel-type id="vh_supplyinlettemp">
+               <item-type>Number:Temperature</item-type>
+               <label>Supply Inlet Temperature</label>
+               <description>Supply inlet temperature</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
+       <channel-type id="vh_supplyoutlettemp">
+               <item-type>Number:Temperature</item-type>
+               <label>Supply Outlet Temperature</label>
+               <description>Supply outlet temperature</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
+       <channel-type id="vh_exhaustinlettemp">
+               <item-type>Number:Temperature</item-type>
+               <label>Exhaust Inlet Temperature</label>
+               <description>Exhaust inlet temperature</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
+       <channel-type id="vh_exhaustoutlettemp">
+               <item-type>Number:Temperature</item-type>
+               <label>Exhaust Outlet Temperature</label>
+               <description>Exhaust outlet temperature</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
+       <channel-type id="vh_actualexhaustfanspeed">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Actual Exhaust Fan Speed</label>
+               <description>Actual exhaust fan speed</description>
+               <state readOnly="true" pattern="%d rpm"/>
+       </channel-type>
+
+       <channel-type id="vh_actualinletfanspeed">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Actual Inlet Fan Speed</label>
+               <description>Actual inlet fan speed</description>
+               <state readOnly="true" pattern="%d rpm"/>
+       </channel-type>
+
+       <channel-type id="vh_nominalventenable">
+               <item-type>Switch</item-type>
+               <label>Nominal Ventilation Value Transfer</label>
+               <description>Nominal ventilation value transfer enabled</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_nominalventrw">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Nominal Ventilation Value</label>
+               <description>Nominal ventilation value</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="0">read-only</option>
+                               <option value="1">read/write</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="vh_nominalventilationvalue">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Nominal Ventilation Value</label>
+               <description>Nominal ventilation value</description>
+               <state readOnly="true" pattern="%d %%"/>
+       </channel-type>
+
+       <channel-type id="vh_filtercheck">
+               <item-type>Switch</item-type>
+               <label>Filter Check</label>
+               <description>Filter Check enabled</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_tspnumber">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Transparent Slave Parameter Number</label>
+               <description>Number of transparent slave parameter entries</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_tspentry">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Transparent Slave Parameter Entry</label>
+               <description>Transparent slave parameter entry</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_fhbnumber">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Fault History Buffer Number</label>
+               <description>Number of fault history buffer entries</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_fhbentry">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Fault History Buffer Entry</label>
+               <description>Fault history buffer entry</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="vh_ventilationsetpoint">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Ventilation Setpoint</label>
+               <description>Ventilation setpoint override</description>
+               <state readOnly="false" min="0" max="100" step="1"/>
        </channel-type>
 
 </thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/openthermgateway.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/openthermgateway.xml
new file mode 100644 (file)
index 0000000..ac5a39d
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="openthermgateway"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <bridge-type id="openthermgateway">
+               <label>OpenTherm Gateway</label>
+               <description>OpenTherm Gateway</description>
+               <channels>
+                       <channel id="sendcommand" typeId="sendcommand"/>
+               </channels>
+               <properties>
+                       <property name="version">2.2.0</property>
+               </properties>
+               <config-description-ref uri="thing-type:openthermgateway:openthermgateway"/>
+       </bridge-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/otgw.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/otgw.xml
deleted file mode 100644 (file)
index 373527a..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="openthermgateway"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-       <thing-type id="otgw">
-               <label>OpenTherm Gateway</label>
-               <description>OpenTherm Gateway binding</description>
-               <channels>
-                       <channel id="roomtemp" typeId="roomtemp"/>
-                       <channel id="roomsetpoint" typeId="roomsetpoint"/>
-                       <channel id="temperaturetemporary" typeId="temperaturetemporary"/>
-                       <channel id="temperatureconstant" typeId="temperatureconstant"/>
-                       <channel id="controlsetpoint" typeId="controlsetpoint"/>
-                       <channel id="controlsetpointrequested" typeId="controlsetpointrequested"/>
-                       <channel id="controlsetpointoverride" typeId="controlsetpointoverride"/>
-                       <channel id="controlsetpoint2" typeId="controlsetpoint2"/>
-                       <channel id="controlsetpoint2requested" typeId="controlsetpoint2requested"/>
-                       <channel id="controlsetpoint2override" typeId="controlsetpoint2override"/>
-                       <channel id="dhwtemp" typeId="dhwtemp"/>
-                       <channel id="tdhwset" typeId="tdhwset"/>
-                       <channel id="overridedhwsetpoint" typeId="overridedhwsetpoint"/>
-                       <channel id="flowtemp" typeId="flowtemp"/>
-                       <channel id="returntemp" typeId="returntemp"/>
-                       <channel id="outsidetemp" typeId="outsidetemp"/>
-                       <channel id="waterpressure" typeId="waterpressure"/>
-                       <channel id="ch_enable" typeId="ch_enable"/>
-                       <channel id="ch_enablerequested" typeId="ch_enablerequested"/>
-                       <channel id="ch_enableoverride" typeId="ch_enableoverride"/>
-                       <channel id="ch2_enable" typeId="ch2_enable"/>
-                       <channel id="ch2_enablerequested" typeId="ch2_enablerequested"/>
-                       <channel id="ch2_enableoverride" typeId="ch2_enableoverride"/>
-                       <channel id="ch_mode" typeId="ch_mode"/>
-                       <channel id="dhw_enable" typeId="dhw_enable"/>
-                       <channel id="dhw_mode" typeId="dhw_mode"/>
-                       <channel id="flame" typeId="flame"/>
-                       <channel id="modulevel" typeId="modulevel"/>
-                       <channel id="maxrelmdulevel" typeId="maxrelmdulevel"/>
-                       <channel id="fault" typeId="fault"/>
-                       <channel id="servicerequest" typeId="servicerequest"/>
-                       <channel id="lockout-reset" typeId="lockout-reset"/>
-                       <channel id="lowwaterpress" typeId="lowwaterpress"/>
-                       <channel id="gasflamefault" typeId="gasflamefault"/>
-                       <channel id="airpressfault" typeId="airpressfault"/>
-                       <channel id="waterovtemp" typeId="waterovtemp"/>
-                       <channel id="oemfaultcode" typeId="oemfaultcode"/>
-                       <channel id="unsuccessfulburnerstarts" typeId="unsuccessfulburnerstarts"/>
-                       <channel id="burnerstarts" typeId="burnerstarts"/>
-                       <channel id="chpumpstarts" typeId="chpumpstarts"/>
-                       <channel id="dhwpvstarts" typeId="dhwpvstarts"/>
-                       <channel id="dhwburnerstarts" typeId="dhwburnerstarts"/>
-                       <channel id="burnerhours" typeId="burnerhours"/>
-                       <channel id="chpumphours" typeId="chpumphours"/>
-                       <channel id="dhwpvhours" typeId="dhwpvhours"/>
-                       <channel id="dhwburnerhours" typeId="dhwburnerhours"/>
-                       <channel id="sendcommand" typeId="sendcommand"/>
-               </channels>
-               <properties>
-                       <property name="version">1.3.0</property>
-               </properties>
-               <config-description-ref uri="thing-type:openthermgateway:otgw"/>
-       </thing-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/ventilationheatrecovery.xml b/bundles/org.openhab.binding.openthermgateway/src/main/resources/OH-INF/thing/ventilationheatrecovery.xml
new file mode 100644 (file)
index 0000000..84ed581
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="openthermgateway"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+       <thing-type id="ventilationheatrecovery">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="openthermgateway"/>
+               </supported-bridge-type-refs>
+               <label>Ventilation / Heat Recovery</label>
+               <description>Ventilation or Heat Recovery unit</description>
+               <channels>
+                       <channel id="vh_ventilationenable" typeId="vh_ventilationenable"/>
+                       <channel id="vh_bypassposition" typeId="vh_bypassposition"/>
+                       <channel id="vh_bypassmode" typeId="vh_bypassmode"/>
+                       <channel id="vh_freeventilationmode" typeId="vh_freeventilationmode"/>
+                       <channel id="vh_faultindication" typeId="vh_faultindication"/>
+                       <channel id="vh_ventilationmode" typeId="vh_ventilationmode"/>
+                       <channel id="vh_bypassstatus" typeId="vh_bypassstatus"/>
+                       <channel id="vh_bypassautomaticstatus" typeId="vh_bypassautomaticstatus"/>
+                       <channel id="vh_freeventilationstatus" typeId="vh_freeventilationstatus"/>
+                       <channel id="vh_filtercheck" typeId="vh_filtercheck"/>
+                       <channel id="vh_diagnosticindication" typeId="vh_diagnosticindication"/>
+                       <channel id="vh_controlsetpoint" typeId="vh_controlsetpoint"/>
+                       <channel id="vh_servicerequest" typeId="vh_servicerequest"/>
+                       <channel id="vh_exhaustfanfault" typeId="vh_exhaustfanfault"/>
+                       <channel id="vh_inletfanfault" typeId="vh_inletfanfault"/>
+                       <channel id="vh_frostprotection" typeId="vh_frostprotection"/>
+                       <channel id="vh_faultcode" typeId="vh_faultcode"/>
+                       <channel id="vh_diagnosticcode" typeId="vh_diagnosticcode"/>
+                       <channel id="vh_systemtype" typeId="vh_systemtype"/>
+                       <channel id="vh_bypass" typeId="vh_bypass"/>
+                       <channel id="vh_speedcontrol" typeId="vh_speedcontrol"/>
+                       <channel id="vh_memberid" typeId="vh_memberid"/>
+                       <channel id="vh_openthermversion" typeId="vh_openthermversion"/>
+                       <channel id="vh_versiontype" typeId="vh_versiontype"/>
+                       <channel id="vh_relativeventilation" typeId="vh_relativeventilation"/>
+                       <channel id="vh_relativehumidity" typeId="vh_relativehumidity"/>
+                       <channel id="vh_co2level" typeId="vh_co2level"/>
+                       <channel id="vh_supplyinlettemp" typeId="vh_supplyinlettemp"/>
+                       <channel id="vh_supplyoutlettemp" typeId="vh_supplyoutlettemp"/>
+                       <channel id="vh_exhaustinlettemp" typeId="vh_exhaustinlettemp"/>
+                       <channel id="vh_exhaustoutlettemp" typeId="vh_exhaustoutlettemp"/>
+                       <channel id="vh_actualexhaustfanspeed" typeId="vh_actualexhaustfanspeed"/>
+                       <channel id="vh_actualinletfanspeed" typeId="vh_actualinletfanspeed"/>
+                       <channel id="vh_nominalventenable" typeId="vh_nominalventenable"/>
+                       <channel id="vh_nominalventrw" typeId="vh_nominalventrw"/>
+                       <channel id="vh_nominalventilationvalue" typeId="vh_nominalventilationvalue"/>
+                       <channel id="vh_ventilationsetpoint" typeId="vh_ventilationsetpoint"/>
+                       <channel id="vh_tspnumber" typeId="vh_tspnumber"/>
+                       <channel id="vh_tspentry" typeId="vh_tspentry"/>
+                       <channel id="vh_fhbnumber" typeId="vh_fhbnumber"/>
+                       <channel id="vh_fhbentry" typeId="vh_fhbentry"/>
+               </channels>
+       </thing-type>
+</thing:thing-descriptions>