[openthermgateway] Add support for Ventilation/Heat Recovery units (#12367)

* [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>
This commit is contained in:
Andrew Fiddian-Green 2022-03-06 20:18:02 +00:00 committed by GitHub
parent 5842ad1644
commit ba07a870fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2031 additions and 808 deletions

View File

@ -1,195 +1,443 @@
# OpenTherm Gateway Binding # OpenTherm Gateway Binding
This binding is used to integrate the OpenTherm Gateway into openHAB. 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/ 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 ## Discovery
The binding does not support auto discovery. The binding does not support auto discovery.
## Binding Configuration ## 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 ## 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. ### Thing Configuration for `openthermgateway`
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.
| Parameter | Name | Description | Required | Default | 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.
| `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 | | | Parameter | Name | Description | Required | Default |
| `connectionRetryInterval` | Connection Retry Interval | The interval in seconds to retry connecting (0 = disabled). | yes | 60 | |---------------------------|---------------------------|----------------------------------------------------------------------------------|----------|---------|
| `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 ## 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 | | 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 | | 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 ## Full Example
### demo.things ### demo.things
``` ```
Thing openthermgateway:otgw:1 [ ipaddress="192.168.1.100", port=8000, connectionRetryInterval=60 ] 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 ### demo.items for `openthermgateway`
``` ```
Number:Temperature RoomTemperature "Room temperature [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:roomtemp" } Text SendCommand "Send command channel" { channel="openthermgateway:openthermgateway:1:sendcommand" }
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" } ### demo.items for `boiler`
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 RoomTemperature "Room Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:roomtemp }
Number:Temperature ControlSetpoint2 "Control setpoint 2 [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2" } Number:Temperature RoomSetpoint "Room Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:roomsetpoint }
Number:Temperature ControlSetpoint2Requested "Control setpoint 2 requested [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2requested" } Number:Temperature TemporaryRoomSetpointOverride "Temporary Room Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:temperaturetemporary }
Number:Temperature ControlSetpoint2Override "Control setpoint 2 override [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:controlsetpoint2override" } Number:Temperature ConstantRoomSetpointOverride "Constant Room Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:temperatureconstant }
Number:Temperature DomesticHotWaterTemperature "Domestic hot water temperature [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:dhwtemp" } Number:Temperature ControlSetpoint "Control Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint }
Number:Temperature DomesticHotWaterSetpoint "Domestic hot water setpoint [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:tdhwset" } Number:Temperature ControlSetpointRequested "Control Setpoint Requested [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpointrequested }
Number:Temperature DomesticHotWaterSetpointOverride "Domestic hot water setpoint override [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:overridedhwsetpoint" } Number:Temperature ControlSetpointOverride "Control Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpointoverride }
Number:Temperature BoilerWaterTemperature "Boiler water temperature [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:flowtemp" } Number:Temperature ControlSetpoint2 "Control Setpoint 2 [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2 }
Number:Temperature ReturnWaterTemperature "Return water temperature [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:returntemp" } Number:Temperature ControlSetpoint2Requested "Control Setpoint 2 Requested [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2requested }
Number:Temperature OutsideTemperature "Outside temperature [%.1f °C]" <temperature> { channel="openthermgateway:otgw:1:outsidetemp" } Number:Temperature ControlSetpoint2Override "Control Setpoint 2 Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:controlsetpoint2override }
Number:Pressure CentralHeatingWaterPressure "Central heating water pressure [%.1f bar]" { channel="openthermgateway:otgw:1:waterpressure" } Number:Temperature DomesticHotWaterTemperature "Domestic Hot Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:dhwtemp }
Switch CentralHeatingEnabled "Central heating enabled" <switch> { channel="openthermgateway:otgw:1:ch_enable" } Number:Temperature DomesticHotWaterSetpoint "Domestic Hot Water Setpoint [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:tdhwset }
Switch CentralHeatingEnabledRequested "Central heating enabled requested" <switch> { channel="openthermgateway:otgw:1:ch_enablerequested" } Number:Temperature DomesticHotWaterSetpointOverride "Domestic Hot Water Setpoint Override [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:overridedhwsetpoint }
Switch CentralHeatingEnabledOverride "Central heating enabled override" <switch> { channel="openthermgateway:otgw:1:ch_enableoverride" } Number:Temperature BoilerWaterTemperature "Boiler Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:flowtemp }
Switch CentralHeating2Enabled "Central heating 2 enabled" <switch> { channel="openthermgateway:otgw:1:ch2_enable" } Number:Temperature ReturnWaterTemperature "Return Water Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:returntemp }
Switch CentralHeating2EnabledRequested "Central 2 heating enabled requested" <switch> { channel="openthermgateway:otgw:1:ch2_enablerequested" } Number:Temperature OutsideTemperature "Outside Temperature [%.1f %unit%]" <temperature> { channel="openthermgateway:boiler:1:remeha:outsidetemp }
Switch CentralHeating2EnabledOverride "Central 2 heating enabled override" <switch> { channel="openthermgateway:otgw:1:ch2_enableoverride" } Number:Dimensionless CentralHeatingWaterPressure "Central Heating Water Pressure [%.1f bar]" { channel="openthermgateway:boiler:1:remeha:waterpressure }
Switch CentralHeatingActive "Central heating active" <switch> { channel="openthermgateway:otgw:1:ch_mode" } Switch CentralHeatingEnabled "Central Heating Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enable }
Switch DomesticHotWaterEnabled "Domestic hot water enabled" <switch> { channel="openthermgateway:otgw:1:dhw_enable" } Switch CentralHeatingEnabledThermostat "Central Heating Enabled Thermostat" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enablerequested }
Switch DomesticHotWaterActive "Domestic hot water active" <switch> { channel="openthermgateway:otgw:1:dhw_mode" } Switch CentralHeatingOverridden "Central Heating Overridden" <switch> { channel="openthermgateway:boiler:1:remeha:ch_enableoverride }
Switch BurnerActive "Burner active" <switch> { channel="openthermgateway:otgw:1:flame" } Switch CentralHeating2Enabled "Central Heating 2 Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enable }
Number:Dimensionless RelativeModulationLevel "Relative modulation level [%.1f %%]" { channel="openthermgateway:otgw:1:modulevel" } Switch CentralHeating2EnabledThermostat "Central Heating 2 Enabled Thermostat" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enablerequested }
Number:Dimensionless MaximumRelativeModulationLevel "Maximum relative modulation level [%.1f %%]" { channel="openthermgateway:otgw:1:maxrelmdulevel" } Switch CentralHeating2Overridden "Central Heating 2 Overridden" <switch> { channel="openthermgateway:boiler:1:remeha:ch2_enableoverride }
Switch Fault "Fault indication" <switch> { channel="openthermgateway:otgw:1:fault" } Switch CentralHeatingActive "Central Heating Active" <switch> { channel="openthermgateway:boiler:1:remeha:ch_mode }
Switch ServiceRequest "Service required" <switch> { channel="openthermgateway:otgw:1:servicerequest" } Switch DomesticHotWaterEnabled "Domestic Hot Water Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:dhw_enable }
Switch LockoutReset "Lockout-reset" <switch> { channel="openthermgateway:otgw:1:lockout-reset" } Switch DomesticHotWaterActive "Domestic Hot Water Active" <switch> { channel="openthermgateway:boiler:1:remeha:dhw_mode }
Switch LowWaterPress "Low water pressure fault" <switch> { channel="openthermgateway:otgw:1:lowwaterpress" } Switch BurnerActive "Burner Active" <switch> { channel="openthermgateway:boiler:1:remeha:flame }
Switch GasFlameFault "Gas or flame fault" <switch> { channel="openthermgateway:otgw:1:gasflamefault" } Number:Dimensionless RelativeModulationLevel "Relative Modulation Level [%.1f %%]" { channel="openthermgateway:boiler:1:remeha:modulevel }
Switch AirPressFault "Air pressure fault" <switch> { channel="openthermgateway:otgw:1:airpressfault" } Number:Dimensionless MaximumRelativeModulationLevel "Maximum Relative Modulation Level [%.1f %%]" { channel="openthermgateway:boiler:1:remeha:maxrelmdulevel }
Switch WaterOvTemp "Water over-temperature fault" <switch> { channel="openthermgateway:otgw:1:waterovtemp" } Switch FaultIndication "Fault Indication" <switch> { channel="openthermgateway:boiler:1:remeha:fault }
Number OemFaultCode "OEM fault code" { channel="openthermgateway:otgw:1:oemfaultcode" } Switch ServiceRequired "Service Required" <switch> { channel="openthermgateway:boiler:1:remeha:servicerequest }
Switch Diagnostics "Diagnostics indication" { channel="openthermgateway:otgw:1:diag" } Switch LockoutResetEnabled "Lockout-Reset Enabled" <switch> { channel="openthermgateway:boiler:1:remeha:lockout-reset }
Number:Dimensionless UnsuccessfulBurnerStarts "Unsuccessful burner starts" { channel="openthermgateway:otgw:1:unsuccessfulburnerstarts" } Switch LowWaterPressureFault "Low Water Pressure Fault" <switch> { channel="openthermgateway:boiler:1:remeha:lowwaterpress }
Number:Dimensionless BurnerStarts "Burner starts" { channel="openthermgateway:otgw:1:burnerstarts" } Switch GasOrFlameFault "Gas Or Flame Fault" <switch> { channel="openthermgateway:boiler:1:remeha:gasflamefault }
Number:Dimensionless CentralHeatingPumpStarts "Central heating pump starts" { channel="openthermgateway:otgw:1:chpumpstarts" } Switch AirPressureFault "Air Pressure Fault" <switch> { channel="openthermgateway:boiler:1:remeha:airpressfault }
Number:Dimensionless DomesticHotWaterPumpValveStarts "Domestic hot water pump/valve starts" { channel="openthermgateway:otgw:1:dhwpvstarts" } Switch WaterOverTemperatureFault "Water Over-Temperature Fault" <switch> { channel="openthermgateway:boiler:1:remeha:waterovtemp }
Number:Dimensionless DomesticHotWaterBurnerStarts "Domestic hot water burner starts" { channel="openthermgateway:otgw:1:dhwburnerstarts" } Number:Dimensionless OEMFaultCode "OEM Fault Code" { channel="openthermgateway:boiler:1:remeha:oemfaultcode }
Number:Time BurnerHours "Burner hours" { channel="openthermgateway:otgw:1:burnerhours" } Number:Dimensionless UnsuccessfulBurnerStarts "Unsuccessful Burner Starts" { channel="openthermgateway:boiler:1:remeha:unsuccessfulburnerstarts }
Number:Time CentralHeatingPumpHours "Central heating pump hours" { channel="openthermgateway:otgw:1:chpumphours" } Number:Dimensionless BurnerStarts "Burner Starts" { channel="openthermgateway:boiler:1:remeha:burnerstarts }
Number:Time DomesticHotWaterPumpValveHours "Domestic hot water pump/valve hours" { channel="openthermgateway:otgw:1:dhwpvhours" } Number:Dimensionless CentralHeatingPumpStarts "Central Heating Pump Starts" { channel="openthermgateway:boiler:1:remeha:chpumpstarts }
Number:Time DomesticHotWaterBurnerHours "Domestic hot water burner hours" { channel="openthermgateway:otgw:1:dhwburnerhours" } Number:Dimensionless DomesticHotWaterPump/ValveStarts "Domestic Hot Water Pump/Valve Starts" { channel="openthermgateway:boiler:1:remeha:dhwpvstarts }
Text SendCommand "Send command channel" { channel="openthermgateway:otgw:1:sendcommand" } 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 ### demo.sitemap
``` ```
sitemap demo label="Main Menu" { sitemap demo label="Main Menu" {
Frame label="OpenTherm Gateway" { Frame label="Boiler" {
Text item="RoomTemperature" icon="temperature" label="Room temperature [%.1f °C]" Text item="RoomTemperature" icon="temperature" label="Room Temperature [%.1f %unit%]"
Text item="RoomSetpoint" icon="temperature" label="Room setpoint [%.1f °C]" Text item="RoomSetpoint" icon="temperature" label="Room Setpoint [%.1f %unit%]"
Setpoint item="TemporaryRoomSetpointOverride" icon="temperature" label="Temporary room setpoint override [%.1f °C]" minValue="0" maxValue="30" step="0.1" Text item="TemporaryRoomSetpointOverride" icon="temperature" label="Temporary Room Setpoint Override [%.1f %unit%]" 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="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 °C]" Text item="ControlSetpoint" icon="temperature" label="Control Setpoint [%.1f %unit%]"
Text item="ControlSetpointRequested" icon="temperature" label="Control setpoint requested [%.1f °C]" Text item="ControlSetpointRequested" icon="temperature" label="Control Setpoint Requested [%.1f %unit%]"
Setpoint item="ControlSetpointOverride" icon="temperature" label="Control setpoint override [%.1f °C]" minValue="0" maxValue="100" step="1" Text item="ControlSetpointOverride" icon="temperature" label="Control Setpoint Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
Text item="DomesticHotWaterTemperature" icon="temperature" label="Domestic hot water temperature [%.1f °C]" Text item="ControlSetpoint2" icon="temperature" label="Control Setpoint 2 [%.1f %unit%]"
Text item="DomesticHotWaterSetpoint" icon="temperature" label="Domestic hot water setpoint [%.1f °C]" Text item="ControlSetpoint2Requested" icon="temperature" label="Control Setpoint 2 Requested [%.1f %unit%]"
Setpoint item="DomesticHotWaterSetpointOverride" icon="temperature" label="Domestic hot water setpoint override [%.1f °C]" minValue="0" maxValue="100" step="0.1" Text item="ControlSetpoint2Override" icon="temperature" label="Control Setpoint 2 Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
Text item="BoilerWaterTemperature" icon="temperature" label="Boiler water temperature [%.1f °C]" Text item="DomesticHotWaterTemperature" icon="temperature" label="Domestic Hot Water Temperature [%.1f %unit%]"
Text item="ReturnWaterTemperature" icon="temperature" label="Return water temperature [%.1f °C]" Text item="DomesticHotWaterSetpoint" icon="temperature" label="Domestic Hot Water Setpoint [%.1f %unit%]"
Setpoint item="OutsideTemperature" icon="temperature" label="Outside temperature [%.1f °C]" minValue="-40" maxValue="100" step="0.1" Text item="DomesticHotWaterSetpointOverride" icon="temperature" label="Domestic Hot Water Setpoint Override [%.1f %unit%]" minValue="0" maxValue="100" step="0.1"
Text item="CentralHeatingWaterPressure" icon="" label="Central heating water pressure [%.1f bar]" Text item="BoilerWaterTemperature" icon="temperature" label="Boiler Water Temperature [%.1f %unit%]"
Switch item="CentralHeatingEnabled" icon="switch" label="Central heating enabled" Text item="ReturnWaterTemperature" icon="temperature" label="Return Water Temperature [%.1f %unit%]"
Switch item="CentralHeatingEnabledRequested" icon="switch" label="Central heating enabled requested" Text item="OutsideTemperature" icon="temperature" label="Outside Temperature [%.1f %unit%]" minValue="-40" maxValue="100" step="0.1"
Switch item="CentralHeatingEnabledOverride" icon="switch" label="Central heating enabled override" Text item="CentralHeatingWaterPressure" label="Central Heating Water Pressure [%.1f bar]"
Switch item="CentralHeatingActive" icon="switch" label="Central heating active" Switch item="CentralHeatingEnabled" icon="switch" label="Central Heating Enabled"
Switch item="DomesticHotWaterEnabled" icon="switch" label="Domestic hot water enabled" Switch item="CentralHeatingEnabledThermostat" icon="switch" label="Central Heating Enabled Thermostat"
Switch item="DomesticHotWaterActive" icon="switch" label="Domestic hot water active" Switch item="CentralHeatingOverridden" icon="switch" label="Central Heating Overridden"
Switch item="BurnerActive" icon="switch" label="Burner active" Switch item="CentralHeating2Enabled" icon="switch" label="Central Heating 2 Enabled"
Text item="RelativeModulationLevel" icon="" label="Relative modulation level [%.1f %%]" Switch item="CentralHeating2EnabledThermostat" icon="switch" label="Central Heating 2 Enabled Thermostat"
Text item="MaximumRelativeModulationLevel" icon="" label="Maximum relative modulation level [%.1f %%]" Switch item="CentralHeating2Overridden" icon="switch" label="Central Heating 2 Overridden"
Switch item="Fault" icon="" label="Fault indication" Switch item="CentralHeatingActive" icon="switch" label="Central Heating Active"
Switch item="ServiceRequest" icon="" label="Service required" Switch item="DomesticHotWaterEnabled" icon="switch" label="Domestic Hot Water Enabled"
Switch item="LockoutReset" icon="" label="Lockout-reset" Switch item="DomesticHotWaterActive" icon="switch" label="Domestic Hot Water Active"
Switch item="LowWaterPress" icon="" label="Low water pressure fault" Switch item="BurnerActive" icon="switch" label="Burner Active"
Switch item="GasFlameFault" icon="" label="Gas or flame fault" Text item="RelativeModulationLevel" label="Relative Modulation Level [%.1f %%]"
Switch item="AirPressFault" icon="" label="Air pressure fault" Text item="MaximumRelativeModulationLevel" label="Maximum Relative Modulation Level [%.1f %%]"
Switch item="waterOvTemp" icon="" label="Water over-temperature fault" Switch item="FaultIndication" icon="switch" label="Fault Indication"
Text item="OemFaultCode" icon="" label="OEM fault code" Switch item="ServiceRequired" icon="switch" label="Service Required"
Switch item="Diagnostics" icon="" label="Diagnostics indication" Switch item="Lockout-ResetEnabled" icon="switch" label="Lockout-Reset Enabled"
Text item="UnsuccessfulBurnerStarts" icon="" label="Unsuccessful burner starts" Switch item="LowWaterPressureFault" icon="switch" label="Low Water Pressure Fault"
Text item="BurnerStarts" icon="" label="Burner starts" Switch item="GasOrFlameFault" icon="switch" label="Gas Or Flame Fault"
Text item="CentralHeatingPumpStarts" icon="" label="Central heating pump starts" Switch item="AirPressureFault" icon="switch" label="Air Pressure Fault"
Text item="DomesticHotWaterPumpValveStarts" icon="" label="Domestic hot water pump/valve starts" Switch item="WaterOver-TemperatureFault" icon="switch" label="Water Over-Temperature Fault"
Text item="DomesticHotWaterBurnerStarts" icon="" label="Domestic hot water burner starts" Text item="OEMFaultCode" label="OEM Fault Code"
Text item="BurnerHours" icon="" label="Burner hours" Text item="UnsuccessfulBurnerStarts" label="Unsuccessful Burner Starts"
Text item="CentralHeatingPumpHours" icon="" label="Central heating pump hours" Text item="BurnerStarts" label="Burner Starts"
Text item="DomesticHotWaterPumpValveHours" icon="" label="Domestic hot water pump/valve hours" Text item="CentralHeatingPumpStarts" label="Central Heating Pump Starts"
Text item="DomesticHotWaterBurnerHours" icon="" label="Domestic hot water burner hours" 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.
``` ```

View File

@ -14,4 +14,5 @@
<name>openHAB Add-ons :: Bundles :: OpenTherm Gateway Binding</name> <name>openHAB Add-ons :: Bundles :: OpenTherm Gateway Binding</name>
<description>This binding integrates OpenTherm capable devices via an OpenThermGateway bridge.</description>
</project> </project>

View File

@ -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);
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -15,32 +15,29 @@ package org.openhab.binding.openthermgateway.handler;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openthermgateway.OpenThermGatewayBindingConstants; import org.openhab.binding.openthermgateway.internal.ConnectionState;
import org.openhab.binding.openthermgateway.internal.DataItem;
import org.openhab.binding.openthermgateway.internal.DataItemGroup; import org.openhab.binding.openthermgateway.internal.DataItemGroup;
import org.openhab.binding.openthermgateway.internal.GatewayCommand; import org.openhab.binding.openthermgateway.internal.GatewayCommand;
import org.openhab.binding.openthermgateway.internal.GatewayCommandCode; import org.openhab.binding.openthermgateway.internal.GatewayCommandCode;
import org.openhab.binding.openthermgateway.internal.Message; 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.OpenThermGatewayCallback;
import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConfiguration; import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConfiguration;
import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConnector; import org.openhab.binding.openthermgateway.internal.OpenThermGatewayConnector;
import org.openhab.binding.openthermgateway.internal.OpenThermGatewaySocketConnector; 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.OnOffType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; 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.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -52,38 +49,37 @@ import org.slf4j.LoggerFactory;
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThermGatewayCallback { public class OpenThermGatewayHandler extends BaseBridgeHandler implements OpenThermGatewayCallback {
private final Logger logger = LoggerFactory.getLogger(OpenThermGatewayHandler.class); private final Logger logger = LoggerFactory.getLogger(OpenThermGatewayHandler.class);
private @Nullable OpenThermGatewayConfiguration config; private @Nullable OpenThermGatewayConfiguration configuration;
private @Nullable OpenThermGatewayConnector connector; private @Nullable OpenThermGatewayConnector connector;
private @Nullable ScheduledFuture<?> reconnectTask; private @Nullable ScheduledFuture<?> reconnectTask;
private boolean connecting = false; private @Nullable ConnectionState state;
private boolean explicitDisconnect = false; private boolean autoReconnect = true;
private boolean disposing = false;
public OpenThermGatewayHandler(Thing thing) { public OpenThermGatewayHandler(Bridge bridge) {
super(thing); super(bridge);
} }
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing OpenTherm Gateway handler for uid '{}'", getThing().getUID()); logger.debug("Initializing OpenThermGateway handler for uid {}", getThing().getUID());
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Initializing"); configuration = getConfigAs(OpenThermGatewayConfiguration.class);
logger.debug("Using configuration: {}", configuration);
config = getConfigAs(OpenThermGatewayConfiguration.class);
disposing = false;
updateStatus(ThingStatus.UNKNOWN);
connect(); connect();
} }
@Override @Override
public void handleCommand(ChannelUID channelUID, Command command) { public void handleCommand(ChannelUID channelUID, Command command) {
@Nullable logger.debug("Received command {} for channel {}", command, channelUID);
OpenThermGatewayConnector conn = connector;
logger.debug("Received channel: {}, command: {}", channelUID, command);
if (!(command instanceof RefreshType)) { if (!(command instanceof RefreshType)) {
String channel = channelUID.getId(); String channel = channelUID.getId();
@ -108,146 +104,121 @@ public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThe
gatewayCommand = GatewayCommand.parse(code, command.toFullString()); gatewayCommand = GatewayCommand.parse(code, command.toFullString());
} }
if (conn != null && conn.isConnected()) { sendCommand(gatewayCommand);
conn.sendCommand(gatewayCommand);
if (GatewayCommandCode.ControlSetpoint.equals(code)) { if (GatewayCommandCode.CONTROLSETPOINT.equals(code)) {
if (gatewayCommand.getMessage().equals("0.0")) { if (gatewayCommand.getMessage().equals("0.0")) {
updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT, updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT,
UnDefType.UNDEF); 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")));
} }
} else { updateState(OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED,
connect(); 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 sendCommand(GatewayCommand gatewayCommand) {
public void connecting() {
connecting = true;
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Connecting");
}
@Override
public void connected() {
connecting = false;
updateStatus(ThingStatus.ONLINE);
}
@Override
public void disconnected() {
@Nullable @Nullable
OpenThermGatewayConfiguration conf = config; OpenThermGatewayConnector conn = connector;
connecting = false; if (conn != null && conn.isConnected()) {
conn.sendCommand(gatewayCommand);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Disconnected"); } else {
logger.debug("Unable to send command {}: connector not connected", gatewayCommand.toFullString());
// 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);
} }
} }
@Override @Override
public void receiveMessage(Message message) { public void receiveMessage(Message message) {
if (DataItemGroup.dataItemGroups.containsKey(message.getID())) { scheduler.submit(() -> receiveMessageTask(message));
DataItem[] dataItems = DataItemGroup.dataItemGroups.get(message.getID()); }
for (DataItem dataItem : dataItems) { private void receiveMessageTask(Message message) {
String channelId = dataItem.getSubject(); int msgId = message.getID();
if (!OpenThermGatewayBindingConstants.SUPPORTED_CHANNEL_IDS.contains(channelId) if (!DataItemGroup.DATAITEMGROUPS.containsKey(msgId)) {
|| (dataItem.getFilteredCode() != null && dataItem.getFilteredCode() != message.getCode())) { logger.debug("Unsupported message id {}", msgId);
continue; return;
} }
State state = null; for (Thing thing : getThing().getThings()) {
BaseDeviceHandler handler = (BaseDeviceHandler) thing.getHandler();
switch (dataItem.getDataType()) { if (handler != null) {
case FLAGS: handler.receiveMessage(message);
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;
}
if (state != null) { @Override
logger.debug("Received update for channel '{}': {}", channelId, state); public void connectionStateChanged(ConnectionState state) {
updateState(channelId, state); scheduler.submit(() -> connectionStateChangedTask(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 @Override
public void handleRemoval() { public void handleRemoval() {
logger.debug("Removing OpenTherm Gateway handler"); logger.debug("Removing OpenThermGateway handler");
disconnect(); disconnect();
super.handleRemoval(); super.handleRemoval();
} }
@Override @Override
public void dispose() { public void dispose() {
logger.debug("Disposing OpenThermGateway handler");
disposing = true;
disconnect(); disconnect();
ScheduledFuture<?> localReconnectTask = reconnectTask;
if (localReconnectTask != null) {
localReconnectTask.cancel(true);
reconnectTask = null;
}
super.dispose(); super.dispose();
} }
private void connect() { private void connect() {
@Nullable @Nullable
OpenThermGatewayConfiguration conf = config; OpenThermGatewayConfiguration config = configuration;
explicitDisconnect = false; if (this.state == ConnectionState.CONNECTING) {
logger.debug("OpenThermGateway connector is already connecting");
if (connecting) {
logger.debug("OpenTherm Gateway connector is already connecting ...");
return; return;
} }
// Make sure everything is cleaned up before creating a new connection
disconnect(); disconnect();
if (conf != null) { if (config != null) {
logger.debug("Starting OpenTherm Gateway connector"); connectionStateChanged(ConnectionState.INITIALIZING);
connector = new OpenThermGatewaySocketConnector(this, conf.ipaddress, conf.port); logger.debug("Starting OpenThermGateway connector");
Thread thread = new Thread(connector, "OpenTherm Gateway Binding - socket listener thread"); autoReconnect = true;
thread.setDaemon(true);
thread.start();
logger.debug("OpenTherm Gateway connector started"); OpenThermGatewayConnector conn = connector = new OpenThermGatewaySocketConnector(this, config);
conn.start();
logger.debug("OpenThermGateway connector started");
} }
} }
@ -255,36 +226,59 @@ public class OpenThermGatewayHandler extends BaseThingHandler implements OpenThe
@Nullable @Nullable
OpenThermGatewayConnector conn = connector; OpenThermGatewayConnector conn = connector;
explicitDisconnect = true; autoReconnect = false;
cancelAutoReconnect();
if (conn != null) { if (conn != null) {
if (conn.isConnected()) { conn.stop();
logger.debug("Stopping OpenTherm Gateway connector"); connector = null;
conn.stop(); }
}
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 { private @Nullable String getGatewayCodeFromChannel(String channel) throws IllegalArgumentException {
switch (channel) { switch (channel) {
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_TEMPORARY:
return GatewayCommandCode.TemperatureTemporary; return GatewayCommandCode.TEMPERATURETEMPORARY;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_SETPOINT_CONSTANT:
return GatewayCommandCode.TemperatureConstant; return GatewayCommandCode.TEMPERATURECONSTANT;
case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE: case OpenThermGatewayBindingConstants.CHANNEL_OUTSIDE_TEMPERATURE:
return GatewayCommandCode.TemperatureOutside; return GatewayCommandCode.TEMPERATUREOUTSIDE;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_DHW_SETPOINT:
return GatewayCommandCode.SetpointWater; return GatewayCommandCode.SETPOINTWATER;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_WATER_SETPOINT:
return GatewayCommandCode.ControlSetpoint; return GatewayCommandCode.CONTROLSETPOINT;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING_ENABLED:
return GatewayCommandCode.CentralHeating; return GatewayCommandCode.CENTRALHEATING;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT: case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_WATER_SETPOINT:
return GatewayCommandCode.ControlSetpoint2; return GatewayCommandCode.CONTROLSETPOINT2;
case OpenThermGatewayBindingConstants.CHANNEL_OVERRIDE_CENTRAL_HEATING2_ENABLED: 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: case OpenThermGatewayBindingConstants.CHANNEL_SEND_COMMAND:
return null; return null;
default: default:

View File

@ -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);
}
}

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.openthermgateway.internal; 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 * The {@link ByteType} enum specifies whether the upper, lower or both bytes are used
* *
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault
public enum ByteType { public enum ByteType {
HIGHBYTE, HIGHBYTE,
LOWBYTE, LOWBYTE,

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.openthermgateway.internal; 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. * The {@link CodeType} field is not part of OpenTherm specification, but added by OpenTherm Gateway.
* It can be any of the following: * It can be any of the following:
@ -25,6 +27,7 @@ package org.openhab.binding.openthermgateway.internal;
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
* @author James Melville - Introduced code filtering functionality * @author James Melville - Introduced code filtering functionality
*/ */
@NonNullByDefault
public enum CodeType { public enum CodeType {
/** /**
* Message received from the thermostat * Message received from the thermostat

View File

@ -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
}

View File

@ -12,81 +12,62 @@
*/ */
package org.openhab.binding.openthermgateway.internal; package org.openhab.binding.openthermgateway.internal;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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 * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class DataItem { public abstract class DataItem {
private int id;
private Msg msg; private Msg msg;
private ByteType byteType; private ByteType byteType;
private DataType dataType;
private int bitpos;
private String subject; private String subject;
private @Nullable Unit<?> unit; private @Nullable CodeType codeType;
private @Nullable CodeType filteredCode;
public int getID() {
return id;
}
public Msg getMsg() { public Msg getMsg() {
return msg; return msg;
} }
public ByteType getByteType() { public ByteType getByteType() {
return this.byteType; return byteType;
}
public DataType getDataType() {
return dataType;
}
public int getBitPos() {
return bitpos;
} }
public String getSubject() { public String getSubject() {
return subject; return subject;
} }
public @Nullable Unit<?> getUnit() { public @Nullable CodeType getCodeType() {
return unit; return codeType;
} }
public @Nullable CodeType getFilteredCode() { public DataItem(Msg msg, ByteType byteType, String subject, @Nullable CodeType codeType) {
return filteredCode;
}
public DataItem(int id, Msg msg, ByteType byteType, DataType dataType, int bit, String subject) {
this(id, msg, byteType, dataType, bit, subject, null, null);
}
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);
}
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);
}
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.msg = msg;
this.byteType = byteType; this.byteType = byteType;
this.dataType = dataType;
this.bitpos = bit;
this.subject = subject; this.subject = subject;
this.unit = unit; this.codeType = codeType;
this.filteredCode = filteredCode;
} }
public boolean hasValidCodeType(Message message) {
// Used to bind a dataitem to a specific TBRA code
@Nullable
CodeType code = this.getCodeType();
return (code == null || code == message.getCodeType());
}
/**
* @param message unused in this default implementation
* @return the channel id
*/
public String getChannelId(Message message) {
// Default implementation
return subject;
}
public abstract State createState(Message message);
} }

View File

@ -27,177 +27,206 @@ import org.openhab.core.library.unit.Units;
@NonNullByDefault @NonNullByDefault
public class DataItemGroup { 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() { private static Map<Integer, DataItem[]> createDataItemGroups() {
HashMap<Integer, DataItem[]> g = new HashMap<>(); HashMap<Integer, DataItem[]> g = new HashMap<>();
g.put(0, new DataItem[] { g.put(0, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enable", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enable", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enablerequested", CodeType.T),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enablerequested", CodeType.T), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "ch_enableoverride", CodeType.R),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "ch_enableoverride", CodeType.R), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "dhw_enable", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "dhw_enable", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "cooling_enabled", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "cooling_enabled", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "otc_active", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "otc_active", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enable", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enable", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enablerequested", CodeType.T),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enablerequested", CodeType.T), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "ch2_enableoverride", CodeType.R),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "ch2_enableoverride", CodeType.R), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "0x00:5", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x00:5", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x00:6", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x00:6", CodeType.B), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x00:7", CodeType.B),
new DataItem(0, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x00:7", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "fault", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 0, "fault", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "ch_mode", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 1, "ch_mode", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "dhw_mode", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 2, "dhw_mode", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "flame", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 3, "flame", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "cooling", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 4, "cooling", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "ch2E", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 5, "ch2E", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "diag", CodeType.B),
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 6, "diag", CodeType.B), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 7, "0x00:7", CodeType.B) });
new DataItem(0, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 7, "0x00:7", CodeType.B) }); g.put(1, new DataItem[] { new FloatDataItem(Msg.WRITE, "controlsetpoint", SIUnits.CELSIUS, CodeType.B),
g.put(1, new DataItem[] { new FloatDataItem(Msg.WRITE, "controlsetpointrequested", SIUnits.CELSIUS, CodeType.T),
new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint", SIUnits.CELSIUS, new FloatDataItem(Msg.WRITE, "controlsetpointoverride", SIUnits.CELSIUS, CodeType.R) });
CodeType.B), g.put(2, new DataItem[] { new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 0, "0x02:0"),
new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpointrequested", new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 1, "0x02:1"),
SIUnits.CELSIUS, CodeType.T), new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 2, "0x02:2"),
new DataItem(1, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpointoverride", SIUnits.CELSIUS, new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 3, "0x02:3"),
CodeType.R) }); new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 4, "0x02:4"),
g.put(2, new DataItem[] { new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 0, "0x02:0"), new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 5, "0x02:5"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 1, "0x02:1"), new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 6, "0x02:6"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 2, "0x02:2"), new FlagDataItem(Msg.WRITE, ByteType.HIGHBYTE, 7, "0x02:7"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 3, "0x02:3"), new UIntDataItem(Msg.WRITE, ByteType.LOWBYTE, "mastermemberid") });
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 4, "0x02:4"), g.put(3, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "dhwpresent"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x02:5"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "controltype"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x02:6"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "coolingsupport"),
new DataItem(2, Msg.WRITE, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x02:7"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "dhwconfig"),
new DataItem(2, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "mastermemberid") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "masterlowoff"),
g.put(3, new DataItem[] { new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "dhwpresent"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "ch2present"),
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "controltype"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x03:6"),
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "coolingsupport"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x03:7"),
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "dhwconfig"), new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "slavememberid") });
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "masterlowoff"), g.put(4, new DataItem[] { new UIntDataItem(Msg.WRITE, ByteType.HIGHBYTE, "commandcode"),
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "ch2present"), new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "commandresponse") });
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x03:6"), g.put(5, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "servicerequest"),
new DataItem(3, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x03:7"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "lockout-reset"),
new DataItem(3, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "slavememberid") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "lowwaterpress"),
g.put(4, new DataItem[] { new DataItem(4, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "commandcode"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "gasflamefault"),
new DataItem(4, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "commandresponse") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "airpressfault"),
g.put(5, new DataItem[] { new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "servicerequest"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "waterovtemp"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "lockout-reset"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x05:6"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "lowwaterpress"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x05:7"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "gasflamefault"), new IntDataItem(Msg.READ, ByteType.LOWBYTE, "oemfaultcode") });
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "airpressfault"), g.put(6, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "0x06:h0"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "waterovtemp"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "0x06:h1"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x05:6"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "0x06:h2"),
new DataItem(5, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x05:7"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "0x06:h3"),
new DataItem(5, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "oemfaultcode") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "0x06:h4"),
g.put(6, new DataItem[] { new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 0, "0x06:l0"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "0x06:h5"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 1, "0x06:l1"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "0x06:h6"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 2, "0x06:l2"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "0x06:h7"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 3, "0x06:l3"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "0x06:l0"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 4, "0x06:l4"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "0x06:l1"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 5, "0x06:l5"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "0x06:l2"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 6, "0x06:l6"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "0x06:l3"),
new DataItem(6, Msg.READ, ByteType.LOWBYTE, DataType.FLAGS, 7, "0x06:l7"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "0x06:l4"),
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "0x06:h0"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "0x06:l5"),
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "0x06:h1"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "0x06:l6"),
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "0x06:h2"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 7, "0x06:l7") });
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "0x06:h3"), g.put(7, new DataItem[] { new FloatDataItem(Msg.WRITE, "coolingcontrolsignal") });
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "0x06:h4"), g.put(8, new DataItem[] { new FloatDataItem(Msg.WRITE, "controlsetpoint2", SIUnits.CELSIUS, CodeType.B),
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "0x06:h5"), new FloatDataItem(Msg.WRITE, "controlsetpoint2requested", SIUnits.CELSIUS, CodeType.T),
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "0x06:h6"), new FloatDataItem(Msg.WRITE, "controlsetpoint2override", SIUnits.CELSIUS, CodeType.R) });
new DataItem(6, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "0x06:h7") }); g.put(9, new DataItem[] { new FloatDataItem(Msg.READ, "overridesetpoint") });
g.put(7, new DataItem[] { new DataItem(7, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "0x07") }); g.put(10, new DataItem[] { new TspFhbSizeDataItem(Msg.WRITE, ByteType.HIGHBYTE, 11, "tspnumber") });
g.put(8, new DataItem[] { g.put(11, new DataItem[] { new TspFhbValueDataItem(Msg.READWRITE, "tspentry") });
new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2", SIUnits.CELSIUS, g.put(12, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 13, "fhbnumber") });
CodeType.B), g.put(13, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "fhbentry") });
new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2requested", g.put(14, new DataItem[] { new FloatDataItem(Msg.READ, "maxrelmdulevel", Units.PERCENT) });
SIUnits.CELSIUS, CodeType.T), g.put(15, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.HIGHBYTE, "maxcapkw"),
new DataItem(8, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "controlsetpoint2override", new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "maxcapprc") });
SIUnits.CELSIUS, CodeType.R) }); g.put(16, new DataItem[] { new FloatDataItem(Msg.WRITE, "roomsetpoint", SIUnits.CELSIUS) });
g.put(9, new DataItem[] { new DataItem(9, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "overridesetpoint") }); g.put(17, new DataItem[] { new FloatDataItem(Msg.READ, "modulevel", Units.PERCENT) });
g.put(10, new DataItem[] { new DataItem(10, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0a:h"), g.put(18, new DataItem[] { new FloatDataItem(Msg.READ, "waterpressure", Units.BAR) });
new DataItem(10, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0a:l") }); g.put(19, new DataItem[] { new FloatDataItem(Msg.READ, "dhwflow") });
g.put(11, new DataItem[] { new DataItem(11, Msg.READWRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "tspindex"), g.put(20, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.BOTH, "dowtod") });
new DataItem(11, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "tspvalue") }); g.put(21, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.HIGHBYTE, "month"),
g.put(12, new DataItem[] { new DataItem(12, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0c:h"), new UIntDataItem(Msg.READWRITE, ByteType.LOWBYTE, "day") });
new DataItem(12, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0c:l") }); g.put(22, new DataItem[] { new UIntDataItem(Msg.READWRITE, ByteType.BOTH, "year") });
g.put(13, new DataItem[] { new DataItem(13, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "0x0d:h"), g.put(23, new DataItem[] { new FloatDataItem(Msg.WRITE, "setpointch2") });
new DataItem(13, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "0x0d:l") }); g.put(24, new DataItem[] { new FloatDataItem(Msg.WRITE, "roomtemp", SIUnits.CELSIUS) });
g.put(14, new DataItem[] { g.put(25, new DataItem[] { new FloatDataItem(Msg.READ, "flowtemp", SIUnits.CELSIUS) });
new DataItem(14, Msg.READ, ByteType.LOWBYTE, DataType.FLOAT, 0, "maxrelmdulevel", Units.PERCENT) }); g.put(26, new DataItem[] { new FloatDataItem(Msg.READ, "dhwtemp", SIUnits.CELSIUS) });
g.put(15, new DataItem[] { new DataItem(15, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "maxcapkw"), g.put(27, new DataItem[] { new FloatDataItem(Msg.READ, "outsidetemp", SIUnits.CELSIUS) });
new DataItem(15, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "maxcapprc") }); g.put(28, new DataItem[] { new FloatDataItem(Msg.READ, "returntemp", SIUnits.CELSIUS) });
g.put(16, new DataItem[] {
new DataItem(16, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "roomsetpoint", SIUnits.CELSIUS) }); g.put(29, new DataItem[] { new FloatDataItem(Msg.READ, "ss_temperature") });
g.put(17, new DataItem[] { g.put(30, new DataItem[] { new FloatDataItem(Msg.READ, "ss_collectortemperature") });
new DataItem(17, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "modulevel", Units.PERCENT) });
g.put(18, new DataItem[] { g.put(31, new DataItem[] { new FloatDataItem(Msg.READ, "flowtemp2") });
new DataItem(18, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "waterpressure", Units.BAR) }); g.put(32, new DataItem[] { new FloatDataItem(Msg.READ, "dhw2temp") });
g.put(19, new DataItem[] { new DataItem(19, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhwflow") }); g.put(33, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "exhausttemp") });
g.put(20, new DataItem[] { new DataItem(20, Msg.READWRITE, ByteType.BOTH, DataType.DOWTOD, 0, "dowtod") }); g.put(48, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "tdhwsetu"),
g.put(21, new DataItem[] { new DataItem(21, Msg.READWRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "month"), new IntDataItem(Msg.READ, ByteType.LOWBYTE, "tdhwsetl") });
new DataItem(21, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "dom") }); g.put(49, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "maxchu"),
g.put(22, new DataItem[] { new DataItem(22, Msg.READWRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "year") }); new IntDataItem(Msg.READ, ByteType.LOWBYTE, "maxchl") });
g.put(23, new DataItem[] { new DataItem(23, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "setpointch2") }); g.put(50, new DataItem[] { new IntDataItem(Msg.READ, ByteType.HIGHBYTE, "otcu"),
g.put(24, new DataItem[] { new IntDataItem(Msg.READ, ByteType.LOWBYTE, "otcl") });
new DataItem(24, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "roomtemp", SIUnits.CELSIUS) }); g.put(56, new DataItem[] { new FloatDataItem(Msg.READWRITE, "tdhwset", SIUnits.CELSIUS) });
g.put(25, new DataItem[] { g.put(57, new DataItem[] { new FloatDataItem(Msg.READWRITE, "tchmax") });
new DataItem(25, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "flowtemp", SIUnits.CELSIUS) }); g.put(58, new DataItem[] { new FloatDataItem(Msg.READWRITE, "otchcratio") });
g.put(26, new DataItem[] { g.put(70,
new DataItem(26, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhwtemp", SIUnits.CELSIUS) }); new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_ventilationenable"),
g.put(27, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "vh_bypassposition"),
new DataItem(27, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "outsidetemp", SIUnits.CELSIUS) }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "vh_bypassmode"),
g.put(28, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "vh_freeventilationmode"),
new DataItem(28, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "returntemp", SIUnits.CELSIUS) }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 0, "vh_faultindication"),
g.put(29, new DataItem[] { new DataItem(29, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "solstortemp") }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 1, "vh_ventilationmode"),
g.put(30, new DataItem[] { new DataItem(30, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "solcolltemp") }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 2, "vh_bypassstatus"),
g.put(31, new DataItem[] { new DataItem(31, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "flowtemp2") }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 3, "vh_bypassautomaticstatus"),
g.put(32, new DataItem[] { new DataItem(32, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "dhw2temp") }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 4, "vh_freeventilationstatus"),
g.put(33, new DataItem[] { new DataItem(33, Msg.READ, ByteType.BOTH, DataType.INT16, 0, "exhausttemp") }); new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 5, "vh_filtercheck"),
g.put(48, new DataItem[] { new DataItem(48, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "tdhwsetu"), new FlagDataItem(Msg.READ, ByteType.LOWBYTE, 6, "vh_diagnosticindication") });
new DataItem(48, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "tdhwsetl") }); g.put(71, new DataItem[] { new UIntDataItem(Msg.WRITE, ByteType.LOWBYTE, "vh_controlsetpoint") });
g.put(49, new DataItem[] { new DataItem(49, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "maxchu"), g.put(72,
new DataItem(49, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "maxchl") }); new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "vh_servicerequest"),
g.put(50, new DataItem[] { new DataItem(50, Msg.READ, ByteType.HIGHBYTE, DataType.INT8, 0, "otcu"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "vh_exhaustfanfault"),
new DataItem(50, Msg.READ, ByteType.LOWBYTE, DataType.INT8, 0, "otcl") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "vh_inletfanfault"),
g.put(56, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "vh_frostprotection"),
new DataItem(56, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "tdhwset", SIUnits.CELSIUS) }); new UIntDataItem(Msg.READ, ByteType.LOWBYTE, "vh_faultcode") });
g.put(57, new DataItem[] { new DataItem(57, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "tchmax") }); g.put(73, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "vh_diagnosticcode") });
g.put(58, new DataItem[] { new DataItem(58, Msg.READWRITE, ByteType.BOTH, DataType.FLOAT, 0, "otchcratio") }); 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, g.put(100,
new DataItem[] { new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 0, "rof0"), new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "rof0"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 1, "rof1"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "rof1"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 2, "rof2"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "rof2"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 3, "rof3"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "rof3"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 4, "rof4"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "rof4"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 5, "rof5"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "rof5"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 6, "rof6"), new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "rof6"),
new DataItem(100, Msg.READ, ByteType.HIGHBYTE, DataType.FLAGS, 7, "rof7") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "rof7") });
g.put(113, new DataItem[] {
new DataItem(113, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "unsuccessfulburnerstarts") }); g.put(101,
g.put(115, new DataItem[] { new DataItem(115, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "oemdiagcode") }); new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 0, "rof0"),
g.put(116, new DataItem[] { new DataItem(116, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "burnerstarts") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 1, "rof1"),
g.put(117, new DataItem[] { new DataItem(117, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "chpumpstarts") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 2, "rof2"),
g.put(118, new DataItem[] { new DataItem(118, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwpvstarts") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 3, "rof3"),
g.put(119, new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 4, "rof4"),
new DataItem[] { new DataItem(119, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwburnerstarts") }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 5, "rof5"),
g.put(120, new DataItem[] { new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 6, "rof6"),
new DataItem(120, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "burnerhours", Units.HOUR) }); new FlagDataItem(Msg.READ, ByteType.HIGHBYTE, 7, "rof7") });
g.put(121, new DataItem[] { g.put(102, new DataItem[] {});
new DataItem(121, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "chpumphours", Units.HOUR) });
g.put(122, new DataItem[] { g.put(105, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 106, "ss_tspnumber") });
new DataItem(122, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwpvhours", Units.HOUR) }); g.put(106, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "ss_tspentry") });
g.put(123, new DataItem[] {
new DataItem(123, Msg.READ, ByteType.BOTH, DataType.UINT16, 0, "dhwburnerhours", Units.HOUR) }); g.put(107, new DataItem[] { new TspFhbSizeDataItem(Msg.READ, ByteType.HIGHBYTE, 108, "ss_fhbnumber") });
g.put(124, g.put(108, new DataItem[] { new TspFhbValueDataItem(Msg.READ, "ss_fhbentry") });
new DataItem[] { new DataItem(124, Msg.WRITE, ByteType.BOTH, DataType.FLOAT, 0, "masterotversion") }); g.put(113, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "unsuccessfulburnerstarts") });
g.put(125, new DataItem[] { new DataItem(125, Msg.READ, ByteType.BOTH, DataType.FLOAT, 0, "slaveotversion") }); g.put(115, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "oemdiagcode") });
g.put(126, g.put(116, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "burnerstarts") });
new DataItem[] { g.put(117, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "chpumpstarts") });
new DataItem(126, Msg.WRITE, ByteType.HIGHBYTE, DataType.UINT8, 0, "masterproducttype"), g.put(118, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwpvstarts") });
new DataItem(126, Msg.WRITE, ByteType.LOWBYTE, DataType.UINT8, 0, "masterproductversion") }); g.put(119, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "dhwburnerstarts") });
g.put(127, g.put(120, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "burnerhours", Units.HOUR) });
new DataItem[] { new DataItem(127, Msg.READ, ByteType.HIGHBYTE, DataType.UINT8, 0, "slaveproducttype"), g.put(121, new DataItem[] { new UIntDataItem(Msg.READ, ByteType.BOTH, "chpumphours", Units.HOUR) });
new DataItem(127, Msg.READ, ByteType.LOWBYTE, DataType.UINT8, 0, "slaveproductversion") }); 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; return g;
} }

View File

@ -12,11 +12,14 @@
*/ */
package org.openhab.binding.openthermgateway.internal; package org.openhab.binding.openthermgateway.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link DataType} enum indicates the type of data from a DataItem. * The {@link DataType} enum indicates the type of data from a DataItem.
* *
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault
public enum DataType { public enum DataType {
FLAGS, FLAGS,
UINT8, UINT8,

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -26,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
*/ */
@NonNullByDefault @NonNullByDefault
public class GatewayCommand { 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 code;
private String validationSet; private String validationSet;
@ -83,19 +83,17 @@ public class GatewayCommand {
if (code != null && code.length() == 2) { if (code != null && code.length() == 2) {
String codeUpperCase = code.toUpperCase(); String codeUpperCase = code.toUpperCase();
if (supportedCommands.containsKey(codeUpperCase)) { if (SUPPORTEDCOMMANDS.containsKey(codeUpperCase)) {
String validateSet = supportedCommands.get(codeUpperCase); String validateSet = SUPPORTEDCOMMANDS.get(codeUpperCase);
if (validateSet == null) { if (validateSet == null) {
validateSet = ""; validateSet = "";
} }
return new GatewayCommand(codeUpperCase, message, 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( throw new IllegalArgumentException(
String.format("Unable to parse gateway command with code '%s' and message '%s'", code, message)); 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() { private static Map<String, @Nullable String> getSupportedCommands() {
Map<String, @Nullable String> c = new HashMap<>(); Map<String, @Nullable String> c = new HashMap<>();
c.put(GatewayCommandCode.TemperatureTemporary, null); c.put(GatewayCommandCode.TEMPERATURETEMPORARY, null);
c.put(GatewayCommandCode.TemperatureConstant, null); c.put(GatewayCommandCode.TEMPERATURECONSTANT, null);
c.put(GatewayCommandCode.TemperatureOutside, null); c.put(GatewayCommandCode.TEMPERATUREOUTSIDE, null);
c.put(GatewayCommandCode.SetClock, null); c.put(GatewayCommandCode.SETCLOCK, null);
c.put(GatewayCommandCode.HotWater, 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.PRINTREPORT, "A,B,C,G,I,L,M,O,P,R,S,T,V,W");
c.put(GatewayCommandCode.PrintSummary, "0,1"); c.put(GatewayCommandCode.PRINTSUMMARY, "0,1");
c.put(GatewayCommandCode.GateWay, "0,1,R"); 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.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.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.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.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.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.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.GPIOA, "0,1,2,3,4,5,6,7");
c.put(GatewayCommandCode.GpioB, "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.SETBACK, null);
c.put(GatewayCommandCode.TemperatureSensor, "O,R"); c.put(GatewayCommandCode.TEMPERATURESENSOR, "O,R");
c.put(GatewayCommandCode.AddAlternative, null); c.put(GatewayCommandCode.ADDALTERNATIVE, null);
c.put(GatewayCommandCode.DeleteAlternative, null); c.put(GatewayCommandCode.DELETEALTERNATIVE, null);
c.put(GatewayCommandCode.UnknownID, null); c.put(GatewayCommandCode.UNKNOWNID, null);
c.put(GatewayCommandCode.KnownID, null); c.put(GatewayCommandCode.KNOWNID, null);
c.put(GatewayCommandCode.PriorityMessage, null); c.put(GatewayCommandCode.PRIORITYMESSAGE, null);
c.put(GatewayCommandCode.SetResponse, null); c.put(GatewayCommandCode.SETRESPONSE, null);
c.put(GatewayCommandCode.ClearResponse, null); c.put(GatewayCommandCode.CLEARRESPONSE, null);
c.put(GatewayCommandCode.SetpointHeating, null); c.put(GatewayCommandCode.SETPOINTHEATING, null);
c.put(GatewayCommandCode.SetpointWater, null); c.put(GatewayCommandCode.SETPOINTWATER, null);
c.put(GatewayCommandCode.MaximumModulation, null); c.put(GatewayCommandCode.MAXIMUMMODULATION, null);
c.put(GatewayCommandCode.ControlSetpoint, null); c.put(GatewayCommandCode.CONTROLSETPOINT, null);
c.put(GatewayCommandCode.ControlSetpoint2, null); c.put(GatewayCommandCode.CONTROLSETPOINT2, null);
c.put(GatewayCommandCode.CentralHeating, "0,1"); c.put(GatewayCommandCode.CENTRALHEATING, "0,1");
c.put(GatewayCommandCode.CentralHeating2, "0,1"); c.put(GatewayCommandCode.CENTRALHEATING2, "0,1");
c.put(GatewayCommandCode.VentilationSetpoint, null); c.put(GatewayCommandCode.VENTILATIONSETPOINT, null);
c.put(GatewayCommandCode.Reset, null); c.put(GatewayCommandCode.RESET, null);
c.put(GatewayCommandCode.IgnoreTransition, "0,1"); c.put(GatewayCommandCode.IGNORETRANSITION, "0,1");
c.put(GatewayCommandCode.OverrideHighbyte, "0,1"); c.put(GatewayCommandCode.OVERRIDEHIGHBYTE, "0,1");
c.put(GatewayCommandCode.ForceThermostat, "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.VOLTAGEREFERENCE, "0,1,2,3,4,5,6,7,8,9");
c.put(GatewayCommandCode.DebugPointer, null); c.put(GatewayCommandCode.DEBUGPOINTER, null);
return c; return c;
} }

View File

@ -21,43 +21,43 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public class GatewayCommandCode { public class GatewayCommandCode {
public static final String TemperatureTemporary = "TT"; public static final String TEMPERATURETEMPORARY = "TT";
public static final String TemperatureConstant = "TC"; public static final String TEMPERATURECONSTANT = "TC";
public static final String TemperatureOutside = "OT"; public static final String TEMPERATUREOUTSIDE = "OT";
public static final String SetClock = "ST"; public static final String SETCLOCK = "ST";
public static final String HotWater = "HW"; public static final String HOTWATER = "HW";
public static final String PrintReport = "PR"; public static final String PRINTREPORT = "PR";
public static final String PrintSummary = "PS"; public static final String PRINTSUMMARY = "PS";
public static final String GateWay = "GW"; public static final String GATEWAY = "GW";
public static final String LedA = "LA"; public static final String LEDA = "LA";
public static final String LedB = "LB"; public static final String LEDB = "LB";
public static final String LedC = "LC"; public static final String LEDC = "LC";
public static final String LedD = "LD"; public static final String LEDD = "LD";
public static final String LedE = "LE"; public static final String LEDE = "LE";
public static final String LedF = "LF"; public static final String LEDF = "LF";
public static final String GpioA = "GA"; public static final String GPIOA = "GA";
public static final String GpioB = "GB"; public static final String GPIOB = "GB";
public static final String SetBack = "SB"; public static final String SETBACK = "SB";
public static final String TemperatureSensor = "TS"; public static final String TEMPERATURESENSOR = "TS";
public static final String AddAlternative = "AA"; public static final String ADDALTERNATIVE = "AA";
public static final String DeleteAlternative = "DA"; public static final String DELETEALTERNATIVE = "DA";
public static final String UnknownID = "UI"; public static final String UNKNOWNID = "UI";
public static final String KnownID = "KI"; public static final String KNOWNID = "KI";
public static final String PriorityMessage = "PM"; public static final String PRIORITYMESSAGE = "PM";
public static final String SetResponse = "SR"; public static final String SETRESPONSE = "SR";
public static final String ClearResponse = "CR"; public static final String CLEARRESPONSE = "CR";
public static final String SetpointHeating = "SH"; public static final String SETPOINTHEATING = "SH";
public static final String SetpointWater = "SW"; public static final String SETPOINTWATER = "SW";
public static final String MaximumModulation = "MM"; public static final String MAXIMUMMODULATION = "MM";
public static final String ControlSetpoint = "CS"; public static final String CONTROLSETPOINT = "CS";
public static final String ControlSetpoint2 = "C2"; public static final String CONTROLSETPOINT2 = "C2";
public static final String CentralHeating = "CH"; public static final String CENTRALHEATING = "CH";
public static final String CentralHeating2 = "H2"; public static final String CENTRALHEATING2 = "H2";
public static final String VentilationSetpoint = "VS"; public static final String VENTILATIONSETPOINT = "VS";
public static final String Reset = "RS"; public static final String RESET = "RS";
public static final String IgnoreTransition = "IT"; public static final String IGNORETRANSITION = "IT";
public static final String OverrideHighbyte = "OH"; public static final String OVERRIDEHIGHBYTE = "OH";
public static final String ForceThermostat = "FT"; public static final String FORCETHERMOSTAT = "FT";
public static final String VoltageReference = "VR"; public static final String VOLTAGEREFERENCE = "VR";
public static final String DebugPointer = "DP"; public static final String DEBUGPOINTER = "DP";
} }

View File

@ -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()));
}
}

View File

@ -25,15 +25,15 @@ import org.eclipse.jdt.annotation.Nullable;
@NonNullByDefault @NonNullByDefault
public class Message { 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 MessageType messageType;
private int id; private int id;
private String data; private String data;
public CodeType getCode() { public CodeType getCodeType() {
return this.code; return codeType;
} }
public MessageType getMessageType() { 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 // 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 // thermostat, and it's ID is equal to the previous message, then this is an
// override sent by the OpenTherm Gateway // 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 @Override
public String toString() { 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) { public Message(CodeType codeType, MessageType messageType, int id, String data) {
this.code = code; this.codeType = codeType;
this.messageType = messageType; this.messageType = messageType;
this.id = id; this.id = id;
this.data = data; this.data = data;
} }
public static @Nullable Message parse(String message) { public static @Nullable Message parse(String message) {
if (messagePattern.matcher(message).matches()) { if (MESSAGEPATTERN.matcher(message).matches()) {
// For now, only parse TBRA codes // 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)); MessageType messageType = getMessageType(message.substring(1, 3));
int id = Integer.valueOf(message.substring(3, 5), 16); int id = Integer.valueOf(message.substring(3, 5), 16);
String data = message.substring(5); String data = message.substring(5);
return new Message(code, messageType, id, data); return new Message(codeType, messageType, id, data);
} }
return null; return null;

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.openthermgateway.internal; 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 * The {@link MessageType} indicates the type of message received by the OpenTherm Gateway, based
* on the OpenTherm specification. * on the OpenTherm specification.
* *
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault
public enum MessageType { public enum MessageType {
READDATA, // 000 READDATA, // 000
READACK, // 100 READACK, // 100

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.openthermgateway.internal; 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 * The {@link Msg} flag is used to indicate whether the message is sent for Reading, Writing
* or both, based on the OpenTherm specification. * or both, based on the OpenTherm specification.
* *
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault
public enum Msg { public enum Msg {
READ, READ,
WRITE, WRITE,

View File

@ -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";
}

View File

@ -22,11 +22,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public interface OpenThermGatewayCallback { public interface OpenThermGatewayCallback {
void connecting();
void connected(); void connectionStateChanged(ConnectionState state);
void disconnected();
void receiveMessage(Message message); void receiveMessage(Message message);
} }

View File

@ -27,4 +27,8 @@ public class OpenThermGatewayConfiguration {
public int port = 0; public int port = 0;
public int connectionRetryInterval = 60; public int connectionRetryInterval = 60;
public int connectTimeoutSeconds = 5;
public int readTimeoutSeconds = 20;
} }

View File

@ -12,19 +12,23 @@
*/ */
package org.openhab.binding.openthermgateway.internal; package org.openhab.binding.openthermgateway.internal;
import java.util.concurrent.Callable;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link OpenThermGatewayConnector} interface is used to allow multiple types of connectors * The {@link OpenThermGatewayConnector} interface is used to allow multiple types of connectors
* to be implemented and used to connect to the OpenTherm Gateway. * to be implemented and used to connect to the OpenTherm Gateway.
* *
* @author Arjen Korevaar - Initial contribution * @author Arjen Korevaar - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public interface OpenThermGatewayConnector extends Runnable { public interface OpenThermGatewayConnector extends Callable<Boolean> {
void sendCommand(GatewayCommand command); void sendCommand(GatewayCommand command);
boolean isConnected(); boolean isConnected();
void stop(); void stop();
void start();
} }

View File

@ -14,8 +14,10 @@ package org.openhab.binding.openthermgateway.internal;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; 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.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.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@ -35,15 +37,19 @@ public class OpenThermGatewayHandlerFactory extends BaseThingHandlerFactory {
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return thingTypeUID.equals(OpenThermGatewayBindingConstants.MAIN_THING_TYPE); return OpenThermGatewayBindingConstants.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID);
} }
@Override @Override
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(OpenThermGatewayBindingConstants.MAIN_THING_TYPE)) { if (thingTypeUID.equals(OpenThermGatewayBindingConstants.OPENTHERM_GATEWAY_THING_TYPE_UID)) {
return new OpenThermGatewayHandler(thing); 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; return null;

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.openthermgateway.internal; package org.openhab.binding.openthermgateway.internal;
import static org.openhab.binding.openthermgateway.internal.OpenThermGatewayBindingConstants.BINDING_ID;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -22,12 +24,16 @@ import java.util.AbstractMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.State;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -39,107 +45,138 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnector { public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnector {
private static final int COMMAND_RESPONSE_TIME_MILLISECONDS = 100; private static final int COMMAND_RESPONSE_MIN_WAIT_TIME_MILLISECONDS = 100;
private static final int COMMAND_TIMEOUT_MILLISECONDS = 5000; private static final int COMMAND_RESPONSE_MAX_WAIT_TIME_MILLISECONDS = 5000;
private final Logger logger = LoggerFactory.getLogger(OpenThermGatewaySocketConnector.class); private final Logger logger = LoggerFactory.getLogger(OpenThermGatewaySocketConnector.class);
private final OpenThermGatewayCallback callback; private final OpenThermGatewayCallback callback;
private final String ipaddress; private final String ipaddress;
private final int port; private final int port;
private final int connectTimeoutMilliseconds;
private final int readTimeoutMilliSeconds;
private @Nullable PrintWriter writer; private @Nullable volatile PrintWriter writer;
private @Nullable volatile Thread thread;
private volatile boolean stopping; private @Nullable Future<Boolean> future;
private boolean connected; private @Nullable ExecutorService executor;
private Map<String, Entry<Long, GatewayCommand>> pendingCommands = new ConcurrentHashMap<>(); 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.callback = callback;
this.ipaddress = ipaddress; ipaddress = config.ipaddress;
this.port = port; port = config.port;
connectTimeoutMilliseconds = config.connectTimeoutSeconds * 1000;
readTimeoutMilliSeconds = config.readTimeoutSeconds * 1000;
} }
@Override @Override
public void run() { public Boolean call() throws Exception {
stopping = false; thread = Thread.currentThread();
connected = false;
logger.debug("Connecting OpenThermGatewaySocketConnector to {}:{}", this.ipaddress, this.port);
callback.connecting();
try (Socket socket = new Socket()) { try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(this.ipaddress, this.port), COMMAND_TIMEOUT_MILLISECONDS); logger.debug("Connecting OpenThermGatewaySocketConnector to {}:{}", this.ipaddress, this.port);
socket.setSoTimeout(COMMAND_TIMEOUT_MILLISECONDS); callback.connectionStateChanged(ConnectionState.CONNECTING);
connected = true; socket.connect(new InetSocketAddress(ipaddress, port), connectTimeoutMilliseconds);
socket.setSoTimeout(readTimeoutMilliSeconds);
callback.connected();
logger.debug("OpenThermGatewaySocketConnector connected"); logger.debug("OpenThermGatewaySocketConnector connected");
callback.connectionStateChanged(ConnectionState.CONNECTED);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter wrt = new PrintWriter(socket.getOutputStream(), true)) { PrintWriter wrt = new PrintWriter(socket.getOutputStream(), true)) {
// Make writer accessible on class level // Make writer accessible on class level
writer = wrt; 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 // 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 @Nullable
String message = reader.readLine(); String message = reader.readLine();
if (message != null) { if (message != null) {
handleMessage(message); handleMessage(message);
} else { } else {
logger.debug("Connection closed by OpenTherm Gateway"); logger.debug("Received NULL message from OpenTherm Gateway (EOF)");
break; break;
} }
} }
} catch (IOException ex) {
logger.debug("Stopping OpenThermGatewaySocketConnector"); logger.warn("Error communicating with OpenTherm Gateway: '{}'", ex.getMessage());
} finally {
connected = false;
logger.debug("OpenThermGatewaySocketConnector disconnected");
callback.disconnected();
} }
} catch (IOException ex) { } catch (IOException ex) {
logger.warn("Unable to connect to the OpenTherm Gateway.", ex); logger.warn("Unable to connect to the OpenTherm Gateway: '{}'", ex.getMessage());
callback.disconnected();
} }
thread = null;
writer = null;
logger.debug("OpenThermGatewaySocketConnector disconnected");
callback.connectionStateChanged(ConnectionState.DISCONNECTED);
return true;
} }
@Override @Override
public void stop() { public void stop() {
logger.debug("Stopping OpenThermGatewaySocketConnector"); 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 @Override
public boolean isConnected() { public void start() {
return connected; logger.debug("Starting OpenThermGatewaySocketConnector");
ExecutorService executor = this.executor = Executors
.newSingleThreadExecutor(new NamedThreadFactory("binding-" + BINDING_ID));
future = executor.submit(this);
} }
@Override @Override
public void sendCommand(GatewayCommand command) { public synchronized boolean isConnected() {
@Nullable Thread thread = this.thread;
PrintWriter wrtr = writer; return (thread != null) && thread.isAlive();
}
String msg = command.toFullString(); @Override
public synchronized void sendCommand(GatewayCommand command) {
PrintWriter wrt = writer;
pendingCommands.put(command.getCode(), pendingCommands.put(command.getCode(),
new AbstractMap.SimpleImmutableEntry<>(System.currentTimeMillis(), command)); new AbstractMap.SimpleImmutableEntry<>(System.currentTimeMillis(), command));
if (connected) { String msg = command.toFullString();
if (isConnected() && (wrt != null)) {
logger.debug("Sending message: {}", msg); logger.debug("Sending message: {}", msg);
if (wrtr != null) { wrt.print(msg + "\r\n");
wrtr.print(msg + "\r\n"); wrt.flush();
wrtr.flush(); if (wrt.checkError()) {
logger.warn("sendCommand() error sending message to OpenTherm Gateway => PLEASE REPORT !!");
stop();
} }
} else { } else {
logger.debug("Unable to send message: {}. OpenThermGatewaySocketConnector is not connected.", msg); logger.debug("Unable to send message: {}. OpenThermGatewaySocketConnector is not connected.", msg);
@ -159,8 +196,8 @@ public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnecto
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
for (Entry<Long, GatewayCommand> timeAndCommand : pendingCommands.values()) { for (Entry<Long, GatewayCommand> timeAndCommand : pendingCommands.values()) {
long responseTime = timeAndCommand.getKey() + COMMAND_RESPONSE_TIME_MILLISECONDS; long responseTime = timeAndCommand.getKey() + COMMAND_RESPONSE_MIN_WAIT_TIME_MILLISECONDS;
long timeoutTime = timeAndCommand.getKey() + COMMAND_TIMEOUT_MILLISECONDS; long timeoutTime = timeAndCommand.getKey() + COMMAND_RESPONSE_MAX_WAIT_TIME_MILLISECONDS;
if (currentTime > responseTime && currentTime <= timeoutTime) { if (currentTime > responseTime && currentTime <= timeoutTime) {
logger.debug("Resending command: {}", timeAndCommand.getValue()); logger.debug("Resending command: {}", timeAndCommand.getValue());
@ -175,47 +212,11 @@ public class OpenThermGatewaySocketConnector implements OpenThermGatewayConnecto
if (msg == null) { if (msg == null) {
logger.trace("Received message: {}, (unknown)", message); logger.trace("Received message: {}, (unknown)", message);
return; return;
} else {
logger.trace("Received message: {}, {} {} {}", message, msg.getID(), msg.getCode(), msg.getMessageType());
} }
logger.trace("Received message: {}, {} {} {}", message, msg.getID(), msg.getCodeType(), 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);
}
}
if (msg.getMessageType() == MessageType.READACK || msg.getMessageType() == MessageType.WRITEDATA if (msg.getMessageType() == MessageType.READACK || msg.getMessageType() == MessageType.WRITEDATA
|| msg.getID() == 0 || msg.getID() == 1) { || msg.getID() == 0 || msg.getID() == 1) {
receiveMessage(msg); callback.receiveMessage(msg);
} }
} }
private void receiveMessage(Message message) {
callback.receiveMessage(message);
}
} }

View File

@ -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()));
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -4,7 +4,7 @@
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0" 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"> 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"> <config-description uri="thing-type:openthermgateway:openthermgateway">
<parameter-group name="connection"> <parameter-group name="connection">
<label>Connection</label> <label>Connection</label>
<description>Connection settings.</description> <description>Connection settings.</description>
@ -28,6 +28,20 @@
<default>60</default> <default>60</default>
</parameter> </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-description:config-descriptions> </config-description:config-descriptions>

View File

@ -3,9 +3,12 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" 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"> 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"> <thing-type id="boiler">
<label>OpenTherm Gateway</label> <supported-bridge-type-refs>
<description>OpenTherm Gateway binding</description> <bridge-type-ref id="openthermgateway"/>
</supported-bridge-type-refs>
<label>Boiler</label>
<description>Boiler and thermostat</description>
<channels> <channels>
<channel id="roomtemp" typeId="roomtemp"/> <channel id="roomtemp" typeId="roomtemp"/>
<channel id="roomsetpoint" typeId="roomsetpoint"/> <channel id="roomsetpoint" typeId="roomsetpoint"/>
@ -44,6 +47,7 @@
<channel id="airpressfault" typeId="airpressfault"/> <channel id="airpressfault" typeId="airpressfault"/>
<channel id="waterovtemp" typeId="waterovtemp"/> <channel id="waterovtemp" typeId="waterovtemp"/>
<channel id="oemfaultcode" typeId="oemfaultcode"/> <channel id="oemfaultcode" typeId="oemfaultcode"/>
<channel id="diag" typeId="diag"/>
<channel id="unsuccessfulburnerstarts" typeId="unsuccessfulburnerstarts"/> <channel id="unsuccessfulburnerstarts" typeId="unsuccessfulburnerstarts"/>
<channel id="burnerstarts" typeId="burnerstarts"/> <channel id="burnerstarts" typeId="burnerstarts"/>
<channel id="chpumpstarts" typeId="chpumpstarts"/> <channel id="chpumpstarts" typeId="chpumpstarts"/>
@ -53,12 +57,11 @@
<channel id="chpumphours" typeId="chpumphours"/> <channel id="chpumphours" typeId="chpumphours"/>
<channel id="dhwpvhours" typeId="dhwpvhours"/> <channel id="dhwpvhours" typeId="dhwpvhours"/>
<channel id="dhwburnerhours" typeId="dhwburnerhours"/> <channel id="dhwburnerhours" typeId="dhwburnerhours"/>
<channel id="sendcommand" typeId="sendcommand"/> <channel id="tspnumber" typeId="tspnumber"/>
<channel id="tspentry" typeId="tspentry"/>
<channel id="fhbnumber" typeId="fhbnumber"/>
<channel id="fhbentry" typeId="fhbentry"/>
</channels> </channels>
<properties>
<property name="version">1.3.0</property>
</properties>
<config-description-ref uri="thing-type:openthermgateway:otgw"/>
</thing-type> </thing-type>
</thing:thing-descriptions> </thing:thing-descriptions>

View File

@ -4,6 +4,26 @@
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" 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"> 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"> <channel-type id="roomtemp">
<item-type>Number:Temperature</item-type> <item-type>Number:Temperature</item-type>
<label>Room Temperature</label> <label>Room Temperature</label>
@ -349,11 +369,351 @@
<state readOnly="true"/> <state readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="sendcommand"> <channel-type id="tspnumber">
<item-type>String</item-type> <item-type>Number:Dimensionless</item-type>
<label>Send Command</label> <label>Transparent Slave Parameter Number</label>
<description>Channel to send commands to the OpenTherm Gateway device</description> <description>Number of transparant slave parameter entries</description>
<state readOnly="false"/> <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> </channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>

View File

@ -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>

View File

@ -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>