diff --git a/CODEOWNERS b/CODEOWNERS
index d9817cbd083c3..1a7475680eb5b 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -34,6 +34,7 @@
/bundles/org.openhab.binding.caddx/ @jossuar
/bundles/org.openhab.binding.chromecast/ @kaikreuzer
/bundles/org.openhab.binding.cm11a/ @BobRak
+/bundles/org.openhab.binding.comfoair/ @boehan
/bundles/org.openhab.binding.coolmasternet/ @projectgus
/bundles/org.openhab.binding.coronastats/ @DerOetzi
/bundles/org.openhab.binding.daikin/ @caffineehacker
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 3eb5b9eda311d..fe4313c530290 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -159,6 +159,11 @@
org.openhab.binding.cm11a
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.comfoair
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.coolmasternet
diff --git a/bundles/org.openhab.binding.comfoair/.classpath b/bundles/org.openhab.binding.comfoair/.classpath
new file mode 100644
index 0000000000000..68080783db600
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.comfoair/.project b/bundles/org.openhab.binding.comfoair/.project
new file mode 100644
index 0000000000000..2854be01e302a
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.comfoair
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.comfoair/NOTICE b/bundles/org.openhab.binding.comfoair/NOTICE
new file mode 100644
index 0000000000000..4c20ef446c1e4
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab2-addons
diff --git a/bundles/org.openhab.binding.comfoair/README.md b/bundles/org.openhab.binding.comfoair/README.md
new file mode 100644
index 0000000000000..5b2805bac75e8
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/README.md
@@ -0,0 +1,288 @@
+# ComfoAir Binding
+
+This binding allows to monitor and control Zehnder ComfoAir serial controlled ventilation systems via RS232 serial connection.
+Though the binding is developed based on the protocol description for Zehnder ComfoAir devices it should also work for mostly identical systems from different manufacturers, like StorkAir WHR930, Wernig G90-380 and Paul Santos 370 DC.
+It was also successfully tested on a Wernig G90-160.
+
+### Limitations
+
+* Either the ComfoAir binding or the CCEase Comfocontrol can be active, but not together.
+* You must implement auto mode by yourself with rules, but it is more powerful.
+
+### Prerequisites
+
+Computer communication between ComfoAir device and openHAB via RS232 connection has to be set up.
+The connection should be made with a 3-wire cable connecting pins: GND, TX, RX of RS232 sockets, but RX and TX pins should be crossed (TX of ComfoAir to RX of PC, RX of ComfoAir to TX of PC).
+
+### Serial Port Access Rights
+
+* Take care that the user that runs openHAB has rights to access the serial port
+* On Ubuntu/Debian based systems (incl. openHABian) that usually means adding the user (e.g. openhab) to the group "dialout", i.e.
+
+```
+sudo usermod -a -G dialout openhab
+```
+
+## Supported Things
+
+Only a single generic thing type is supported by the binding:
+
+|Thing Type ID |Description |
+|--------------|---------------------------------------------------------------------|
+|comfoair |A ComfoAir ventilation system connected via RS232 serial connection. |
+
+## Discovery
+
+Discovery is not supported.
+
+## Thing Configuration
+
+For the thing creation, the appropriate serial port has to be set.
+
+|Parameter |Values |Default |
+|---------------|-----------------------------------------|---------------|
+|serialPort |`/dev/ttyUSB0`, `COM1`, etc. |- |
+|refreshInterval|Refresh interval in seconds (10...65535) |60 |
+
+## Channels
+
+The ComfoAir binding supports the following channels.
+Some channels are added dynamically based on installed device options.
+Available device options are shown in thing properties.
+
+| Channel ID | Item Type | Label | Description | Read Only | Advanced |
+|------------------------------------|--------------------------|---------------------------------|---------------------------------------------------------------------------------------------|-----------|----------|
+| Binding Control | | | | | |
+| bindingControl#activate | Switch | Activate Binding Control | Activate (control through openHAB) or deactivate (return control to CCEase) binding control | false | no |
+| Ventilation Values | | | | | |
+| ventilation#fanLevel | Number | Fan Level | Fan level | false | no |
+| ventilation#fanOut0 | Number | Fan Out Level 0 (away) | Fan level 0 performance (%) of outgoing fan | false | yes |
+| ventilation#fanOut1 | Number | Fan Out Level 1 | Fan level 1 performance (%) of outgoing fan | false | yes |
+| ventilation#fanOut2 | Number | Fan Out Level 2 | Fan level 2 performance (%) of outgoing fan | false | yes |
+| ventilation#fanOut3 | Number | Fan Out Level 3 | Fan level 3 performance (%) of outgoing fan | false | yes |
+| ventilation#fanIn0 | Number | Fan In Level 0 (away) | Fan level 0 performance (%) of incoming fan | false | yes |
+| ventilation#fanIn1 | Number | Fan In Level 1 | Fan level 1 performance (%) of incoming fan | false | yes |
+| ventilation#fanIn2 | Number | Fan In Level 2 | Fan level 2 performance (%) of incoming fan | false | yes |
+| ventilation#fanIn3 | Number | Fan In Level 3 | Fan level 3 performance (%) of incoming fan | false | yes |
+| ventilation#fanInPercent | Number | Fan In (%) | Current relative speed (%) of incoming fan | false | yes |
+| ventilation#fanOutPercent | Number | Fan Out (%) | Current relative speed (%) of outgoing fan | true | yes |
+| ventilation#fanInRPM | Number | Fan In (rpm) | Current absolute speed (rpm) of incoming fan | true | yes |
+| ventilation#fanOutRPM | Number | Fan Out (rpm) | Current absolute speed (rpm) of outgoing fan | true | yes |
+| Temperature Values | | | | | |
+| temperatures#targetTemperature | Number:Temperature | Target Temperature | Target (comfort) temperature | false | no |
+| temperatures#outdoorTemperatureIn | Number:Temperature | Outdoor Temperature Incoming | Intake air temperature (outside) | true | yes |
+| temperatures#outdoorTemperatureOut | Number:Temperature | Outdoor Temperature Outgoing | Outlet air temperature (outside) | true | yes |
+| temperatures#indoorTemperatureIn | Number:Temperature | Indoor Temperature Incoming | Inlet air temperature (inside) | true | yes |
+| temperatures#indoorTemperatureOut | Number:Temperature | Indoor Temperature Outgoing | Uptake air temperature (inside) | true | yes |
+| temperatures#isT1Sensor | Switch | Sensor T1 Available | Availability of temperature sensor T1 (outdoor in) | true | yes |
+| temperatures#isT2Sensor | Switch | Sensor T2 Available | Availability of temperature sensor T2 (indoor in) | true | yes |
+| temperatures#isT3Sensor | Switch | Sensor T3 Available | Availability of temperature sensor T3 (indoor out) | true | yes |
+| temperatures#isT4Sensor | Switch | Sensor T4 Available | Availability of temperature sensor T4 (outdoor out) | true | yes |
+| temperatures#isEWTSensor | Switch | EWT Sensor Available | Availability of EWT temperature sensor | true | yes |
+| temperatures#isHeaterSensor | Switch | Heater Sensor Available | Availability of heater temperature sensor | true | yes |
+| temperatures#isCookerhoodSensor | Switch | Cookerhood Sensor Available | Availability of cookerhood temperature sensor | true | yes |
+| temperatures#ewtTemperature | Number:Temperature | EWT Temperature | Temperature of geothermal heat exchanger sensor | true | yes |
+| temperatures#heaterTemperature | Number:Temperature | Heater Temperature | Temperature of heater sensor | true | yes |
+| temperatures#cookerhoodTemperature | Number:Temperature | Cookerhood Temperature | Temperature of cookerhood sensor | true | yes |
+| Uptimes | | | | | |
+| times#level0Time | Number:Time | Level 0 duration | Uptime at level 0 (away) | true | yes |
+| times#level1Time | Number:Time | Level 1 duration | Uptime at level 1 | true | yes |
+| times#level2Time | Number:Time | Level 2 duration | Uptime at level 2 | true | yes |
+| times#level3Time | Number:Time | Level 3 duration | Uptime at level 3 | true | yes |
+| times#freezeTime | Number:Time | Antifrost Duration | Uptime of antifrost | true | yes |
+| times#preheaterTime | Number:Time | Preheater Duration | Uptime of preheater | true | yes |
+| times#bypassTime | Number:Time | Bypass Duration | Hours of bypass open | true | yes |
+| times#filterHours | Number:Time | Filter Duration | Uptime of the filter | true | no |
+| Menu P1: Control States | | | | | |
+| menuP1#menu20Mode | Switch | Menu 20 Mode (P10) | State of menu 20 mode (P10) | true | yes |
+| menuP1#menu21Mode | Switch | Menu 21 Mode (P11) | State of menu 21 mode (P11) | true | yes |
+| menuP1#menu22Mode | Switch | Menu 22 Mode (P12) | State of menu 22 mode (P12) | true | yes |
+| menuP1#menu23Mode | Switch | Menu 23 Mode (P13) | State of menu 23 mode (P13) | true | yes |
+| menuP1#menu24Mode | Switch | Menu 24 Mode (P14) | State of menu 24 mode (P14) | true | yes |
+| menuP1#menu25Mode | Switch | Menu 25 Mode (P15) | State of menu 25 mode (P15) | true | yes |
+| menuP1#menu26Mode | Switch | Menu 26 Mode (P16) | State of menu 26 mode (P16) | true | yes |
+| menuP1#menu27Mode | Switch | Menu 27 Mode (P17) | State of menu 27 mode (P17) | true | yes |
+| menuP1#menu28Mode | Switch | Menu 28 Mode (P18) | State of menu 28 mode (P18) | true | yes |
+| menuP1#menu29Mode | Switch | Menu 29 Mode (P19) | State of menu 29 mode (P19) | true | yes |
+| Menu P2: Delay Settings | | | | | |
+| menuP2#bathroomStartDelay | Number | Menu P21 | Start delay for bathroom switch (min) | false | yes |
+| menuP2#bathroomEndDelay | Number | Menu P22 | End delay for bathroom switch (min) | false | yes |
+| menuP2#L1EndDelay | Number | Menu P23 | End delay for L1 switch (min) | false | yes |
+| menuP2#pulseVentilation | Number | Menu P27 | Period for pulse ventilation (min) | false | yes |
+| menuP2#filterWeeks | Number | Menu P24 | Usage period until filter pollution message (weeks) | false | yes |
+| menuP2#RFShortDelay | Number | Menu P25 | End delay (RF short actuation) for ventilation level 3 (min) | false | yes |
+| menuP2#RFLongDelay | Number | Menu P26 | End delay (RF long actuation) for ventilation level 3 (min) | false | yes |
+| menuP2#cookerhoodDelay | Number | Menu P20 | End delay for cooker hood control (min) | false | yes |
+| Menu P9: Option States | | | | | |
+| menuP9#chimneyState (dynamic) | Switch | Chimney Control State | State of the chimney control | true | yes |
+| menuP9#bypassState (dynamic) | Switch | Bypass State | State of the bypass (ON = open / OFF = closed) | true | yes |
+| menuP9#ewtState (dynamic) | Switch | EWT State | State of the EWT valve (ON = open / OFF = closed) | true | yes |
+| menuP9#heaterState (dynamic) | Switch | Heater State | State of the heater | true | yes |
+| menuP9#vControlState | Switch | 0-10V Control State | State of the 0-10V control | true | yes |
+| menuP9#frostState (dynamic) | Switch | Antifrost State | State of the antifrost control | true | yes |
+| menuP9#cookerhoodState (dynamic) | Switch | Cookerhood State | State of the cookerhood control | true | yes |
+| menuP9#enthalpyState (dynamic) | Switch | Enthalpy State | State of the enthalpy module | true | yes |
+| Error States | | | | | |
+| errors#filterError | Switch | Filter Error | Filter full | true | no |
+| errors#errorsCurrent | String | Errors Current | Current errors | true | no |
+| errors#errorsLast | String | Errors Last | Last errors | true | yes |
+| errors#errorsPrelast | String | Errors Prelast | Prelast errors | true | yes |
+| errors#errorsPrePrelast | String | Errors Pre-Prelast | Pre-Prelast errors | true | yes |
+| Bypass Values (dynamic) | | | | | |
+| bypass#bypassFactor | Number | Bypass Factor | Bypass factor value | true | yes |
+| bypass#bypassLevel | Number | Bypass Level | Bypass level state | true | yes |
+| bypass#bypassCorrection | Number | Bypass Correction | Bypass correction state | true | yes |
+| bypass#bypassSummer | Switch | Bypass Summer Mode | Bypass summer mode | true | yes |
+| Preheater Values (dynamic) | | | | | |
+| preheater#preheaterValve | Number | Preheater Valve | State of the preheater valve | true | yes |
+| preheater#preheaterFrostProtect | Switch | Frost Protection | State of the frost protection | true | yes |
+| preheater#preheaterHeating | Switch | Preheater | State of the preheater | true | yes |
+| preheater#preheaterFrostTime | Number | Preheater Frost Time | Frost minutes | true | yes |
+| preheater#preheaterSafety | Number | Preheater Frost Safety | Frost safety setting | true | yes |
+| EWT Values (dynamic) | | | | | |
+| ewt#ewtTemperatureLow | Number:Temperature | EWT Temperature (low) | Lower temperature of the geothermal heat exchanger | true | yes |
+| ewt#ewtTemperatureHigh | Number:Temperature | EWT Temperature (high) | Upper temperature of the geothermal heat exchanger | true | yes |
+| ewt#ewtSpeed | Number | EWT Speed Up (%) | Speed up of the geothermal heat exchanger | true | yes |
+| Heater Values (dynamic) | | | | | |
+| heater#heaterPower | Number | Heater Power | Heater power value | true | yes |
+| heater#heaterPowerI | Number | Heater Power I-parameter | Heater power I-parameter value | true | yes |
+| heater#heaterTargetTemperature | Number:Temperature | Heater Target Temperature | Target temperature of the heater | true | yes |
+| Cookerhood Values (dynamic) | | | | | |
+| cookerhood#cookerhoodSpeed | Number | Cookerhood Speed Up (%) | Speed up of the cookerhood | true | yes |
+| Enthalpy Values (dynamic) | | | | | |
+| enthalpy#enthalpyTemperature | Number:Temperature | Enthalpy Sensor Temperature | Temperature of the enthalpy sensor | true | yes |
+| enthalpy#enthalpyHumidity | Number | Enthalpy Sensor Humidity | Humidity of the enthalpy sensor | true | yes |
+| enthalpy#enthalpyLevel | Number | Enthalpy Sensor Level | Level of the enthalpy sensor | true | yes |
+| enthalpy#enthalpyTime | Number | Enthalpy Sensor Timer | Timer state of the enthalpy sensor | true | yes |
+| Inputs | | | | | |
+| inputs#isL1Switch | Switch | L1 Switch | Availability of L1 step switch | true | yes |
+| inputs#isL2Switch | Switch | L2 Switch | Availability of L2 step switch | true | yes |
+| inputs#isBathroomSwitch | Switch | Bathroom Switch | Availability of bathroom switch | true | yes |
+| inputs#isCookerhoodSwitch | Switch | Cookerhood Switch | Availability of cookerhood switch | true | yes |
+| inputs#isExternalFilter | Switch | External Filter | Availability of external filter | true | yes |
+| inputs#isWTW | Switch | Heat Recovery | Availability of heat recovery (WTW) | true | yes |
+| inputs#isBathroom2Switch | Switch | Bathroom Switch 2 | Availability of bathroom switch 2 (luxe) | true | yes |
+| Resets | | | | | |
+| resets#filterReset | Switch | Filter Reset | Reset filter uptime | false | no |
+| resets#errorReset | Switch | Error Reset | Reset errors | false | no |
+| Analog Inputs [1-4] | | | | | |
+| analog[1-4]#isAnalog | Switch | Analog Input Availability | Availability of analog input [1-4] | false | yes |
+| analog[1-4]#analogMode | Switch | Analog Input State | State of analog input [1-4] | false | yes |
+| analog[1-4]#analogNegative | Switch | Analog Input Postive/Negative | Postive/Negative state of analog input [1-4] | false | yes |
+| analog[1-4]#analogVolt | Number:ElectricPotential | Analog Input Voltage Level | Voltage level of analog input [1-4] | false | yes |
+| analog[1-4]#analogMin | Number | Analog Input Min | Minimum setting for analog input [1-4] | false | yes |
+| analog[1-4]#analogMax | Number | Analog Input Max | Maximum setting for analog input [1-4] | false | yes |
+| analog[1-4]#analogValue | Number | Analog Input Target | Target setting for analog input [1-4] | false | yes |
+| RF Input | | | | | |
+| analogRF#isRF | Switch | RF Input Availability | Availability of RF input | false | yes |
+| analogRF#RFMode | Switch | RF Input State | State of RF input | false | yes |
+| analogRF#RFNegative | Switch | RF Input Postive/Negative | Postive/Negative state of RF input | false | yes |
+| analogRF#RFMin | Number | RF Input Min | Minimum setting for RF input | false | yes |
+| analogRF#RFMax | Number | RF Input Max | Maximum setting for RF input | false | yes |
+| analogRF#RFValue | Number | RF Input Target | Target setting for RF input | false | yes |
+| analogRF#analogPriority | Number | Analog Priority | Priority of control | false | yes |
+
+## Full Example
+
+`.things` file:
+
+```
+Thing comfoair:comfoair:myComfoAir "ComfoAir" [serialPort="/dev/ttyUSB0", refreshInterval="60"]
+```
+
+`.items` file:
+
+```
+Switch comfoairControl "Activate" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:bindingControl#activate"}
+Number comfoairFanLevel "Ventilation level [%d]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ventilation#fanLevel"}
+Switch comfoairErrorReset "Error reset" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:resets#errorReset"}
+Switch comfoairFilterReset "Filter reset" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:resets#filterReset"}
+Number comfoairFilterPeriod "Filter period [%d weeks]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP2#filterWeeks"}
+
+// Messages
+String comfoairError "Error: [%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:errors#errorMessage"}
+Number:Time comfoairFilterRuntime "Filter runtime [%.0f h]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:times#filterHours"}
+
+// State
+Number:Temperature comfoairTargetTemperature "Comfort temperature [%.1f °C]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:temperatures#targetTemperature"}
+Number:Temperature comfoairOutdoorIncomingTemperature "Inlet air temperature [%.1f °C]" (ComfoAir, comfoairTemps_Chart) {channel="comfoair:comfoair:myComfoAir:temperatures#outdoorTemperatureIn"}
+Number:Temperature comfoairIndoorIncomingTemperature "Supply air temperature [%.1f °C]" (ComfoAir, comfoairTemps_Chart) {channel="comfoair:comfoair:myComfoAir:temperatures#indoorTemperatureIn"}
+Number:Temperature comfoairIndoorOutgoingTemperature "Return air temperature [%.1f °C]" (ComfoAir, comfoairTemps_Chart) {channel="comfoair:comfoair:myComfoAir:temperatures#indoorTemperatureOut"}
+Number:Temperature comfoairOutdoorOutgoingTemperature "Exhaust air temperature [%.1f °C]" (ComfoAir, comfoairTemps_Chart) {channel="comfoair:comfoair:myComfoAir:temperatures#outdoorTemperatureOut"}
+Number comfoairIncomingFan "Supply capacity [%d %%]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ventilation#fanInPercent"}
+Number comfoairOutgoingFan "Exhaust capacity [%d %%]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ventilation#fanOutPercent"}
+Number comfoairFanIn0 "Supply capacity - level 0 [%d %%]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ventilation#fanIn0"}
+Number comfoairFanOut0 "Exhaust capacity - level 0 [%d %%]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ventilation#fanOut0"}
+Switch comfoairBypassMode "Bypass [MAP(comfoair_bypass.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#bypassState"}
+Switch comfoairEWTMode "EWT [MAP(comfoair_on-off.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#ewtState"}
+Switch comfoairChimneyMode "Fire programme [MAP(comfoair_on-off.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#chimneyState"}
+Switch comfoairHeaterMode "Heater [MAP(comfoair_on-off.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#heaterState"}
+Switch comfoairCookerHoodMode "Extractor hood [MAP(comfoair_on-off.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#cookerhoodState"}
+Switch comfoairEnthalpyMode "Enthalpy [MAP(comfoair_on-off.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#enthalpyState"}
+Switch comfoairFreezeMode "Freeze [MAP(comfoair_freeze.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP9#frostState"}
+```
+
+`.sitemap` file:
+
+```
+sitemap comfoair label="ComfoAir" {
+ Frame label="Main" {
+ Text item=comfoairError labelcolor=[!="OK"="red"] valuecolor=[!="OK"="red"]
+ Switch item=comfoairControl mappings=[0="CCEase", 1="Computer"]
+ Switch item=comfoairErrorReset mappings=[ON="Reset"]
+ Switch item=comfoairFilterReset mappings=[ON="Reset"]
+ }
+ Frame label="Control" {
+ Selection item=comfoairFanLevel
+ Setpoint item=comfoairTargetTemperature step=0.5 minValue=15 maxValue=28 valuecolor=["black"]
+ }
+ Frame label="State" {
+ Text item=comfoairOutdoorIncomingTemperature valuecolor=["black"]
+ Text item=comfoairOutdoorOutgoingTemperature valuecolor=["black"]
+ Text item=comfoairIndoorIncomingTemperature valuecolor=["black"]
+ Text item=comfoairIndoorOutgoingTemperature valuecolor=["black"]
+ }
+ Frame {
+ Text item=comfoairIncomingFan valuecolor=["black"]
+ Text item=comfoairBypassMode valuecolor=["black"]
+ Text item=comfoairOutgoingFan valuecolor=["black"]
+ Text item=comfoairEWTMode valuecolor=[OFF="silver", ON="black"]
+ Text item=comfoairEfficiency valuecolor=["black"]
+ Text item=comfoairFreezeMode valuecolor=[OFF="black", ON="red"]
+ Text item=comfoairFilterRuntime_Message valuecolor=["black"]
+ Text item=comfoairChimneyMode valuecolor=[OFF="silver", ON="black"]
+ }
+}
+```
+
+`comfoair_bypass.map` file:
+
+```
+1=Opened
+0=Closed
+undefined=unknown
+-=unknown
+```
+
+`comfoair_on-off.map` file:
+
+```
+ON=active
+OFF=inactive
+undefined=unknown
+-=unknown
+```
+
+`comfoair_freeze.map` file:
+
+```
+ON=frozen
+OFF=OK
+undefined=unknown
+-=unknown
+```
+
+## Control Protocol Reference
+
+For reference the protocol description used can be found here (german version only):
+[Protokollbeschreibung Zehnder ComfoAir](http://www.see-solutions.de/sonstiges/Protokollbeschreibung_ComfoAir.pdf)
diff --git a/bundles/org.openhab.binding.comfoair/pom.xml b/bundles/org.openhab.binding.comfoair/pom.xml
new file mode 100644
index 0000000000000..c48322f8cb8f6
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.9-SNAPSHOT
+
+
+ org.openhab.binding.comfoair
+
+ openHAB Add-ons :: Bundles :: ComfoAir Binding
+
+
diff --git a/bundles/org.openhab.binding.comfoair/src/main/feature/feature.xml b/bundles/org.openhab.binding.comfoair/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..31b85d6e45e72
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/feature/feature.xml
@@ -0,0 +1,10 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ openhab-transport-serial
+ mvn:org.openhab.addons.bundles/org.openhab.binding.comfoair/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirBindingConstants.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirBindingConstants.java
new file mode 100644
index 0000000000000..07668c30e7e2d
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirBindingConstants.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+/**
+ * The {@link ComfoAirBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Hans Böhm - Initial contribution
+ */
+@NonNullByDefault
+public class ComfoAirBindingConstants {
+
+ private static final String BINDING_ID = "comfoair";
+
+ public static final ThingTypeUID THING_TYPE_COMFOAIR_GENERIC = new ThingTypeUID(BINDING_ID, "comfoair");
+
+ // Thing properties
+ // General properties
+ public static final String PROPERTY_SOFTWARE_MAIN_VERSION = "SOFTWARE_VERSION_MAIN";
+ public static final String PROPERTY_SOFTWARE_MINOR_VERSION = "SOFTWARE_VERSION_MINOR";
+ public static final String PROPERTY_DEVICE_NAME = "DEVICE_NAME";
+ // Installed options
+ public static final String PROPERTY_OPTION_PREHEATER = "OPTION_PREHEATER";
+ public static final String PROPERTY_OPTION_BYPASS = "OPTION_BYPASS";
+ public static final String PROPERTY_OPTION_RECU_TYPE = "DEVICE_TYPE";
+ public static final String PROPERTY_OPTION_RECU_SIZE = "DEVICE_SIZE";
+ public static final String PROPERTY_OPTION_CHIMNEY = "OPTION_CHIMNEY";
+ public static final String PROPERTY_OPTION_COOKERHOOD = "OPTION_COOKERHOOD";
+ public static final String PROPERTY_OPTION_HEATER = "OPTION_HEATER";
+ public static final String PROPERTY_OPTION_ENTHALPY = "OPTION_ENTHALPY";
+ public static final String PROPERTY_OPTION_EWT = "OPTION_EWT";
+ // Common option states
+ public static final String[] COMMON_OPTION_STATES = { "Not installed", "Installed" };
+
+ // Channel groups
+ public static final String CG_CONTROL_PREFIX = "bindingControl#";
+ public static final String CG_VENTILATION_PREFIX = "ventilation#";
+ public static final String CG_TEMPS_PREFIX = "temperatures#";
+ public static final String CG_TIMES_PREFIX = "times#";
+ public static final String CG_BYPASS_PREFIX = "bypass#";
+ public static final String CG_PREHEATER_PREFIX = "preheater#";
+ public static final String CG_EWT_PREFIX = "ewt#";
+ public static final String CG_HEATER_PREFIX = "heater#";
+ public static final String CG_COOKERHOOD_PREFIX = "cookerhood#";
+ public static final String CG_ENTHALPY_PREFIX = "enthalpy#";
+ public static final String CG_OPTIONS_PREFIX = "options#";
+ public static final String CG_MENUP1_PREFIX = "menuP1#";
+ public static final String CG_MENUP2_PREFIX = "menuP2#";
+ public static final String CG_MENUP9_PREFIX = "menuP9#";
+ public static final String CG_INPUTS_PREFIX = "inputs#";
+ public static final String CG_ANALOG1_PREFIX = "analog1#";
+ public static final String CG_ANALOG2_PREFIX = "analog2#";
+ public static final String CG_ANALOG3_PREFIX = "analog3#";
+ public static final String CG_ANALOG4_PREFIX = "analog4#";
+ public static final String CG_ANALOGRF_PREFIX = "analogRF#";
+ public static final String CG_ERRORS_PREFIX = "errors#";
+ public static final String CG_RESETS_PREFIX = "resets#";
+
+ // Channels
+ // Control channels
+ public static final String CHANNEL_ACTIVATE = "activate";
+ // Ventilation channels
+ public static final String CHANNEL_FAN_LEVEL = "fanLevel";
+ public static final String CHANNEL_FAN_OUT_0 = "fanOut0";
+ public static final String CHANNEL_FAN_OUT_1 = "fanOut1";
+ public static final String CHANNEL_FAN_OUT_2 = "fanOut2";
+ public static final String CHANNEL_FAN_OUT_3 = "fanOut3";
+ public static final String CHANNEL_FAN_IN_0 = "fanIn0";
+ public static final String CHANNEL_FAN_IN_1 = "fanIn1";
+ public static final String CHANNEL_FAN_IN_2 = "fanIn2";
+ public static final String CHANNEL_FAN_IN_3 = "fanIn3";
+ public static final String CHANNEL_FAN_IN_PERCENT = "fanInPercent";
+ public static final String CHANNEL_FAN_OUT_PERCENT = "fanOutPercent";
+ public static final String CHANNEL_FAN_IN_RPM = "fanInRPM";
+ public static final String CHANNEL_FAN_OUT_RPM = "fanOutRPM";
+ // Temperature channels
+ public static final String CHANNEL_TEMP_TARGET = "targetTemperature";
+ public static final String CHANNEL_TEMP_OUTDOOR_IN = "outdoorTemperatureIn";
+ public static final String CHANNEL_TEMP_OUTDOOR_OUT = "outdoorTemperatureOut";
+ public static final String CHANNEL_TEMP_INDOOR_IN = "indoorTemperatureIn";
+ public static final String CHANNEL_TEMP_INDOOR_OUT = "indoorTemperatureOut";
+ public static final String CHANNEL_IS_SENSOR_T1 = "isT1Sensor";
+ public static final String CHANNEL_IS_SENSOR_T2 = "isT2Sensor";
+ public static final String CHANNEL_IS_SENSOR_T3 = "isT3Sensor";
+ public static final String CHANNEL_IS_SENSOR_T4 = "isT4Sensor";
+ public static final String CHANNEL_IS_SENSOR_EWT = "isEWTSensor";
+ public static final String CHANNEL_IS_SENSOR_HEATER = "isHeaterSensor";
+ public static final String CHANNEL_IS_SENSOR_COOKERHOOD = "isCookerhoodSensor";
+ public static final String CHANNEL_TEMP_EWT = "ewtTemperature";
+ public static final String CHANNEL_TEMP_HEATER = "heaterTemperature";
+ public static final String CHANNEL_TEMP_COOKERHOOD = "cookerhoodTemperature";
+ // Time channels
+ public static final String CHANNEL_TIME_LEVEL0 = "level0Time";
+ public static final String CHANNEL_TIME_LEVEL1 = "level1Time";
+ public static final String CHANNEL_TIME_LEVEL2 = "level2Time";
+ public static final String CHANNEL_TIME_LEVEL3 = "level3Time";
+ public static final String CHANNEL_TIME_FREEZE = "freezeTime";
+ public static final String CHANNEL_TIME_PREHEATER = "preheaterTime";
+ public static final String CHANNEL_TIME_BYPASS = "bypassTime";
+ public static final String CHANNEL_TIME_FILTER = "filterHours";
+ // Bypass channels
+ public static final String CHANNEL_BYPASS_FACTOR = "bypassFactor";
+ public static final String CHANNEL_BYPASS_LEVEL = "bypassLevel";
+ public static final String CHANNEL_BYPASS_CORRECTION = "bypassCorrection";
+ public static final String CHANNEL_BYPASS_SUMMER = "bypassSummer";
+ // Preheater channels
+ public static final String CHANNEL_PREHEATER_VALVE = "preheaterValve";
+ public static final String CHANNEL_PREHEATER_FROST_PROTECT = "preheaterFrostProtect";
+ public static final String CHANNEL_PREHEATER_HEATING = "preheaterHeating";
+ public static final String CHANNEL_PREHEATER_FROST_TIME = "preheaterFrostTime";
+ public static final String CHANNEL_PREHEATER_SAFETY = "preheaterSafety";
+ // EWT channels
+ public static final String CHANNEL_EWT_TEMP_LOW = "ewtTemperatureLow";
+ public static final String CHANNEL_EWT_TEMP_HIGH = "ewtTemperatureHigh";
+ public static final String CHANNEL_EWT_SPEED = "ewtSpeed";
+ // Heater channels
+ public static final String CHANNEL_HEATER_POWER = "heaterPower";
+ public static final String CHANNEL_HEATER_POWER_I = "heaterPowerI";
+ public static final String CHANNEL_HEATER_TEMP_TARGET = "heaterTargetTemperature";
+ // Cookerhood channels
+ public static final String CHANNEL_COOKERHOOD_SPEED = "cookerhoodSpeed";
+ // Enthalpy channels
+ public static final String CHANNEL_ENTHALPY_TEMP = "enthalpyTemperature";
+ public static final String CHANNEL_ENTHALPY_HUMIDITY = "enthalpyHumidity";
+ public static final String CHANNEL_ENTHALPY_LEVEL = "enthalpyLevel";
+ public static final String CHANNEL_ENTHALPY_TIME = "enthalpyTime";
+ // Menu P1 channels
+ public static final String CHANNEL_MENU20_MODE = "menu20Mode";
+ public static final String CHANNEL_MENU21_MODE = "menu21Mode";
+ public static final String CHANNEL_MENU22_MODE = "menu22Mode";
+ public static final String CHANNEL_MENU23_MODE = "menu23Mode";
+ public static final String CHANNEL_MENU24_MODE = "menu24Mode";
+ public static final String CHANNEL_MENU25_MODE = "menu25Mode";
+ public static final String CHANNEL_MENU26_MODE = "menu26Mode";
+ public static final String CHANNEL_MENU27_MODE = "menu27Mode";
+ public static final String CHANNEL_MENU28_MODE = "menu28Mode";
+ public static final String CHANNEL_MENU29_MODE = "menu29Mode";
+ // Menu P2 channels
+ public static final String CHANNEL_BR_START_DELAY = "bathroomStartDelay";
+ public static final String CHANNEL_BR_END_DELAY = "bathroomEndDelay";
+ public static final String CHANNEL_L1_END_DELAY = "L1EndDelay";
+ public static final String CHANNEL_PULSE_VENTILATION = "pulseVentilation";
+ public static final String CHANNEL_FILTER_WEEKS = "filterWeeks";
+ public static final String CHANNEL_RF_SHORT_DELAY = "RFShortDelay";
+ public static final String CHANNEL_RF_LONG_DELAY = "RFLongDelay";
+ public static final String CHANNEL_COOKERHOOD_DELAY = "cookerhoodDelay";
+ // Menu P9 channels
+ public static final String CHANNEL_CHIMNEY_STATE = "chimneyState";
+ public static final String CHANNEL_BYPASS_STATE = "bypassState";
+ public static final String CHANNEL_EWT_STATE = "ewtState";
+ public static final String CHANNEL_HEATER_STATE = "heaterState";
+ public static final String CHANNEL_VCONTROL_STATE = "vControlState";
+ public static final String CHANNEL_FROST_STATE = "frostState";
+ public static final String CHANNEL_COOKERHOOD_STATE = "cookerhoodState";
+ public static final String CHANNEL_ENTHALPY_STATE = "enthalpyState";
+ // Inputs channels
+ public static final String CHANNEL_IS_L1_SWITCH = "isL1Switch";
+ public static final String CHANNEL_IS_L2_SWITCH = "isL2Switch";
+ public static final String CHANNEL_IS_BATHROOM_SWITCH = "isBathroomSwitch";
+ public static final String CHANNEL_IS_COOKERHOOD_SWITCH = "isCookerhoodSwitch";
+ public static final String CHANNEL_IS_EXTERNAL_FILTER = "isExternalFilter";
+ public static final String CHANNEL_IS_WTW = "isWTW";
+ public static final String CHANNEL_IS_BATHROOM2_SWITCH = "isBathroom2Switch";
+ // Analog channels
+ public static final String CHANNEL_IS_ANALOG = "isAnalog";
+ public static final String CHANNEL_ANALOG_MODE = "analogMode";
+ public static final String CHANNEL_ANALOG_NEGATIVE = "analogNegative";
+ public static final String CHANNEL_ANALOG_VOLT = "analogVolt";
+ public static final String CHANNEL_ANALOG_MIN = "analogMin";
+ public static final String CHANNEL_ANALOG_MAX = "analogMax";
+ public static final String CHANNEL_ANALOG_VALUE = "analogValue";
+ public static final String CHANNEL_IS_RF = "isRF";
+ public static final String CHANNEL_RF_MODE = "RFMode";
+ public static final String CHANNEL_RF_NEGATIVE = "RFNegative";
+ public static final String CHANNEL_RF_MIN = "RFMin";
+ public static final String CHANNEL_RF_MAX = "RFMax";
+ public static final String CHANNEL_RF_VALUE = "RFValue";
+ public static final String CHANNEL_ANALOG_PRIORITY = "analogPriority";
+ // Error channels
+ public static final String CHANNEL_FILTER_ERROR = "filterError";
+ public static final String CHANNEL_ERRORS_CURRENT = "errorsCurrent";
+ public static final String CHANNEL_ERRORS_LAST = "errorsLast";
+ public static final String CHANNEL_ERRORS_PRELAST = "errorsPrelast";
+ public static final String CHANNEL_ERRORS_PREPRELAST = "errorsPrePrelast";
+ // Reset channels
+ public static final String CHANNEL_FILTER_RESET = "filterReset";
+ public static final String CHANNEL_ERROR_RESET = "errorReset";
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommand.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommand.java
new file mode 100644
index 0000000000000..7342654f5916d
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommand.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Class to encapsulate all data which is needed to send a cmd to comfoair
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public class ComfoAirCommand {
+
+ private final List keys;
+ private @Nullable Integer requestCmd;
+ private @Nullable Integer replyCmd;
+ private int[] requestData;
+ private final @Nullable Integer requestValue;
+ private final @Nullable Integer dataPosition;
+
+ /**
+ * @param key
+ * command key
+ * @param requestCmd
+ * command as byte value
+ * @param replyCmd
+ * reply command as byte value
+ * @param data
+ * request byte values
+ * @param requestValue
+ * request byte value
+ * @param dataPosition
+ * request byte position
+ */
+
+ public ComfoAirCommand(String key, @Nullable Integer requestCmd, @Nullable Integer replyCmd, int[] data,
+ @Nullable Integer dataPosition, @Nullable Integer requestValue) {
+ this.keys = new ArrayList();
+ this.keys.add(key);
+ this.requestCmd = requestCmd;
+ this.replyCmd = replyCmd;
+ this.requestData = data;
+ this.dataPosition = dataPosition;
+ this.requestValue = requestValue;
+ }
+
+ /*
+ * Constructor for basic read command
+ */
+ public ComfoAirCommand(String key) {
+ this.keys = new ArrayList();
+ this.keys.add(key);
+ ComfoAirCommandType commandType = ComfoAirCommandType.getCommandTypeByKey(key);
+ if (commandType != null) {
+ this.requestCmd = commandType.getReadCommand() == 0 ? null : commandType.getReadCommand();
+ this.replyCmd = commandType.getReadReplyCommand();
+ }
+ this.requestData = ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ this.dataPosition = null;
+ this.requestValue = null;
+ }
+
+ /**
+ * @param key
+ * additional command key
+ */
+ public void addKey(String key) {
+ keys.add(key);
+ }
+
+ /**
+ * @return command keys
+ */
+ public List getKeys() {
+ return keys;
+ }
+
+ /**
+ * @return command byte value
+ */
+ public @Nullable Integer getRequestCmd() {
+ return requestCmd;
+ }
+
+ /**
+ * @return request data as byte values
+ */
+ public int[] getRequestData() {
+ return requestData;
+ }
+
+ /**
+ * @return acknowledge cmd byte value
+ */
+ public @Nullable Integer getReplyCmd() {
+ return replyCmd;
+ }
+
+ /**
+ * @return request value as byte value
+ */
+ public @Nullable Integer getRequestValue() {
+ return requestValue;
+ }
+
+ /**
+ * @return position of request byte
+ */
+ public @Nullable Integer getDataPosition() {
+ return dataPosition;
+ }
+
+ /**
+ * set request command byte value
+ */
+ public void setRequestCmd(@Nullable Integer newRequestCmd) {
+ requestCmd = newRequestCmd;
+ }
+
+ /**
+ * set reply command byte value
+ */
+ public void setReplyCmd(@Nullable Integer newReplyCmd) {
+ replyCmd = newReplyCmd;
+ }
+
+ /**
+ * set request data byte values
+ */
+ public void setRequestData(int[] newRequestData) {
+ requestData = newRequestData;
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommandType.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommandType.java
new file mode 100644
index 0000000000000..97d21484364a0
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommandType.java
@@ -0,0 +1,908 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.datatypes.ComfoAirDataType;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeBoolean;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeMessage;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeNumber;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeRPM;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeTemperature;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeTime;
+import org.openhab.binding.comfoair.internal.datatypes.DataTypeVolt;
+
+/**
+ * Represents all valid commands which could be processed by this binding
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public enum ComfoAirCommandType {
+ /**
+ * Below all valid commands to change or read parameters from ComfoAir
+ *
+ * @param key
+ * command name
+ * @param dataType
+ * data type (can be: DataTypeBoolean.getInstance(), DataTypeMessage.getInstance(),
+ * DataTypeNumber.getInstance(), DataTypeRPM.getInstance(), DataTypeTemperature.getInstance(),
+ * DataTypeTime.getInstance(), DataTypeVolt.getInstance())
+ * @param possibleValues
+ * possible values for write command, if it can only take certain values
+ * @param changeCommand
+ * byte number for ComfoAir write command
+ * @param changeDataSize
+ * size of bytes list for ComfoAir write command
+ * @param changeDataPos
+ * position in bytes list to change
+ * @param changeAffected
+ * list of affected commands (can be empty)
+ * is mandatory for read-write command
+ * @param readCommand
+ * request byte number for ComfoAir read command
+ * @param readReplyCommand
+ * reply byte list size for ComfoAir read command (list of values only)
+ * @param readReplyDataPos
+ * list of byte positions in reply bytes list from ComfoAir
+ * @param readReplyDataBits
+ * byte value on readReplyDataPos position to be considered by command (used with
+ * DataTypeBoolean.class dataType)
+ */
+ ACTIVATE(ComfoAirBindingConstants.CG_CONTROL_PREFIX + ComfoAirBindingConstants.CHANNEL_ACTIVATE,
+ DataTypeBoolean.getInstance(), new int[] { 0x03 }, Constants.REQUEST_SET_RS232, 1, 0,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REPLY_SET_RS232, Constants.REPLY_SET_RS232, new int[] { 0 }, 0x03),
+ MENU20_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU20_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x01),
+ MENU21_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU21_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x02),
+ MENU22_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU22_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x04),
+ MENU23_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU23_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x08),
+ MENU24_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU24_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x10),
+ MENU25_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU25_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x20),
+ MENU26_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU26_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x40),
+ MENU27_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU27_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 6 },
+ 0x80),
+ MENU28_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU28_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 7 },
+ 0x01),
+ MENU29_MODE(ComfoAirBindingConstants.CG_MENUP1_PREFIX + ComfoAirBindingConstants.CHANNEL_MENU29_MODE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 7 },
+ 0x02),
+ BATHROOM_START_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_BR_START_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 0, new ComfoAirCommandType[] { MENU21_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 0 }),
+ BATHROOM_END_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_BR_END_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 1, new ComfoAirCommandType[] { MENU22_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 1 }),
+ L1_END_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_L1_END_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 2, new ComfoAirCommandType[] { MENU27_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 2 }),
+ PULSE_VENTILATION(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_PULSE_VENTILATION,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 3, new ComfoAirCommandType[] { MENU23_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 3 }),
+ FILTER_WEEKS(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_FILTER_WEEKS,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 4, new ComfoAirCommandType[] { MENU24_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 4 }),
+ RF_SHORT_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_SHORT_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 5, new ComfoAirCommandType[] { MENU25_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 5 }),
+ RF_LONG_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_LONG_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 6, new ComfoAirCommandType[] { MENU26_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 6 }),
+ COOKERHOOD_DELAY(ComfoAirBindingConstants.CG_MENUP2_PREFIX + ComfoAirBindingConstants.CHANNEL_COOKERHOOD_DELAY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_DELAYS, 8, 7, new ComfoAirCommandType[] { MENU20_MODE },
+ Constants.REQUEST_GET_DELAYS, Constants.REPLY_GET_DELAYS, new int[] { 7 }),
+ CHIMNEY_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_CHIMNEY_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x01),
+ BYPASS_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_BYPASS_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x02),
+ EWT_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x04),
+ HEATER_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_HEATER_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x08),
+ V_CONTROL_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_VCONTROL_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x10),
+ FROST_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_FROST_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x20),
+ COOKERHOOD_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_COOKERHOOD_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x40),
+ ENTHALPY_STATE(ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_ENTHALPY_STATE,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 8 },
+ 0x80),
+ FAN_IN_PERCENT(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_PERCENT,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_FAN, Constants.REPLY_GET_FAN, new int[] { 0 }),
+ FAN_OUT_PERCENT(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_PERCENT,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_FAN, Constants.REPLY_GET_FAN, new int[] { 1 }),
+ FAN_IN_RPM(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_RPM,
+ DataTypeRPM.getInstance(), Constants.REQUEST_GET_FAN, Constants.REPLY_GET_FAN, new int[] { 2, 3 }),
+ FAN_OUT_RPM(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_RPM,
+ DataTypeRPM.getInstance(), Constants.REQUEST_GET_FAN, Constants.REPLY_GET_FAN, new int[] { 4, 5 }),
+ FAN_OUT_0(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_0,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 0,
+ new ComfoAirCommandType[] { FAN_OUT_PERCENT, FAN_OUT_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 0 }),
+ FAN_OUT_1(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_1,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 1,
+ new ComfoAirCommandType[] { FAN_OUT_PERCENT, FAN_OUT_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 1 }),
+ FAN_OUT_2(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_2,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 2,
+ new ComfoAirCommandType[] { FAN_OUT_PERCENT, FAN_OUT_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 2 }),
+ FAN_OUT_3(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_OUT_3,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 6,
+ new ComfoAirCommandType[] { FAN_OUT_PERCENT, FAN_OUT_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 10 }),
+ FAN_IN_0(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_0,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 3,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_IN_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 3 }),
+ FAN_IN_1(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_1,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 4,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_IN_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 4 }),
+ FAN_IN_2(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_2,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 5,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_IN_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 5 }),
+ FAN_IN_3(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_IN_3,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_FAN_LEVEL, 9, 7,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_IN_RPM }, Constants.REQUEST_GET_FAN_LEVEL,
+ Constants.REPLY_GET_FAN_LEVEL, new int[] { 11 }),
+ FAN_LEVEL(ComfoAirBindingConstants.CG_VENTILATION_PREFIX + ComfoAirBindingConstants.CHANNEL_FAN_LEVEL,
+ DataTypeNumber.getInstance(), new int[] { 0x01, 0x02, 0x03, 0x04 }, Constants.REQUEST_SET_LEVEL, 1, 0,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_IN_RPM, FAN_OUT_PERCENT, FAN_OUT_RPM },
+ Constants.REQUEST_GET_FAN_LEVEL, Constants.REPLY_GET_FAN_LEVEL, new int[] { 8 }),
+ LEVEL0_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_LEVEL0,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 0, 1, 2 }),
+ LEVEL1_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_LEVEL1,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 3, 4, 5 }),
+ LEVEL2_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_LEVEL2,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 6, 7, 8 }),
+ LEVEL3_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_LEVEL3,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS,
+ new int[] { 17, 18, 19 }),
+ FREEZE_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_FREEZE,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 9, 10 }),
+ PREHEATER_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_PREHEATER,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 11, 12 }),
+ BYPASS_TIME(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_BYPASS,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 13, 14 }),
+ BYPASS_FACTOR(ComfoAirBindingConstants.CG_BYPASS_PREFIX + ComfoAirBindingConstants.CHANNEL_BYPASS_FACTOR,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_BYPASS, Constants.REPLY_GET_BYPASS, new int[] { 2 }),
+ BYPASS_LEVEL(ComfoAirBindingConstants.CG_BYPASS_PREFIX + ComfoAirBindingConstants.CHANNEL_BYPASS_LEVEL,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_BYPASS, Constants.REPLY_GET_BYPASS, new int[] { 3 }),
+ BYPASS_CORRECTION(ComfoAirBindingConstants.CG_BYPASS_PREFIX + ComfoAirBindingConstants.CHANNEL_BYPASS_CORRECTION,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_BYPASS, Constants.REPLY_GET_BYPASS, new int[] { 4 }),
+ BYPASS_SUMMER(ComfoAirBindingConstants.CG_BYPASS_PREFIX + ComfoAirBindingConstants.CHANNEL_BYPASS_SUMMER,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_BYPASS, Constants.REPLY_GET_BYPASS, new int[] { 6 }),
+ ENTHALPY_TEMPERATURE(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX + ComfoAirBindingConstants.CHANNEL_ENTHALPY_TEMP,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_SENSORS, Constants.REPLY_GET_SENSORS,
+ new int[] { 0 }),
+ ENTHALPY_HUMIDITY(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX + ComfoAirBindingConstants.CHANNEL_ENTHALPY_HUMIDITY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_SENSORS, Constants.REPLY_GET_SENSORS, new int[] { 1 }),
+ ENTHALPY_LEVEL(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX + ComfoAirBindingConstants.CHANNEL_ENTHALPY_LEVEL,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_SENSORS, Constants.REPLY_GET_SENSORS, new int[] { 4 }),
+ ENTHALPY_TIME(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX + ComfoAirBindingConstants.CHANNEL_ENTHALPY_TIME,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_SENSORS, Constants.REPLY_GET_SENSORS, new int[] { 5 }),
+ PREHEATER_VALVE(ComfoAirBindingConstants.CG_PREHEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_PREHEATER_VALVE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_PREHEATER, Constants.REPLY_GET_PREHEATER,
+ new int[] { 0 }),
+ PREHEATER_FROST_PROTECT(
+ ComfoAirBindingConstants.CG_PREHEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_PREHEATER_FROST_PROTECT,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_PREHEATER, Constants.REPLY_GET_PREHEATER,
+ new int[] { 1 }),
+ PREHEATER_HEATING(ComfoAirBindingConstants.CG_PREHEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_PREHEATER_HEATING,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_PREHEATER, Constants.REPLY_GET_PREHEATER,
+ new int[] { 2 }),
+ PREHEATER_FROST_TIME(
+ ComfoAirBindingConstants.CG_PREHEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_PREHEATER_FROST_TIME,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_PREHEATER, Constants.REPLY_GET_PREHEATER,
+ new int[] { 3, 4 }),
+ PREHEATER_OPTION(ComfoAirBindingConstants.CG_PREHEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_PREHEATER_SAFETY,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_PREHEATER, Constants.REPLY_GET_PREHEATER,
+ new int[] { 5 }),
+ TARGET_TEMPERATURE(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_TARGET,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_SET_TEMPS, 1, 0,
+ new ComfoAirCommandType[] { BYPASS_FACTOR, BYPASS_LEVEL, BYPASS_SUMMER }, Constants.REQUEST_GET_TEMPS,
+ Constants.REPLY_GET_TEMPS, new int[] { 0 }),
+ OUTDOOR_TEMPERATURE_IN(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_OUTDOOR_IN,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 1 }),
+ OUTDOOR_TEMPERATURE_OUT(
+ ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_OUTDOOR_OUT,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 4 }),
+ INDOOR_TEMPERATURE_IN(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_INDOOR_IN,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 2 }),
+ INDOOR_TEMPERATURE_OUT(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_INDOOR_OUT,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 3 }),
+ IS_T1_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_T1,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x01),
+ IS_T2_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_T2,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x02),
+ IS_T3_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_T3,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x04),
+ IS_T4_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_T4,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x08),
+ IS_EWT_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_EWT,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x10),
+ IS_HEATER_SENSOR(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_HEATER,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x20),
+ IS_COOKERHOOD_SENSOR(
+ ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_SENSOR_COOKERHOOD,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 5 },
+ 0x40),
+ EWT_TEMPERATURE(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_EWT,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 6 }),
+ HEATER_TEMPERATURE(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_HEATER,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 7 }),
+ COOKERHOOD_TEMPERATURE(ComfoAirBindingConstants.CG_TEMPS_PREFIX + ComfoAirBindingConstants.CHANNEL_TEMP_COOKERHOOD,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_GET_TEMPS, Constants.REPLY_GET_TEMPS, new int[] { 8 }),
+ EWT_SPEED(ComfoAirBindingConstants.CG_EWT_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_SPEED,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_EWT, 5, 2,
+ new ComfoAirCommandType[] { EWT_STATE, EWT_TEMPERATURE }, Constants.REQUEST_GET_EWT,
+ Constants.REPLY_GET_EWT, new int[] { 2 }),
+ EWT_TEMPERATURE_LOW(ComfoAirBindingConstants.CG_EWT_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_TEMP_LOW,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_SET_EWT, 5, 0, new ComfoAirCommandType[] { EWT_STATE },
+ Constants.REQUEST_GET_EWT, Constants.REPLY_GET_EWT, new int[] { 0 }),
+ EWT_TEMPERATURE_HIGH(ComfoAirBindingConstants.CG_EWT_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_TEMP_HIGH,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_SET_EWT, 5, 1, new ComfoAirCommandType[] { EWT_STATE },
+ Constants.REQUEST_GET_EWT, Constants.REPLY_GET_EWT, new int[] { 1 }),
+ COOKERHOOD_SPEED(ComfoAirBindingConstants.CG_COOKERHOOD_PREFIX + ComfoAirBindingConstants.CHANNEL_COOKERHOOD_SPEED,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_EWT, 5, 3,
+ new ComfoAirCommandType[] { COOKERHOOD_STATE, COOKERHOOD_TEMPERATURE }, Constants.REQUEST_GET_EWT,
+ Constants.REPLY_GET_EWT, new int[] { 3 }),
+ HEATER_POWER(ComfoAirBindingConstants.CG_HEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_HEATER_POWER,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_EWT, Constants.REPLY_GET_EWT, new int[] { 4 }),
+ HEATER_POWER_I(ComfoAirBindingConstants.CG_HEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_HEATER_POWER_I,
+ DataTypeNumber.getInstance(), Constants.REQUEST_GET_EWT, Constants.REPLY_GET_EWT, new int[] { 5 }),
+ HEATER_TARGET_TEMPERATURE(
+ ComfoAirBindingConstants.CG_HEATER_PREFIX + ComfoAirBindingConstants.CHANNEL_HEATER_TEMP_TARGET,
+ DataTypeTemperature.getInstance(), Constants.REQUEST_SET_EWT, 5, 4,
+ new ComfoAirCommandType[] { HEATER_STATE, HEATER_POWER, HEATER_TEMPERATURE }, Constants.REQUEST_GET_EWT,
+ Constants.REPLY_GET_EWT, new int[] { 6 }),
+ IS_PREHEATER(ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER, DataTypeBoolean.getInstance(),
+ Constants.REQUEST_SET_STATES, 8, 0,
+ new ComfoAirCommandType[] { OUTDOOR_TEMPERATURE_IN, INDOOR_TEMPERATURE_IN, PREHEATER_FROST_PROTECT,
+ PREHEATER_FROST_TIME, PREHEATER_HEATING, FROST_STATE, PREHEATER_OPTION, PREHEATER_TIME,
+ PREHEATER_VALVE },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 0 }),
+ IS_BYPASS(ComfoAirBindingConstants.PROPERTY_OPTION_BYPASS, DataTypeBoolean.getInstance(),
+ Constants.REQUEST_SET_STATES, 8, 1,
+ new ComfoAirCommandType[] { INDOOR_TEMPERATURE_IN, OUTDOOR_TEMPERATURE_OUT }, Constants.REQUEST_GET_STATES,
+ Constants.REPLY_GET_STATES, new int[] { 1 }),
+ RECU_TYPE(ComfoAirBindingConstants.PROPERTY_OPTION_RECU_TYPE, DataTypeNumber.getInstance(),
+ new int[] { 0x01, 0x02 }, Constants.REQUEST_SET_STATES, 8, 2,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_OUT_PERCENT, INDOOR_TEMPERATURE_IN, INDOOR_TEMPERATURE_OUT,
+ OUTDOOR_TEMPERATURE_IN, OUTDOOR_TEMPERATURE_OUT },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 2 }),
+ RECU_SIZE(ComfoAirBindingConstants.PROPERTY_OPTION_RECU_SIZE, DataTypeNumber.getInstance(),
+ new int[] { 0x01, 0x02 }, Constants.REQUEST_SET_STATES, 8, 3,
+ new ComfoAirCommandType[] { FAN_IN_PERCENT, FAN_OUT_PERCENT, FAN_IN_0, FAN_IN_1, FAN_IN_2, FAN_IN_3,
+ FAN_OUT_0, FAN_OUT_1, FAN_OUT_2, FAN_OUT_3 },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 3 }),
+ IS_CHIMNEY(ComfoAirBindingConstants.PROPERTY_OPTION_CHIMNEY, DataTypeBoolean.getInstance(), new int[] { 0x01 },
+ Constants.REQUEST_SET_STATES, 8, 4, Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_STATES,
+ Constants.REPLY_GET_STATES, new int[] { 4 }, 0x01),
+ IS_COOKERHOOD(ComfoAirBindingConstants.PROPERTY_OPTION_COOKERHOOD, DataTypeBoolean.getInstance(),
+ new int[] { 0x02 }, Constants.REQUEST_SET_STATES, 8, 4,
+ new ComfoAirCommandType[] { COOKERHOOD_DELAY, COOKERHOOD_STATE, COOKERHOOD_SPEED, COOKERHOOD_TEMPERATURE },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 4 }, 0x02),
+ IS_HEATER(ComfoAirBindingConstants.PROPERTY_OPTION_HEATER, DataTypeBoolean.getInstance(), new int[] { 0x04 },
+ Constants.REQUEST_SET_STATES, 8, 4,
+ new ComfoAirCommandType[] { HEATER_TARGET_TEMPERATURE, HEATER_POWER, HEATER_STATE, HEATER_POWER_I,
+ HEATER_TEMPERATURE },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 4 }, 0x04),
+ IS_ENTHALPY(ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY, DataTypeNumber.getInstance(),
+ new int[] { 0x00, 0x01, 0x02 }, Constants.REQUEST_SET_STATES, 8, 6,
+ new ComfoAirCommandType[] { ENTHALPY_TEMPERATURE, ENTHALPY_HUMIDITY, ENTHALPY_LEVEL, ENTHALPY_STATE,
+ ENTHALPY_TIME },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 9 }),
+ IS_EWT(ComfoAirBindingConstants.PROPERTY_OPTION_EWT, DataTypeNumber.getInstance(), new int[] { 0x00, 0x01, 0x02 },
+ Constants.REQUEST_SET_STATES, 8, 7,
+ new ComfoAirCommandType[] { EWT_SPEED, EWT_TEMPERATURE_LOW, EWT_TEMPERATURE_HIGH, EWT_STATE,
+ EWT_TEMPERATURE },
+ Constants.REQUEST_GET_STATES, Constants.REPLY_GET_STATES, new int[] { 10 }),
+ SOFTWARE_MAIN_VERSION(ComfoAirBindingConstants.PROPERTY_SOFTWARE_MAIN_VERSION, DataTypeNumber.getInstance(),
+ Constants.REQUEST_GET_FIRMWARE, Constants.REPLY_GET_FIRMWARE, new int[] { 0 }),
+ SOFTWARE_MINOR_VERSION(ComfoAirBindingConstants.PROPERTY_SOFTWARE_MINOR_VERSION, DataTypeNumber.getInstance(),
+ Constants.REQUEST_GET_FIRMWARE, Constants.REPLY_GET_FIRMWARE, new int[] { 1 }),
+ DEVICE_NAME(ComfoAirBindingConstants.PROPERTY_DEVICE_NAME, DataTypeNumber.getInstance(),
+ Constants.REQUEST_GET_FIRMWARE, Constants.REPLY_GET_FIRMWARE,
+ new int[] { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }),
+ ERRORS_CURRENT(ComfoAirBindingConstants.CG_ERRORS_PREFIX + ComfoAirBindingConstants.CHANNEL_ERRORS_CURRENT,
+ DataTypeMessage.getInstance(), Constants.REQUEST_GET_ERRORS, Constants.REPLY_GET_ERRORS,
+ new int[] { 0, 1, 9, 13 }),
+ ERRORS_LAST(ComfoAirBindingConstants.CG_ERRORS_PREFIX + ComfoAirBindingConstants.CHANNEL_ERRORS_LAST,
+ DataTypeMessage.getInstance(), Constants.REQUEST_GET_ERRORS, Constants.REPLY_GET_ERRORS,
+ new int[] { 2, 3, 10, 14 }),
+ ERRORS_PRELAST(ComfoAirBindingConstants.CG_ERRORS_PREFIX + ComfoAirBindingConstants.CHANNEL_ERRORS_PRELAST,
+ DataTypeMessage.getInstance(), Constants.REQUEST_GET_ERRORS, Constants.REPLY_GET_ERRORS,
+ new int[] { 4, 5, 11, 15 }),
+ ERRORS_PREPRELAST(ComfoAirBindingConstants.CG_ERRORS_PREFIX + ComfoAirBindingConstants.CHANNEL_ERRORS_PREPRELAST,
+ DataTypeMessage.getInstance(), Constants.REQUEST_GET_ERRORS, Constants.REPLY_GET_ERRORS,
+ new int[] { 6, 7, 12, 16 }),
+ ERROR_RESET(ComfoAirBindingConstants.CG_RESETS_PREFIX + ComfoAirBindingConstants.CHANNEL_ERROR_RESET,
+ DataTypeNumber.getInstance(), new int[] { 0x01 }, Constants.REQUEST_SET_RESETS, 4, 0,
+ new ComfoAirCommandType[] { ERRORS_CURRENT, ERRORS_LAST, ERRORS_PRELAST, ERRORS_PREPRELAST }),
+ FILTER_ERROR(ComfoAirBindingConstants.CG_ERRORS_PREFIX + ComfoAirBindingConstants.CHANNEL_FILTER_ERROR,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_ERRORS, Constants.REPLY_GET_ERRORS, new int[] { 8 },
+ 0x01),
+ FILTER_HOURS(ComfoAirBindingConstants.CG_TIMES_PREFIX + ComfoAirBindingConstants.CHANNEL_TIME_FILTER,
+ DataTypeTime.getInstance(), Constants.REQUEST_GET_HOURS, Constants.REPLY_GET_HOURS, new int[] { 15, 16 }),
+ FILTER_RESET(ComfoAirBindingConstants.CG_RESETS_PREFIX + ComfoAirBindingConstants.CHANNEL_FILTER_RESET,
+ DataTypeNumber.getInstance(), new int[] { 0x01 }, Constants.REQUEST_SET_RESETS, 4, 3,
+ new ComfoAirCommandType[] { FILTER_HOURS, FILTER_ERROR }),
+ ANALOG1_NEGATIVE(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_NEGATIVE,
+ DataTypeBoolean.getInstance(), new int[] { 0x01 }, Constants.REQUEST_SET_ANALOGS, 19, 2,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 2 },
+ 0x01),
+ ANALOG2_NEGATIVE(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_NEGATIVE,
+ DataTypeBoolean.getInstance(), new int[] { 0x02 }, Constants.REQUEST_SET_ANALOGS, 19, 2,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 2 },
+ 0x02),
+ ANALOG3_NEGATIVE(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_NEGATIVE,
+ DataTypeBoolean.getInstance(), new int[] { 0x04 }, Constants.REQUEST_SET_ANALOGS, 19, 2,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 2 },
+ 0x04),
+ ANALOG4_NEGATIVE(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_NEGATIVE,
+ DataTypeBoolean.getInstance(), new int[] { 0x08 }, Constants.REQUEST_SET_ANALOGS, 19, 2,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 2 },
+ 0x08),
+ RF_NEGATIVE(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_NEGATIVE,
+ DataTypeBoolean.getInstance(), new int[] { 0x10 }, Constants.REQUEST_SET_ANALOGS, 19, 2,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 2 },
+ 0x10),
+ ANALOG1_MIN(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MIN,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 3, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 3 }),
+ ANALOG1_MAX(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MAX,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 4, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 4 }),
+ ANALOG1_VALUE(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VALUE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 5, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 5 }),
+ ANALOG2_MIN(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MIN,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 6, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 6 }),
+ ANALOG2_MAX(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MAX,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 7, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 7 }),
+ ANALOG2_VALUE(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VALUE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 8, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 8 }),
+ ANALOG3_MIN(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MIN,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 9, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 9 }),
+ ANALOG3_MAX(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MAX,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 10, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 10 }),
+ ANALOG3_VALUE(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VALUE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 11, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 11 }),
+ ANALOG4_MIN(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MIN,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 12, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 12 }),
+ ANALOG4_MAX(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MAX,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 13, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 13 }),
+ ANALOG4_VALUE(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VALUE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 14, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 14 }),
+ RF_MIN(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_MIN,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 15, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 15 }),
+ RF_MAX(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_MAX,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 16, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 16 }),
+ RF_VALUE(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_VALUE,
+ DataTypeNumber.getInstance(), Constants.REQUEST_SET_ANALOGS, 19, 17, Constants.EMPTY_TYPE_ARRAY,
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 17 }),
+ ANALOG_MODE(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_PRIORITY,
+ DataTypeNumber.getInstance(), new int[] { 0x00, 0x01 }, Constants.REQUEST_SET_ANALOGS, 19, 18,
+ Constants.EMPTY_TYPE_ARRAY, Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 18 }),
+ ANALOG1_VOLT(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VOLT,
+ DataTypeVolt.getInstance(), Constants.REQUEST_GET_ANALOG_VOLTS, Constants.REPLY_GET_ANALOG_VOLTS,
+ new int[] { 0 }),
+ ANALOG2_VOLT(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VOLT,
+ DataTypeVolt.getInstance(), Constants.REQUEST_GET_ANALOG_VOLTS, Constants.REPLY_GET_ANALOG_VOLTS,
+ new int[] { 1 }),
+ ANALOG3_VOLT(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VOLT,
+ DataTypeVolt.getInstance(), Constants.REQUEST_GET_ANALOG_VOLTS, Constants.REPLY_GET_ANALOG_VOLTS,
+ new int[] { 2 }),
+ ANALOG4_VOLT(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_VOLT,
+ DataTypeVolt.getInstance(), Constants.REQUEST_GET_ANALOG_VOLTS, Constants.REPLY_GET_ANALOG_VOLTS,
+ new int[] { 3 }),
+ ANALOG1_MODE(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MODE,
+ DataTypeBoolean.getInstance(), new int[] { 0x01 }, Constants.REQUEST_SET_ANALOGS, 19, 1,
+ new ComfoAirCommandType[] { ANALOG1_NEGATIVE, ANALOG1_MIN, ANALOG1_MAX, ANALOG1_VALUE, ANALOG1_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 1 }, 0x01),
+ ANALOG2_MODE(ComfoAirBindingConstants.CG_ANALOG2_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MODE,
+ DataTypeBoolean.getInstance(), new int[] { 0x02 }, Constants.REQUEST_SET_ANALOGS, 19, 1,
+ new ComfoAirCommandType[] { ANALOG2_NEGATIVE, ANALOG2_MIN, ANALOG2_MAX, ANALOG2_VALUE, ANALOG2_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 1 }, 0x02),
+ ANALOG3_MODE(ComfoAirBindingConstants.CG_ANALOG3_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MODE,
+ DataTypeBoolean.getInstance(), new int[] { 0x04 }, Constants.REQUEST_SET_ANALOGS, 19, 1,
+ new ComfoAirCommandType[] { ANALOG3_NEGATIVE, ANALOG3_MIN, ANALOG3_MAX, ANALOG3_VALUE, ANALOG3_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 1 }, 0x04),
+ ANALOG4_MODE(ComfoAirBindingConstants.CG_ANALOG4_PREFIX + ComfoAirBindingConstants.CHANNEL_ANALOG_MODE,
+ DataTypeBoolean.getInstance(), new int[] { 0x08 }, Constants.REQUEST_SET_ANALOGS, 19, 1,
+ new ComfoAirCommandType[] { ANALOG4_NEGATIVE, ANALOG4_MIN, ANALOG4_MAX, ANALOG4_VALUE, ANALOG4_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 1 }, 0x08),
+ RF_MODE(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_RF_MODE,
+ DataTypeBoolean.getInstance(), new int[] { 0x10 }, Constants.REQUEST_SET_ANALOGS, 19, 1,
+ new ComfoAirCommandType[] { RF_NEGATIVE, RF_MIN, RF_MAX, RF_VALUE }, Constants.REQUEST_GET_ANALOGS,
+ Constants.REPLY_GET_ANALOGS, new int[] { 1 }, 0x10),
+ IS_L1_SWITCH(ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_L1_SWITCH,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 0 },
+ 0x01),
+ IS_L2_SWITCH(ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_L2_SWITCH,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 0 },
+ 0x02),
+ IS_BATHROOM_SWITCH(ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_BATHROOM_SWITCH,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 1 },
+ 0x01),
+ IS_COOKERHOOD_SWITCH(
+ ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_COOKERHOOD_SWITCH,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 1 },
+ 0x02),
+ IS_EXTERNAL_FILTER(ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_EXTERNAL_FILTER,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 1 },
+ 0x04),
+ IS_WTW(ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_WTW,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 1 },
+ 0x08),
+ IS_BATHROOM2_SWITCH(
+ ComfoAirBindingConstants.CG_INPUTS_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_BATHROOM2_SWITCH,
+ DataTypeBoolean.getInstance(), Constants.REQUEST_GET_INPUTS, Constants.REPLY_GET_INPUTS, new int[] { 1 },
+ 0x10),
+ IS_ANALOG1(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_ANALOG,
+ DataTypeBoolean.getInstance(), new int[] { 0x01 }, Constants.REQUEST_SET_ANALOGS, 19, 0,
+ new ComfoAirCommandType[] { ANALOG1_MODE, ANALOG1_NEGATIVE, ANALOG1_MIN, ANALOG1_MAX, ANALOG1_VALUE,
+ ANALOG1_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 0 }, 0x01),
+ IS_ANALOG2(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_ANALOG,
+ DataTypeBoolean.getInstance(), new int[] { 0x02 }, Constants.REQUEST_SET_ANALOGS, 19, 0,
+ new ComfoAirCommandType[] { ANALOG2_MODE, ANALOG2_NEGATIVE, ANALOG2_MIN, ANALOG2_MAX, ANALOG2_VALUE,
+ ANALOG2_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 0 }, 0x02),
+ IS_ANALOG3(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_ANALOG,
+ DataTypeBoolean.getInstance(), new int[] { 0x04 }, Constants.REQUEST_SET_ANALOGS, 19, 0,
+ new ComfoAirCommandType[] { ANALOG3_MODE, ANALOG3_NEGATIVE, ANALOG3_MIN, ANALOG3_MAX, ANALOG3_VALUE,
+ ANALOG3_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 0 }, 0x04),
+ IS_ANALOG4(ComfoAirBindingConstants.CG_ANALOG1_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_ANALOG,
+ DataTypeBoolean.getInstance(), new int[] { 0x08 }, Constants.REQUEST_SET_ANALOGS, 19, 0,
+ new ComfoAirCommandType[] { ANALOG4_MODE, ANALOG4_NEGATIVE, ANALOG4_MIN, ANALOG4_MAX, ANALOG4_VALUE,
+ ANALOG4_VOLT },
+ Constants.REQUEST_GET_ANALOGS, Constants.REPLY_GET_ANALOGS, new int[] { 0 }, 0x08),
+ IS_RF(ComfoAirBindingConstants.CG_ANALOGRF_PREFIX + ComfoAirBindingConstants.CHANNEL_IS_RF,
+ DataTypeBoolean.getInstance(), new int[] { 0x10 }, Constants.REQUEST_SET_ANALOGS, 19, 0,
+ new ComfoAirCommandType[] { RF_MODE, RF_NEGATIVE, RF_MIN, RF_MAX, RF_VALUE }, Constants.REQUEST_GET_ANALOGS,
+ Constants.REPLY_GET_ANALOGS, new int[] { 0 }, 0x10);
+
+ private final String key;
+ private final ComfoAirDataType dataType;
+
+ /*
+ * Possible values
+ */
+ private final int @Nullable [] possibleValues;
+
+ /*
+ * Cmd code to change properties on the comfoair.
+ */
+ private final int changeCommand;
+
+ /*
+ * The size of the data block.
+ */
+ private final int changeDataSize;
+
+ /*
+ * The byte inside the data block which holds the crucial value.
+ */
+ private final int changeDataPos;
+
+ /*
+ * Affected commands which should be refreshed after a successful change
+ * command call.
+ */
+ private final ComfoAirCommandType @Nullable [] changeAffected;
+
+ /*
+ * Command for reading properties.
+ */
+ private final int readCommand;
+
+ /*
+ * ACK Command which identifies the matching response.
+ */
+ private final int readReplyCommand;
+
+ /*
+ * The byte position inside the response data.
+ */
+ private final int @Nullable [] readReplyDataPos;
+
+ /*
+ * Bit mask for boolean response properties to identify a true value.
+ */
+ private final int readReplyDataBits;
+
+ /*
+ * Constructor for full read/write command
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int @Nullable [] possibleValues,
+ int changeCommand, int changeDataSize, int changeDataPos, ComfoAirCommandType @Nullable [] changeAffected,
+ int readCommand, int readReplyCommand, int @Nullable [] readReplyDataPos, int readReplyDataBits) {
+ this.key = key;
+ this.dataType = dataType;
+ this.possibleValues = possibleValues;
+ this.changeCommand = changeCommand;
+ this.changeDataSize = changeDataSize;
+ this.changeDataPos = changeDataPos;
+ this.changeAffected = changeAffected;
+ this.readCommand = readCommand;
+ this.readReplyCommand = readReplyCommand;
+ this.readReplyDataPos = readReplyDataPos;
+ this.readReplyDataBits = readReplyDataBits;
+ }
+
+ /*
+ * Constructor for read/write command w/o predefined readReplyDataBits
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int[] possibleValues, int changeCommand,
+ int changeDataSize, int changeDataPos, ComfoAirCommandType[] changeAffected, int readCommand,
+ int readReplyCommand, int[] readReplyDataPos) {
+ this(key, dataType, possibleValues, changeCommand, changeDataSize, changeDataPos, changeAffected, readCommand,
+ readReplyCommand, readReplyDataPos, 0);
+ }
+
+ /*
+ * Constructor for read/write command w/o predefined readReplyDataBits & possibleValues
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int changeCommand, int changeDataSize,
+ int changeDataPos, ComfoAirCommandType[] changeAffected, int readCommand, int readReplyCommand,
+ int[] readReplyDataPos) {
+ this(key, dataType, null, changeCommand, changeDataSize, changeDataPos, changeAffected, readCommand,
+ readReplyCommand, readReplyDataPos, 0);
+ }
+
+ /*
+ * Constructor for write-only command (reset)
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int[] possibleValues, int changeCommand,
+ int changeDataSize, int changeDataPos, ComfoAirCommandType[] changeAffected) {
+ this(key, dataType, possibleValues, changeCommand, changeDataSize, changeDataPos, changeAffected, 0, 0, null,
+ 0);
+ }
+
+ /*
+ * Constructor for read-only command
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int readCommand, int readReplyCommand,
+ int[] readReplyDataPos, int readReplyDataBits) {
+ this(key, dataType, null, 0, 0, 0, null, readCommand, readReplyCommand, readReplyDataPos, readReplyDataBits);
+ }
+
+ /*
+ * Constructor for read-only command w/o readReplyDataBits
+ */
+ private ComfoAirCommandType(String key, ComfoAirDataType dataType, int readCommand, int readReplyCommand,
+ int[] readReplyDataPos) {
+ this(key, dataType, null, 0, 0, 0, null, readCommand, readReplyCommand, readReplyDataPos, 0);
+ }
+
+ public static class Constants {
+ public static final int REQUEST_GET_INPUTS = 0x03;
+ public static final int REPLY_GET_INPUTS = 0x04;
+ public static final int REQUEST_GET_FAN = 0x0b;
+ public static final int REPLY_GET_FAN = 0x0c;
+ public static final int REQUEST_GET_ANALOG_VOLTS = 0x13;
+ public static final int REPLY_GET_ANALOG_VOLTS = 0x14;
+ public static final int REQUEST_GET_FIRMWARE = 0x69;
+ public static final int REPLY_GET_FIRMWARE = 0x6a;
+ public static final int REQUEST_GET_SENSORS = 0x97;
+ public static final int REPLY_GET_SENSORS = 0x98;
+ public static final int REQUEST_SET_LEVEL = 0x99;
+ public static final int REQUEST_SET_RS232 = 0x9b;
+ public static final int REPLY_SET_RS232 = 0x9c;
+ public static final int REQUEST_GET_ANALOGS = 0x9d;
+ public static final int REPLY_GET_ANALOGS = 0x9e;
+ public static final int REQUEST_SET_ANALOGS = 0x9f;
+ public static final int REQUEST_GET_DELAYS = 0xc9;
+ public static final int REPLY_GET_DELAYS = 0xca;
+ public static final int REQUEST_SET_DELAYS = 0xcb;
+ public static final int REQUEST_GET_FAN_LEVEL = 0xcd;
+ public static final int REPLY_GET_FAN_LEVEL = 0xce;
+ public static final int REQUEST_SET_FAN_LEVEL = 0xcf;
+ public static final int REQUEST_GET_TEMPS = 0xd1;
+ public static final int REPLY_GET_TEMPS = 0xd2;
+ public static final int REQUEST_SET_TEMPS = 0xd3;
+ public static final int REQUEST_GET_STATES = 0xd5;
+ public static final int REPLY_GET_STATES = 0xd6;
+ public static final int REQUEST_SET_STATES = 0xd7;
+ public static final int REQUEST_GET_ERRORS = 0xd9;
+ public static final int REPLY_GET_ERRORS = 0xda;
+ public static final int REQUEST_SET_RESETS = 0xdb;
+ public static final int REQUEST_GET_HOURS = 0xdd;
+ public static final int REPLY_GET_HOURS = 0xde;
+ public static final int REQUEST_GET_BYPASS = 0xdf;
+ public static final int REPLY_GET_BYPASS = 0xe0;
+ public static final int REQUEST_GET_PREHEATER = 0xe1;
+ public static final int REPLY_GET_PREHEATER = 0xe2;
+ public static final int REQUEST_GET_RF = 0xe5;
+ public static final int REPLY_GET_RF = 0xe6;
+ public static final int REQUEST_GET_EWT = 0xeb;
+ public static final int REPLY_GET_EWT = 0xec;
+ public static final int REQUEST_SET_EWT = 0xed;
+
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+ public static final int[] EMPTY_INT_ARRAY = new int[0];
+ public static final ComfoAirCommandType[] EMPTY_TYPE_ARRAY = new ComfoAirCommandType[0];
+ }
+
+ /**
+ * @return command key
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * @return data type for this command key
+ */
+ public ComfoAirDataType getDataType() {
+ return dataType;
+ }
+
+ /**
+ * @return readCommand for this command key
+ */
+ public int getReadCommand() {
+ return readCommand;
+ }
+
+ /**
+ * @return readCommand for this command key
+ */
+ public int getReadReplyCommand() {
+ return readReplyCommand;
+ }
+
+ /**
+ * @return possible byte values
+ */
+ public int @Nullable [] getPossibleValues() {
+ return possibleValues;
+ }
+
+ /**
+ * @return relevant byte position inside the response byte value array
+ */
+ public int getChangeDataPos() {
+ return changeDataPos;
+ }
+
+ /**
+ * @return generate a byte value sequence for the response stream
+ */
+ public int[] getChangeDataTemplate() {
+ int[] template = new int[changeDataSize];
+ for (int i = 0; i < template.length; i++) {
+ template[i] = 0x00;
+ }
+ return template;
+ }
+
+ /**
+ * @return byte position inside the request byte value array
+ */
+ public int @Nullable [] getReadReplyDataPos() {
+ return readReplyDataPos;
+ }
+
+ /**
+ * @return bit mask for the response byte value
+ */
+ public int getReadReplyDataBits() {
+ return readReplyDataBits;
+ }
+
+ /**
+ * Get single read command to update item.
+ *
+ * @param key
+ *
+ * @return ComfoAirCommand identified by key
+ */
+ public static @Nullable ComfoAirCommand getReadCommand(String key) {
+ ComfoAirCommandType commandType = getCommandTypeByKey(key);
+
+ if (commandType != null) {
+ return new ComfoAirCommand(key);
+ }
+ return null;
+ }
+
+ /**
+ * Get a command to change properties on the comfoair.
+ *
+ * @param key
+ * command key
+ * @param value
+ * new state
+ * @return initialized ComfoAirCommand
+ */
+ public static @Nullable ComfoAirCommand getChangeCommand(String key, Command command) {
+ ComfoAirCommandType commandType = getCommandTypeByKey(key);
+ State value = UnDefType.NULL;
+
+ if (commandType != null) {
+ ComfoAirDataType dataType = commandType.getDataType();
+ if (dataType == DataTypeBoolean.getInstance() || dataType == DataTypeNumber.getInstance()
+ || dataType == DataTypeRPM.getInstance() || command instanceof QuantityType>) {
+ value = (State) command;
+ }
+ if (value instanceof UnDefType) {
+ return null;
+ } else {
+ int[] data = dataType.convertFromState(value, commandType);
+ DecimalType decimalValue = value.as(DecimalType.class);
+ if (decimalValue != null) {
+ int intValue = decimalValue.intValue();
+
+ if (data != null) {
+ int dataPosition = commandType.getChangeDataPos();
+ return new ComfoAirCommand(key, commandType.changeCommand, null, data, dataPosition, intValue);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all commands which should be refreshed after a successful change
+ * command.
+ *
+ * @param key
+ * command key
+ * @param usedKeys
+ * @return ComfoAirCommand's which should be updated after a modifying
+ * ComfoAirCommand named by key
+ */
+ public static Collection getAffectedReadCommands(String key, Set usedKeys) {
+ Map commands = new HashMap<>();
+
+ ComfoAirCommandType commandType = getCommandTypeByKey(key);
+ if (commandType != null) {
+ uniteCommandsMap(commands, commandType);
+
+ if (commandType.changeAffected != null) {
+ for (ComfoAirCommandType affectedCommandType : commandType.changeAffected) {
+ // refresh affected event keys only when they are used
+ if (usedKeys.contains(affectedCommandType.getKey())) {
+ uniteCommandsMap(commands, affectedCommandType);
+ }
+ }
+ }
+ }
+ return commands.values();
+ }
+
+ /**
+ * Get all commands which receive informations to update items.
+ *
+ * @return all ComfoAirCommand's identified by keys
+ */
+ public static Collection getReadCommandsByEventTypes(List keys) {
+ Map commands = new HashMap<>();
+ for (ComfoAirCommandType entry : values()) {
+ if (keys.contains(entry.key)) {
+ uniteCommandsMap(commands, entry);
+ }
+ }
+ return commands.values();
+ }
+
+ /**
+ * Get commandtypes which matches the replyCmd.
+ *
+ * @param replyCmd
+ * reply command byte value
+ * @return ComfoAirCommandType identified by replyCmd
+ */
+ public static List getCommandTypesByReplyCmd(int replyCmd) {
+ List commands = new ArrayList<>();
+ for (ComfoAirCommandType entry : values()) {
+ if (entry.readReplyCommand == replyCmd) {
+ commands.add(entry);
+ }
+ }
+ return commands;
+ }
+
+ /**
+ * Get a specific command.
+ *
+ * @param key
+ * command key
+ * @return ComfoAirCommandType identified by key
+ */
+ public static @Nullable ComfoAirCommandType getCommandTypeByKey(String key) {
+ for (ComfoAirCommandType entry : values()) {
+ if (entry.key.equals(key)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("null")
+ private static void uniteCommandsMap(Map commands, ComfoAirCommandType commandType) {
+ if (commandType.readReplyCommand != 0) {
+ int replyCmd = commandType.readReplyCommand;
+
+ ComfoAirCommand command = commands.get(replyCmd);
+
+ if (command == null) {
+ command = new ComfoAirCommand(commandType.key);
+ commands.put(replyCmd, command);
+ } else {
+ command.addKey(commandType.key);
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirConfiguration.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirConfiguration.java
new file mode 100644
index 0000000000000..160f05ebda8e8
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirConfiguration.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ComfoAirConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Hans Böhm - Initial contribution
+ */
+@NonNullByDefault
+public class ComfoAirConfiguration {
+
+ /**
+ * Serial port used for communication.
+ */
+ public String serialPort = "";
+
+ /**
+ * Polling interval for state refresh.
+ */
+ public int refreshInterval;
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandler.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandler.java
new file mode 100644
index 0000000000000..ad7fe31c8547f
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandler.java
@@ -0,0 +1,438 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Channel;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
+import org.eclipse.smarthome.core.types.Command;
+import org.eclipse.smarthome.core.types.RefreshType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.eclipse.smarthome.io.transport.serial.SerialPortManager;
+import org.openhab.binding.comfoair.internal.datatypes.ComfoAirDataType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ComfoAirHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Hans Böhm - Initial contribution
+ */
+@NonNullByDefault
+public class ComfoAirHandler extends BaseThingHandler {
+ private static final int DEFAULT_REFRESH_INTERVAL_SEC = 60;
+
+ private final Logger logger = LoggerFactory.getLogger(ComfoAirHandler.class);
+ private final ComfoAirConfiguration config = getConfigAs(ComfoAirConfiguration.class);
+ private final SerialPortManager serialPortManager;
+ private @Nullable ScheduledFuture> poller;
+ private @Nullable ScheduledFuture> affectedItemsPoller;
+ private @Nullable ComfoAirSerialConnector comfoAirConnector;
+
+ public static final int BAUDRATE = 9600;
+
+ public ComfoAirHandler(Thing thing, final SerialPortManager serialPortManager) {
+ super(thing);
+ this.serialPortManager = serialPortManager;
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ String channelId = channelUID.getId();
+
+ if (command instanceof RefreshType) {
+ Channel channel = this.thing.getChannel(channelUID);
+ if (channel != null) {
+ updateChannelState(channel);
+ }
+ } else {
+ ComfoAirCommand changeCommand = ComfoAirCommandType.getChangeCommand(channelId, command);
+
+ if (changeCommand != null) {
+ Set keysToUpdate = getThing().getChannels().stream().map(Channel::getUID).filter(this::isLinked)
+ .map(ChannelUID::getId).collect(Collectors.toSet());
+ sendCommand(changeCommand, channelId);
+
+ Collection affectedReadCommands = ComfoAirCommandType
+ .getAffectedReadCommands(channelId, keysToUpdate);
+
+ if (affectedReadCommands.size() > 0) {
+ Runnable updateThread = new AffectedItemsUpdateThread(affectedReadCommands);
+ affectedItemsPoller = scheduler.schedule(updateThread, 3, TimeUnit.SECONDS);
+ }
+ } else {
+ logger.warn("Unhandled command type: {}, channelId: {}", command.toString(), channelId);
+ }
+ }
+ }
+
+ @Override
+ public void initialize() {
+ String serialPort = this.config.serialPort;
+
+ if (serialPort.isEmpty()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured.");
+ return;
+ } else {
+ ComfoAirSerialConnector comfoAirConnector = new ComfoAirSerialConnector(serialPortManager, serialPort,
+ BAUDRATE);
+ this.comfoAirConnector = comfoAirConnector;
+ }
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.submit(this::connect);
+ }
+
+ private void connect() {
+ if (comfoAirConnector != null) {
+ try {
+ comfoAirConnector.open();
+ if (comfoAirConnector != null) {
+ updateStatus(ThingStatus.ONLINE);
+ pullDeviceProperties();
+ Map properties = thing.getProperties();
+
+ List toBeRemovedChannels = new ArrayList<>();
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing()
+ .getChannelsOfGroup(ComfoAirBindingConstants.CG_PREHEATER_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_FROST_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_BYPASS)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing()
+ .getChannelsOfGroup(ComfoAirBindingConstants.CG_BYPASS_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_BYPASS_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_CHIMNEY)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_CHIMNEY_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_COOKERHOOD)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing().getChannelsOfGroup(
+ ComfoAirBindingConstants.CG_COOKERHOOD_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_COOKERHOOD_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_HEATER)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing()
+ .getChannelsOfGroup(ComfoAirBindingConstants.CG_HEATER_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_HEATER_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing()
+ .getChannelsOfGroup(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
+ + ComfoAirBindingConstants.CHANNEL_ENTHALPY_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_EWT)
+ .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
+ toBeRemovedChannels.addAll(getThing()
+ .getChannelsOfGroup(ComfoAirBindingConstants.CG_EWT_PREFIX.replaceAll("#$", "")));
+ Channel stateChannel = getThing().getChannel(
+ ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_STATE);
+ if (stateChannel != null) {
+ toBeRemovedChannels.add(stateChannel);
+ }
+ }
+ ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
+ updateThing(builder.build());
+
+ List channels = this.thing.getChannels();
+
+ poller = scheduler.scheduleWithFixedDelay(() -> {
+ for (Channel channel : channels) {
+ updateChannelState(channel);
+ }
+ }, 0, (this.config.refreshInterval > 0) ? this.config.refreshInterval
+ : DEFAULT_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ }
+ } catch (ComfoAirSerialException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ if (comfoAirConnector != null) {
+ comfoAirConnector.close();
+ }
+
+ final ScheduledFuture> localPoller = poller;
+
+ if (localPoller != null) {
+ localPoller.cancel(true);
+ poller = null;
+ }
+
+ final ScheduledFuture> localAffectedItemsPoller = affectedItemsPoller;
+
+ if (localAffectedItemsPoller != null) {
+ localAffectedItemsPoller.cancel(true);
+ affectedItemsPoller = null;
+ }
+ }
+
+ private void updateChannelState(Channel channel) {
+ if (!isLinked(channel.getUID())) {
+ return;
+ }
+ String commandKey = channel.getUID().getId();
+
+ ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(commandKey);
+ if (readCommand != null) {
+ scheduler.submit(() -> {
+ State state = sendCommand(readCommand, commandKey);
+ updateState(channel.getUID(), state);
+ });
+ }
+ }
+
+ private State sendCommand(ComfoAirCommand command, String commandKey) {
+ ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
+
+ if (comfoAirConnector != null) {
+ Integer requestCmd = command.getRequestCmd();
+ Integer replyCmd = command.getReplyCmd();
+ int[] requestData = command.getRequestData();
+
+ Integer preRequestCmd;
+ Integer preReplyCmd;
+ int[] preResponse = ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+
+ if (requestCmd != null) {
+ switch (requestCmd) {
+ case ComfoAirCommandType.Constants.REQUEST_SET_ANALOGS:
+ preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_ANALOGS;
+ preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_ANALOGS;
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_DELAYS:
+ preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_DELAYS;
+ preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_DELAYS;
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_FAN_LEVEL:
+ preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_FAN_LEVEL;
+ preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_FAN_LEVEL;
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_STATES:
+ preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_STATES;
+ preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_STATES;
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_EWT:
+ preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_EWT;
+ preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_EWT;
+ break;
+ default:
+ preRequestCmd = requestCmd;
+ preReplyCmd = replyCmd;
+ }
+
+ if (!preRequestCmd.equals(requestCmd)) {
+ command.setRequestCmd(preRequestCmd);
+ command.setReplyCmd(preReplyCmd);
+ command.setRequestData(ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+
+ preResponse = comfoAirConnector.sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+
+ if (preResponse.length <= 0) {
+ return UnDefType.NULL;
+ } else {
+ command.setRequestCmd(requestCmd);
+ command.setReplyCmd(replyCmd);
+ command.setRequestData(requestData);
+ }
+ }
+
+ int[] response = comfoAirConnector.sendCommand(command, preResponse);
+
+ if (response.length > 0) {
+ ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(commandKey);
+ State value = UnDefType.UNDEF;
+
+ if (comfoAirCommandType != null) {
+ ComfoAirDataType dataType = comfoAirCommandType.getDataType();
+ value = dataType.convertToState(response, comfoAirCommandType);
+ }
+ if (value instanceof UnDefType) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("unexpected value for DATA: {}", ComfoAirSerialConnector.dumpData(response));
+ }
+ }
+ return value;
+ }
+ }
+ }
+ return UnDefType.UNDEF;
+ }
+
+ public void pullDeviceProperties() {
+ Map properties = editProperties();
+ ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
+
+ if (comfoAirConnector != null) {
+ String[] namedProperties = new String[] { ComfoAirBindingConstants.PROPERTY_SOFTWARE_MAIN_VERSION,
+ ComfoAirBindingConstants.PROPERTY_SOFTWARE_MINOR_VERSION,
+ ComfoAirBindingConstants.PROPERTY_DEVICE_NAME };
+ String[] optionProperties = new String[] { ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER,
+ ComfoAirBindingConstants.PROPERTY_OPTION_BYPASS, ComfoAirBindingConstants.PROPERTY_OPTION_RECU_TYPE,
+ ComfoAirBindingConstants.PROPERTY_OPTION_RECU_SIZE,
+ ComfoAirBindingConstants.PROPERTY_OPTION_CHIMNEY,
+ ComfoAirBindingConstants.PROPERTY_OPTION_COOKERHOOD,
+ ComfoAirBindingConstants.PROPERTY_OPTION_HEATER, ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY,
+ ComfoAirBindingConstants.PROPERTY_OPTION_EWT };
+
+ for (String prop : namedProperties) {
+ ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(prop);
+ if (readCommand != null) {
+ int[] response = comfoAirConnector.sendCommand(readCommand,
+ ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+ if (response.length > 0) {
+ ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(prop);
+ String value = "";
+
+ if (comfoAirCommandType != null) {
+ ComfoAirDataType dataType = comfoAirCommandType.getDataType();
+ if (prop.equals(ComfoAirBindingConstants.PROPERTY_DEVICE_NAME)) {
+ value = dataType.calculateStringValue(response, comfoAirCommandType);
+ } else {
+ value = String.valueOf(dataType.calculateNumberValue(response, comfoAirCommandType));
+ }
+ }
+ properties.put(prop, value);
+ }
+ }
+ }
+
+ ComfoAirCommand optionsReadCommand = new ComfoAirCommand(ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER,
+ ComfoAirCommandType.Constants.REQUEST_GET_STATES, ComfoAirCommandType.Constants.REPLY_GET_STATES,
+ ComfoAirCommandType.Constants.EMPTY_INT_ARRAY, null, null);
+ int[] response = comfoAirConnector.sendCommand(optionsReadCommand,
+ ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+ if (response.length > 0) {
+ for (String prop : optionProperties) {
+ ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(prop);
+ String value = "";
+
+ if (comfoAirCommandType != null) {
+ ComfoAirDataType dataType = comfoAirCommandType.getDataType();
+ int intValue = dataType.calculateNumberValue(response, comfoAirCommandType);
+
+ switch (prop) {
+ case ComfoAirBindingConstants.PROPERTY_OPTION_RECU_TYPE:
+ value = intValue == 1 ? "LEFT" : "RIGHT";
+ break;
+ case ComfoAirBindingConstants.PROPERTY_OPTION_RECU_SIZE:
+ value = intValue == 1 ? "BIG" : "SMALL";
+ break;
+ case ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY:
+ if (intValue == 1) {
+ value = ComfoAirBindingConstants.COMMON_OPTION_STATES[1];
+ } else if (intValue == 2) {
+ value = "Installed w\\o sensor";
+ } else {
+ value = ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
+ }
+ break;
+ case ComfoAirBindingConstants.PROPERTY_OPTION_EWT:
+ if (intValue == 1) {
+ value = "Regulated";
+ } else if (intValue == 2) {
+ value = "Unregulated";
+ } else {
+ value = ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
+ }
+ break;
+ default:
+ value = intValue > 0 ? ComfoAirBindingConstants.COMMON_OPTION_STATES[1]
+ : ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
+ break;
+ }
+ }
+ properties.put(prop, value);
+ }
+ }
+ thing.setProperties(properties);
+ }
+ }
+
+ private class AffectedItemsUpdateThread implements Runnable {
+
+ private Collection affectedReadCommands;
+
+ public AffectedItemsUpdateThread(Collection affectedReadCommands) {
+ this.affectedReadCommands = affectedReadCommands;
+ }
+
+ @Override
+ public void run() {
+ for (ComfoAirCommand readCommand : this.affectedReadCommands) {
+ Integer replyCmd = readCommand.getReplyCmd();
+ if (replyCmd != null) {
+ List commandTypes = ComfoAirCommandType.getCommandTypesByReplyCmd(replyCmd);
+
+ for (ComfoAirCommandType commandType : commandTypes) {
+ String commandKey = commandType.getKey();
+ sendCommand(readCommand, commandKey);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandlerFactory.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandlerFactory.java
new file mode 100644
index 0000000000000..4e7a826351b7f
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandlerFactory.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.eclipse.smarthome.io.transport.serial.SerialPortManager;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link ComfoAirHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Hans Böhm - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.comfoair", service = ThingHandlerFactory.class)
+public class ComfoAirHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Collections
+ .singleton(ComfoAirBindingConstants.THING_TYPE_COMFOAIR_GENERIC);
+
+ private @NonNullByDefault({}) SerialPortManager serialPortManager;
+
+ @Reference
+ protected void setSerialPortManager(final SerialPortManager serialPortManager) {
+ this.serialPortManager = serialPortManager;
+ }
+
+ protected void unsetSerialPortManager(final SerialPortManager serialPortManager) {
+ this.serialPortManager = null;
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (ComfoAirBindingConstants.THING_TYPE_COMFOAIR_GENERIC.equals(thingTypeUID)) {
+ return new ComfoAirHandler(thing, serialPortManager);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialConnector.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialConnector.java
new file mode 100644
index 0000000000000..7574b90dbd8ba
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialConnector.java
@@ -0,0 +1,563 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.OnOffType;
+import org.eclipse.smarthome.io.transport.serial.PortInUseException;
+import org.eclipse.smarthome.io.transport.serial.SerialPort;
+import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier;
+import org.eclipse.smarthome.io.transport.serial.SerialPortManager;
+import org.eclipse.smarthome.io.transport.serial.UnsupportedCommOperationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Connector class for serial communication with ComfoAir device
+ *
+ * @author Hans Böhm - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class ComfoAirSerialConnector {
+
+ private final Logger logger = LoggerFactory.getLogger(ComfoAirSerialConnector.class);
+
+ private static final byte CTRL = (byte) 0x07;
+ private static final byte[] START = { CTRL, (byte) 0xf0 };
+ private static final byte[] END = { CTRL, (byte) 0x0f };
+ private static final byte[] ACK = { CTRL, (byte) 0xf3 };
+
+ private static final int RS232_ENABLED_VALUE = 0x03;
+ private static final int RS232_DISABLED_VALUE = 0x00;
+
+ private boolean isSuspended = true;
+
+ private final String serialPortName;
+ private final int baudRate;
+ private final SerialPortManager serialPortManager;
+ private @Nullable SerialPort serialPort;
+ private @Nullable InputStream inputStream;
+ private @Nullable OutputStream outputStream;
+
+ public ComfoAirSerialConnector(final SerialPortManager serialPortManager, final String serialPortName,
+ final int baudRate) {
+ this.serialPortManager = serialPortManager;
+ this.serialPortName = serialPortName;
+ this.baudRate = baudRate;
+ }
+
+ /**
+ * Open serial port.
+ *
+ * @throws PortInUseException, UnsupportedCommOperationException, IOException
+ */
+ public void open() throws ComfoAirSerialException {
+ logger.debug("open(): Opening ComfoAir connection");
+
+ try {
+ SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
+ if (portIdentifier != null) {
+ SerialPort serialPort = portIdentifier.open(this.getClass().getName(), 3000);
+ serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
+ SerialPort.PARITY_NONE);
+ serialPort.enableReceiveThreshold(1);
+ serialPort.enableReceiveTimeout(1000);
+ serialPort.notifyOnDataAvailable(true);
+ this.serialPort = serialPort;
+
+ inputStream = new DataInputStream(new BufferedInputStream(serialPort.getInputStream()));
+ outputStream = serialPort.getOutputStream();
+
+ ComfoAirCommand command = ComfoAirCommandType.getChangeCommand(ComfoAirCommandType.ACTIVATE.getKey(),
+ OnOffType.ON);
+
+ if (command != null) {
+ sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+ } else {
+ logger.debug("Failure while creating COMMAND: {}", command);
+ }
+ } else {
+ throw new ComfoAirSerialException("No such Port: " + serialPortName);
+ }
+ } catch (PortInUseException | UnsupportedCommOperationException | IOException e) {
+ throw new ComfoAirSerialException(e);
+ }
+ }
+
+ /**
+ * Close serial port.
+ */
+ public void close() {
+ logger.debug("close(): Close ComfoAir connection");
+ SerialPort serialPort = this.serialPort;
+
+ if (serialPort != null) {
+ ComfoAirCommand command = ComfoAirCommandType.getChangeCommand(ComfoAirCommandType.ACTIVATE.getKey(),
+ OnOffType.OFF);
+
+ if (command != null) {
+ sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
+ } else {
+ logger.debug("Failure while creating COMMAND: {}", command);
+ }
+
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ logger.debug("Error while closing input stream: {}", e.getMessage());
+ }
+ }
+
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ logger.debug("Error while closing output stream: {}", e.getMessage());
+ }
+ }
+
+ serialPort.close();
+ }
+ }
+
+ /**
+ * Prepare a command for sending using the serial port.
+ *
+ * @param command
+ * @param preRequestData
+ * @return reply byte values
+ */
+ public synchronized int[] sendCommand(ComfoAirCommand command, int[] preRequestData) {
+ Integer requestCmd = command.getRequestCmd();
+ int retry = 0;
+
+ if (requestCmd != null) {
+ // Switch support for app or ccease control
+ if (requestCmd == ComfoAirCommandType.Constants.REQUEST_SET_RS232) {
+ isSuspended = !isSuspended;
+ } else if (requestCmd == ComfoAirCommandType.Constants.REPLY_SET_RS232) {
+ return new int[] { isSuspended ? RS232_DISABLED_VALUE : RS232_ENABLED_VALUE };
+ } else if (isSuspended) {
+ logger.trace("Ignore cmd. Service is currently suspended");
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+
+ do {
+ // If preRequestData param was send (preRequestData is sending for write command)
+ int[] requestData;
+
+ if (preRequestData.length <= 0) {
+ requestData = command.getRequestData();
+ } else {
+ requestData = buildRequestData(command, preRequestData);
+
+ if (requestData.length <= 0) {
+ logger.debug("Unable to build data for write command: {}",
+ String.format("%02x", command.getReplyCmd()));
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ }
+
+ byte[] requestBlock = calculateRequest(requestCmd, requestData);
+ if (logger.isTraceEnabled()) {
+ logger.trace("send DATA: {}", dumpData(requestBlock));
+ }
+
+ if (!send(requestBlock)) {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+
+ byte[] responseBlock = new byte[0];
+
+ try {
+ InputStream inputStream = this.inputStream;
+ // 31 is max. response length
+ byte[] readBuffer = new byte[31];
+ do {
+ while (inputStream != null && inputStream.available() > 0) {
+ int bytes = inputStream.read(readBuffer);
+
+ // merge bytes
+ byte[] mergedBytes = new byte[responseBlock.length + bytes];
+ System.arraycopy(responseBlock, 0, mergedBytes, 0, responseBlock.length);
+ System.arraycopy(readBuffer, 0, mergedBytes, responseBlock.length, bytes);
+
+ responseBlock = mergedBytes;
+ }
+ try {
+ // add wait states around reading the stream, so that
+ // interrupted transmissions are merged
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Transmission was interrupted: {}", e.getMessage());
+ throw new RuntimeException(e);
+ }
+ } while (inputStream != null && inputStream.available() > 0);
+
+ // check for ACK
+ if (responseBlock.length >= 2 && responseBlock[0] == ACK[0] && responseBlock[1] == ACK[1]) {
+ if (command.getReplyCmd() == null) {
+ // confirm additional data with an ACK
+ if (responseBlock.length > 2) {
+ send(ACK);
+ }
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+
+ // check for start and end sequence and if the response cmd
+ // matches
+ // 11 is the minimum response length with one data byte
+ if (responseBlock.length >= 11 && responseBlock[2] == START[0] && responseBlock[3] == START[1]
+ && responseBlock[responseBlock.length - 2] == END[0]
+ && responseBlock[responseBlock.length - 1] == END[1]
+ && (responseBlock[5] & 0xff) == command.getReplyCmd()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("receive RAW DATA: {}", dumpData(responseBlock));
+ }
+
+ byte[] cleanedBlock = cleanupBlock(responseBlock);
+
+ int dataSize = cleanedBlock[2];
+
+ // the cleanedBlock size should equal dataSize + 2 cmd
+ // bytes and + 1 checksum byte
+ if (dataSize + 3 == cleanedBlock.length - 1) {
+ byte checksum = cleanedBlock[dataSize + 3];
+ int[] replyData = new int[dataSize];
+ for (int i = 0; i < dataSize; i++) {
+ replyData[i] = cleanedBlock[i + 3] & 0xff;
+ }
+
+ byte[] block = Arrays.copyOf(cleanedBlock, 3 + dataSize);
+
+ // validate calculated checksum against submitted
+ // checksum
+ if (calculateChecksum(block) == checksum) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("receive CMD: {} DATA: {}",
+ String.format("%02x", command.getReplyCmd()), dumpData(replyData));
+ }
+ send(ACK);
+
+ return replyData;
+ }
+
+ logger.debug("Unable to handle data. Checksum verification failed");
+ } else {
+ logger.debug("Unable to handle data. Data size not valid");
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("skip CMD: {} DATA: {}", String.format("%02x", command.getReplyCmd()),
+ dumpData(cleanedBlock));
+ }
+ }
+ }
+ } catch (InterruptedIOException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Transmission was interrupted: {}", e.getMessage());
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ logger.debug("IO error: {}", e.getMessage());
+ }
+
+ try {
+ Thread.sleep(1000);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retry cmd. Last call was not successful. Request: {} Response: {}",
+ dumpData(requestBlock), (responseBlock.length > 0 ? dumpData(responseBlock) : "null"));
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Transmission was interrupted: {}", e.getMessage());
+ throw new RuntimeException(e);
+ }
+ } while (retry++ < 5);
+
+ if (retry == 5) {
+ logger.debug("Unable to send command. {} retries failed.", retry);
+ }
+ }
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+
+ /**
+ * Generate the byte sequence for sending to ComfoAir (incl. START & END
+ * sequence and checksum).
+ *
+ * @param command
+ * @param requestData
+ * @return response byte value block with cmd, data and checksum
+ */
+ private byte[] calculateRequest(int command, int[] requestData) {
+ // generate the command block (cmd and request data)
+ int length = requestData.length;
+ byte[] block = new byte[4 + length];
+
+ block[0] = 0x00;
+ block[1] = (byte) command;
+ block[2] = (byte) length;
+
+ if (requestData.length > 0) {
+ for (int i = 0; i < requestData.length; i++) {
+ block[i + 3] = (byte) requestData[i];
+ }
+ }
+
+ // calculate checksum for command block
+ byte checksum = calculateChecksum(block);
+ block[block.length - 1] = checksum;
+
+ // escape the command block with checksum included
+ block = escapeBlock(block);
+ byte[] request = new byte[4 + block.length];
+
+ request[0] = START[0];
+ request[1] = START[1];
+ System.arraycopy(block, 0, request, 2, block.length);
+ request[request.length - 2] = END[0];
+ request[request.length - 1] = END[1];
+
+ return request;
+ }
+
+ /**
+ * Calculates a checksum for a command block (cmd, data and checksum).
+ *
+ * @param block
+ * @return checksum byte value
+ */
+ private byte calculateChecksum(byte[] block) {
+ int datasum = 0;
+ for (int i = 0; i < block.length; i++) {
+ datasum += block[i];
+ }
+ datasum += 173;
+
+ return (byte) (datasum & 0xFF);
+ }
+
+ /**
+ * Cleanup a commandblock from quoted 0x07 characters.
+ *
+ * @param processBuffer
+ * @return the 0x07 cleaned byte values
+ */
+ private byte[] cleanupBlock(byte[] processBuffer) {
+ int pos = 0;
+ byte[] cleanedBuffer = new byte[processBuffer.length];
+
+ for (int i = 4; i < processBuffer.length - 2; i++) {
+ if (CTRL == processBuffer[i] && CTRL == processBuffer[i + 1]) {
+ i++;
+ }
+ cleanedBuffer[pos] = processBuffer[i];
+ pos++;
+ }
+ return Arrays.copyOf(cleanedBuffer, pos);
+ }
+
+ /**
+ * Escape special 0x07 character.
+ *
+ * @param cleanedBuffer
+ * @return escaped byte value array
+ */
+ private byte[] escapeBlock(byte[] cleanedBuffer) {
+ int pos = 0;
+ byte[] processBuffer = new byte[50];
+
+ for (int i = 0; i < cleanedBuffer.length; i++) {
+ if (CTRL == cleanedBuffer[i]) {
+ processBuffer[pos] = CTRL;
+ pos++;
+ }
+ processBuffer[pos] = cleanedBuffer[i];
+ pos++;
+ }
+ return Arrays.copyOf(processBuffer, pos);
+ }
+
+ /**
+ * Send the byte values.
+ *
+ * @param request
+ * @return successful flag
+ */
+ private boolean send(byte[] request) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("send DATA: {}", dumpData(request));
+ }
+
+ try {
+ if (outputStream != null) {
+ outputStream.write(request);
+ }
+ return true;
+ } catch (IOException e) {
+ logger.debug("Error writing to serial port {}: {}", serialPortName, e.getLocalizedMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Is used to debug byte values.
+ *
+ * @param replyData
+ * @return
+ */
+ public static String dumpData(int[] replyData) {
+ StringBuilder sb = new StringBuilder();
+ for (int ch : replyData) {
+ sb.append(String.format(" %02x", ch));
+ }
+ return sb.toString();
+ }
+
+ private String dumpData(byte[] data) {
+ StringBuilder sb = new StringBuilder();
+ for (byte ch : data) {
+ sb.append(String.format(" %02x", ch));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Build request data based on reply data
+ *
+ * @param command
+ * @param preRequestData
+ * @return new build int values array
+ */
+ private int[] buildRequestData(ComfoAirCommand command, int[] preRequestData) {
+ int[] newRequestData;
+ Integer requestCmd = command.getRequestCmd();
+ Integer dataPosition = command.getDataPosition();
+ Integer requestValue = command.getRequestValue();
+
+ if (requestCmd != null && dataPosition != null && requestValue != null) {
+ switch (requestCmd) {
+ case ComfoAirCommandType.Constants.REQUEST_SET_DELAYS:
+ newRequestData = new int[8];
+
+ if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) {
+ System.arraycopy(preRequestData, 0, newRequestData, 0, newRequestData.length);
+ newRequestData[dataPosition] = requestValue;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_FAN_LEVEL:
+ newRequestData = new int[9];
+
+ if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) {
+ System.arraycopy(preRequestData, 0, newRequestData, 0, 6);
+ System.arraycopy(preRequestData, 10, newRequestData, 6, newRequestData.length - 6);
+ newRequestData[dataPosition] = requestValue;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_STATES:
+ newRequestData = new int[8];
+
+ if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) {
+ if (dataPosition == 4) {
+ requestValue = preRequestData[dataPosition]
+ + checkByteAndCalculateValue(command, requestValue, preRequestData[dataPosition]);
+ }
+ System.arraycopy(preRequestData, 0, newRequestData, 0, 6);
+ System.arraycopy(preRequestData, 9, newRequestData, 6, newRequestData.length - 6);
+ newRequestData[dataPosition] = requestValue;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_EWT:
+ newRequestData = new int[5];
+
+ if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) {
+ System.arraycopy(preRequestData, 0, newRequestData, 0, 4);
+ System.arraycopy(preRequestData, 6, newRequestData, 4, newRequestData.length - 4);
+ newRequestData[dataPosition] = requestValue;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ break;
+ case ComfoAirCommandType.Constants.REQUEST_SET_ANALOGS:
+ newRequestData = new int[19];
+
+ if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) {
+ switch (dataPosition) {
+ case 0:
+ case 1:
+ case 2:
+ requestValue = preRequestData[dataPosition] + checkByteAndCalculateValue(command,
+ requestValue, preRequestData[dataPosition]);
+ }
+ System.arraycopy(preRequestData, 0, newRequestData, 0, newRequestData.length);
+ newRequestData[dataPosition] = requestValue;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ break;
+ default:
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ return newRequestData;
+ } else {
+ return ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
+ }
+ }
+
+ /**
+ * Check if preValue contains possible byte and calculate new value
+ *
+ * @param command
+ * @param requestValue
+ * @param preValue
+ * @return new int value
+ */
+ private int checkByteAndCalculateValue(ComfoAirCommand command, int requestValue, int preValue) {
+ String key = command.getKeys().get(0);
+ ComfoAirCommandType commandType = ComfoAirCommandType.getCommandTypeByKey(key);
+ if (commandType != null) {
+ int[] possibleValues = commandType.getPossibleValues();
+ if (possibleValues != null) {
+ int possibleValue = possibleValues[0];
+ boolean isActive = (preValue & possibleValue) == possibleValue;
+ int newValue;
+
+ if (isActive) {
+ newValue = requestValue == 1 ? 0 : -possibleValue;
+ } else {
+ newValue = requestValue == 1 ? possibleValue : 0;
+ }
+ return newValue;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialException.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialException.java
new file mode 100644
index 0000000000000..7170a88357230
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialException.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ConnectException} is thrown on errors relating the serial connection.
+ *
+ * @author Hans Böhm - Initial contribution
+ */
+@NonNullByDefault
+public class ComfoAirSerialException extends Exception {
+ private static final long serialVersionUID = 5439939653331189828L;
+
+ public ComfoAirSerialException(Throwable cause) {
+ super(cause);
+ }
+
+ public ComfoAirSerialException(String message) {
+ super(message);
+ }
+
+ public ComfoAirSerialException(String message, Exception e) {
+ super(message, e);
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/ComfoAirDataType.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/ComfoAirDataType.java
new file mode 100644
index 0000000000000..d948fae95aeb3
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/ComfoAirDataType.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.types.State;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+
+/**
+ * Abstract class to convert binary hex values into openHAB states and vice
+ * versa
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public interface ComfoAirDataType {
+ /**
+ * Generate a openHAB State object based on response data.
+ *
+ * @param response
+ * @param commandType
+ * @return converted State object
+ */
+ State convertToState(int[] response, ComfoAirCommandType commandType);
+
+ /**
+ * Generate byte array based on a openHAB State.
+ *
+ * @param value
+ * @param commandType
+ * @return converted byte array
+ */
+ int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType);
+
+ default int calculateNumberValue(int[] data, ComfoAirCommandType commandType) {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ int value = 0;
+ if (readReplyDataPos != null) {
+ int base = 0;
+
+ for (int i = readReplyDataPos.length - 1; i >= 0; i--) {
+ if (readReplyDataPos[i] < data.length) {
+ value += data[readReplyDataPos[i]] << base;
+ base += 8;
+ } else {
+ return -1;
+ }
+ }
+ } else {
+ value = -1;
+ }
+ return value;
+ }
+
+ default String calculateStringValue(int[] data, ComfoAirCommandType commandType) {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ StringBuilder value = new StringBuilder();
+ if (readReplyDataPos != null) {
+ for (int pos : readReplyDataPos) {
+ if (pos < data.length) {
+ value.append((char) data[pos]);
+ }
+ }
+ }
+ return value.toString();
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeBoolean.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeBoolean.java
new file mode 100644
index 0000000000000..5a2133a3d8bdf
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeBoolean.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.library.types.OnOffType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle boolean values which are handled as decimal 0/1 states
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public class DataTypeBoolean implements ComfoAirDataType {
+ private static final DataTypeBoolean SINGLETON_INSTANCE = new DataTypeBoolean();
+
+ private DataTypeBoolean() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeBoolean.class);
+
+ public static DataTypeBoolean getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeBoolean\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ int readReplyDataBits = commandType.getReadReplyDataBits();
+ int readCommand = commandType.getReadCommand();
+ boolean result;
+
+ if (readReplyDataPos != null && readReplyDataPos[0] < data.length) {
+ if (readReplyDataBits == 0) {
+ result = data[readReplyDataPos[0]] == 1;
+ } else {
+ result = (data[readReplyDataPos[0]] & readReplyDataBits) == readReplyDataBits;
+ }
+ return OnOffType.from(result);
+ } else if (readCommand == 0) {
+ return OnOffType.OFF; // handle write-only commands (resets)
+ } else {
+ return UnDefType.NULL;
+ }
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ if (value instanceof UnDefType) {
+ logger.trace("\"DataTypeBoolean\" class \"convertFromState\" undefined state");
+ return null;
+ } else {
+ DecimalType decimalValue = value.as(DecimalType.class);
+ int[] possibleValues = commandType.getPossibleValues();
+ int returnValue = 0x01;
+
+ if (possibleValues != null) {
+ returnValue = possibleValues[0];
+ }
+
+ if (decimalValue != null) {
+ int[] template = commandType.getChangeDataTemplate();
+
+ template[commandType.getChangeDataPos()] = decimalValue.intValue() == 1 ? returnValue : 0x00;
+
+ return template;
+ } else {
+ logger.trace(
+ "\"DataTypeBoolean\" class \"convertFromState\" method: State value conversion returned null");
+ return null;
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeMessage.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeMessage.java
new file mode 100644
index 0000000000000..56e8e0d170b90
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeMessage.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.StringType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle error messages
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public class DataTypeMessage implements ComfoAirDataType {
+ private static final DataTypeMessage SINGLETON_INSTANCE = new DataTypeMessage();
+
+ private DataTypeMessage() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeMessage.class);
+
+ public static DataTypeMessage getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeMessage\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ if (readReplyDataPos != null) {
+ int errorAlo = data[readReplyDataPos[0]];
+ int errorE = data[readReplyDataPos[1]];
+ int errorEA = data[readReplyDataPos[2]];
+ int errorAhi = data[readReplyDataPos[3]];
+
+ StringBuilder errorCode = new StringBuilder();
+
+ if (errorAlo > 0) {
+ errorCode.append("A");
+ errorCode.append(convertToCode(errorAlo));
+ } else if (errorAhi > 0) {
+ if (errorAhi == 0x80) {
+ errorCode.append("A0");
+ } else {
+ errorCode.append("A");
+ errorCode.append(convertToCode(errorAhi) + 8);
+ }
+ }
+
+ if (errorE > 0) {
+ if (errorCode.length() > 0) {
+ errorCode.append(" ");
+ }
+ errorCode.append("E");
+ errorCode.append(convertToCode(errorE));
+ } else if (errorEA > 0) {
+ if (errorCode.length() > 0) {
+ errorCode.append(" ");
+ }
+ errorCode.append("EA");
+ errorCode.append(convertToCode(errorEA));
+ }
+ return new StringType(errorCode.length() > 0 ? errorCode.toString() : "No Errors");
+ } else {
+ return UnDefType.UNDEF;
+ }
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ return null;
+ }
+
+ private int convertToCode(int code) {
+ switch (code) {
+ case 0x01:
+ return 1;
+ case 0x02:
+ return 2;
+ case 0x04:
+ return 3;
+ case 0x08:
+ return 4;
+ case 0x10:
+ return 5;
+ case 0x20:
+ return 6;
+ case 0x40:
+ return 7;
+ case 0x80:
+ return 8;
+ default:
+ return -1;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeNumber.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeNumber.java
new file mode 100644
index 0000000000000..99520cebdd623
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeNumber.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle numeric values
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public class DataTypeNumber implements ComfoAirDataType {
+ private static final DataTypeNumber SINGLETON_INSTANCE = new DataTypeNumber();
+
+ private DataTypeNumber() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeNumber.class);
+
+ public static DataTypeNumber getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeNumber\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int value = calculateNumberValue(data, commandType);
+
+ if (value < 0) {
+ return UnDefType.NULL;
+ }
+
+ int[] possibleValues = commandType.getPossibleValues();
+ if (possibleValues != null) {
+ // fix for unexpected value for "level" value. got a 0x33. valid was
+ // the 0x03. 0x30 was to much.
+ // send DATA: 07 f0 00 cd 00 7a 07 0f
+ // receive CMD: ce DATA: 0f 20 32 00 0f 21 33 2d 33 03 01 5a 5b 00
+ for (int possibleValue : possibleValues) {
+ if ((value & possibleValue) == possibleValue) {
+ return new DecimalType(value);
+ }
+ }
+ return UnDefType.NULL;
+ }
+ return new DecimalType(value);
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ if (value instanceof UnDefType) {
+ logger.trace("\"DataTypeNumber\" class \"convertFromState\" undefined state");
+ return null;
+ } else {
+ int[] template = commandType.getChangeDataTemplate();
+ int[] possibleValues = commandType.getPossibleValues();
+ int position = commandType.getChangeDataPos();
+
+ int intValue = ((DecimalType) value).intValue();
+
+ if (possibleValues == null) {
+ template[position] = intValue;
+ } else {
+ for (int i = 0; i < possibleValues.length; i++) {
+ if (possibleValues[i] == intValue) {
+ template[position] = intValue;
+ break;
+ }
+ }
+ }
+ return template;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeRPM.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeRPM.java
new file mode 100644
index 0000000000000..ce33a728670f2
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeRPM.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.DecimalType;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle revolutions per minute values
+ *
+ * @author Grzegorz Miasko - Initial Contribution
+ * @author Hans Böhm - Refactoring
+ */
+@NonNullByDefault
+public class DataTypeRPM implements ComfoAirDataType {
+ private static final DataTypeRPM SINGLETON_INSTANCE = new DataTypeRPM();
+
+ private DataTypeRPM() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeRPM.class);
+
+ public static DataTypeRPM getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeRPM\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int value = calculateNumberValue(data, commandType);
+
+ if (value < 0) {
+ return UnDefType.NULL;
+ }
+ // transferred value is (1875000 / rpm) per protocol
+ return new DecimalType((int) (1875000.0 / value));
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ if (value instanceof UnDefType) {
+ logger.trace("\"DataTypeRPM\" class \"convertFromState\" undefined state");
+ return null;
+ } else {
+ int[] template = commandType.getChangeDataTemplate();
+ // transferred value is (1875000 / rpm) per protocol
+ template[commandType.getChangeDataPos()] = (int) (1875000 / ((DecimalType) value).doubleValue());
+ return template;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTemperature.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTemperature.java
new file mode 100644
index 0000000000000..f0c03029029f2
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTemperature.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.unit.SIUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle temperature values
+ *
+ * @author Holger Hees - Initial Contribution
+ * @author Hans Böhm - QuantityTypes
+ */
+@NonNullByDefault
+public class DataTypeTemperature implements ComfoAirDataType {
+ private static final DataTypeTemperature SINGLETON_INSTANCE = new DataTypeTemperature();
+
+ private DataTypeTemperature() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeTemperature.class);
+
+ public static DataTypeTemperature getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeTemperature\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ if (readReplyDataPos != null && readReplyDataPos[0] < data.length) {
+ return new QuantityType<>((((double) data[readReplyDataPos[0]]) / 2) - 20, SIUnits.CELSIUS);
+ } else {
+ return UnDefType.NULL;
+ }
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ int[] template = commandType.getChangeDataTemplate();
+ QuantityType> celsius = ((QuantityType>) value).toUnit(SIUnits.CELSIUS);
+
+ if (celsius != null) {
+ template[commandType.getChangeDataPos()] = (int) (celsius.doubleValue() + 20) * 2;
+ return template;
+ } else {
+ logger.trace("\"DataTypeTemperature\" class \"convertFromState\" undefined state");
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTime.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTime.java
new file mode 100644
index 0000000000000..d341860d54703
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTime.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle time values
+ *
+ * @author Hans Böhm - Initial Contribution
+ */
+@NonNullByDefault
+public class DataTypeTime implements ComfoAirDataType {
+ private static final DataTypeTime SINGLETON_INSTANCE = new DataTypeTime();
+
+ private DataTypeTime() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeTime.class);
+
+ public static DataTypeTime getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeTime\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int value = calculateNumberValue(data, commandType);
+
+ if (value < 0) {
+ return UnDefType.NULL;
+ }
+
+ return new QuantityType<>(value, SmartHomeUnits.HOUR);
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ int[] template = commandType.getChangeDataTemplate();
+ int[] possibleValues = commandType.getPossibleValues();
+ int position = commandType.getChangeDataPos();
+ QuantityType> hours = ((QuantityType>) value).toUnit(SmartHomeUnits.HOUR);
+
+ if (hours != null) {
+ int intValue = hours.intValue();
+
+ if (possibleValues == null) {
+ template[position] = intValue;
+ } else {
+ for (int i = 0; i < possibleValues.length; i++) {
+ if (possibleValues[i] == intValue) {
+ template[position] = intValue;
+ break;
+ }
+ }
+ }
+ return template;
+ } else {
+ logger.trace("\"DataTypeTime\" class \"convertFromState\" undefined state");
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeVolt.java b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeVolt.java
new file mode 100644
index 0000000000000..80475237bacd0
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeVolt.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2010-2020 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.comfoair.internal.datatypes;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.library.types.QuantityType;
+import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
+import org.eclipse.smarthome.core.types.State;
+import org.eclipse.smarthome.core.types.UnDefType;
+import org.openhab.binding.comfoair.internal.ComfoAirCommandType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class to handle volt values
+ *
+ * @author Grzegorz Miasko - Initial Contribution
+ * @author Hans Böhm - QuantityTypes
+ */
+@NonNullByDefault
+public class DataTypeVolt implements ComfoAirDataType {
+ private static final DataTypeVolt SINGLETON_INSTANCE = new DataTypeVolt();
+
+ private DataTypeVolt() {
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(DataTypeVolt.class);
+
+ public static DataTypeVolt getInstance() {
+ return SINGLETON_INSTANCE;
+ }
+
+ @Override
+ public State convertToState(int @Nullable [] data, ComfoAirCommandType commandType) {
+ if (data == null) {
+ logger.trace("\"DataTypeVolt\" class \"convertToState\" method parameter: null");
+ return UnDefType.NULL;
+ } else {
+ int[] readReplyDataPos = commandType.getReadReplyDataPos();
+ if (readReplyDataPos != null && readReplyDataPos[0] < data.length) {
+ return new QuantityType<>((double) data[readReplyDataPos[0]] * 10 / 255, SmartHomeUnits.VOLT);
+ } else {
+ return UnDefType.NULL;
+ }
+ }
+ }
+
+ @Override
+ public int @Nullable [] convertFromState(State value, ComfoAirCommandType commandType) {
+ int[] template = commandType.getChangeDataTemplate();
+ QuantityType> volts = ((QuantityType>) value).toUnit(SmartHomeUnits.VOLT);
+
+ if (volts != null) {
+ template[commandType.getChangeDataPos()] = (int) (volts.doubleValue() * 255 / 10);
+ return template;
+ } else {
+ logger.trace("\"DataTypeVolt\" class \"convertFromState\" undefined state");
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..0032e9d0eea5c
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,10 @@
+
+
+
+ ComfoAir Binding
+ This is the binding for ComfoAir.
+ Hans Böhm
+
+
diff --git a/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..d12408e616cd3
--- /dev/null
+++ b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -0,0 +1,1185 @@
+
+
+
+
+
+ Provides a generic access to a ComfoAir compatible ventilation system
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ serial-port
+ Serial port that the ComfoAir is connected to
+
+
+
+ Refresh interval in seconds
+ 60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Switch
+
+ Activate (control through openHAB) or deactivate (return control to CCEase) binding control
+ Switch
+
+
+
+
+ Number
+
+ Fan level
+ Number
+
+
+
+
+
+
+
+
+
+
+
+ Number:Temperature
+
+ Target (comfort) temperature
+ Temperature
+
+
+
+
+ Switch
+
+ Filter full
+ Switch
+
+
+
+
+ Number
+
+ Reset filter uptime
+ Switch
+
+
+
+
+
+
+
+
+ Number
+
+ Reset errors
+ Switch
+
+
+
+
+
+
+
+
+ Number
+
+ Fan level 0 performance (%) of outgoing fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 1 performance (%) of outgoing fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 2 performance (%) of outgoing fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 3 performance (%) of outgoing fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 0 performance (%) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 1 performance (%) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 2 performance (%) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Fan level 3 performance (%) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Current relative speed (%) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Current relative speed (%) of outgoing fan
+ Number
+
+
+
+
+ Number
+
+ Current absolute speed (rpm) of incoming fan
+ Number
+
+
+
+
+ Number
+
+ Current absolute speed (rpm) of outgoing fan
+ Number
+
+
+
+
+ Number:Temperature
+
+ Intake air temperature (outside)
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Outlet air temperature (outside)
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Inlet air temperature (inside)
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Uptake air temperature (inside)
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of temperature sensor T1 (outdoor in)
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of temperature sensor T2 (indoor in)
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of temperature sensor T3 (indoor out)
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of temperature sensor T4 (outdoor out)
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of EWT temperature sensor
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of heater temperature sensor
+ Temperature
+
+
+
+
+ Switch
+
+ Availability of cookerhood temperature sensor
+ Temperature
+
+
+
+
+ Switch
+
+ State of the chimney control
+ Switch
+
+
+
+
+ Switch
+
+ State of the bypass (ON = open / OFF = closed)
+ Switch
+
+
+
+
+ Switch
+
+ State of the EWT valve (ON = open / OFF = closed)
+ Switch
+
+
+
+
+ Switch
+
+ State of the heater
+ Switch
+
+
+
+
+ Switch
+
+ State of the 0-10V control
+ Switch
+
+
+
+
+ Switch
+
+ State of the antifrost control
+ Switch
+
+
+
+
+ Switch
+
+ State of the cookerhood control
+ Switch
+
+
+
+
+ Switch
+
+ State of the enthalpy module
+ Switch
+
+
+
+
+ Number:Temperature
+
+ Temperature of geothermal heat exchanger sensor
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Temperature of heater sensor
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Temperature of cookerhood sensor
+ Temperature
+
+
+
+
+ Number:Time
+
+ Uptime at level 0 (away)
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime at level 1
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime at level 2
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime at level 3
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime of antifrost
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime of preheater
+ Time
+
+
+
+
+ Number:Time
+
+ Hours of bypass open
+ Time
+
+
+
+
+ Number:Time
+
+ Uptime of the filter
+ Time
+
+
+
+
+ Number
+
+ Bypass factor value
+ Number
+
+
+
+
+ Number
+
+ Bypass level state
+ Number
+
+
+
+
+ Number
+
+ Bypass correction state
+ Number
+
+
+
+
+ Switch
+
+ Bypass summer mode
+ Switch
+
+
+
+
+ Number
+
+ State of the preheater valve
+ Number
+
+
+
+
+ Switch
+
+ State of the frost protection
+ Switch
+
+
+
+
+ Switch
+
+ State of the preheater
+ Switch
+
+
+
+
+ Number
+
+ Frost minutes
+ Number
+
+
+
+
+ Number
+
+ Frost safety setting
+ Number
+
+
+
+
+ Number:Temperature
+
+ Lower temperature of the geothermal heat exchanger
+ Temperature
+
+
+
+
+ Number:Temperature
+
+ Upper temperature of the geothermal heat exchanger
+ Temperature
+
+
+
+
+ Number
+
+ Speed up of the geothermal heat exchanger
+ Number
+
+
+
+
+ Number
+
+ Heater power value
+ Number
+
+
+
+
+ Number
+
+ Heater power I-parameter value
+ Number
+
+
+
+
+ Number:Temperature
+
+ Target temperature of the heater
+ Temperature
+
+
+
+
+ Number
+
+ Speed up of the cookerhood
+ Number
+
+
+
+
+ Number:Temperature
+
+ Temperature of the enthalpy sensor
+ Temperature
+
+
+
+
+ Number
+
+ Humidity of the enthalpy sensor
+ Humidity
+
+
+
+
+ Number
+
+ Level of the enthalpy sensor
+ Number
+
+
+
+
+ Number
+
+ Timer state of the enthalpy sensor
+ Time
+
+
+
+
+ Switch
+
+ Availability of L1 step switch
+ Switch
+
+
+
+
+ Switch
+
+ Availability of L2 step switch
+ Switch
+
+
+
+
+ Switch
+
+ Availability of bathroom switch
+ Switch
+
+
+
+
+ Switch
+
+ Availability of cookerhood switch
+ Switch
+
+
+
+
+ Switch
+
+ Availability of external filter
+ Switch
+
+
+
+
+ Switch
+
+ Availability of heat recovery (WTW)
+ Switch
+
+
+
+
+ Switch
+
+ Availability of bathroom switch 2 (luxe)
+ Switch
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number
+
+ End delay for cooker hood control (min)
+ Time
+
+
+
+
+ Number
+
+ Start delay for bathroom switch (min)
+ Time
+
+
+
+
+ Number
+
+ End delay for bathroom switch (min)
+ Time
+
+
+
+
+ Number
+
+ End delay for L1 switch (min)
+ Time
+
+
+
+
+ Number
+
+ Usage period until filter pollution message (weeks)
+ Time
+
+
+
+
+ Number
+
+ End delay (RF short actuation) for ventilation level 3 (min)
+ Time
+
+
+
+
+ Number
+
+ End delay (RF long actuation) for ventilation level 3 (min)
+ Time
+
+
+
+
+ Number
+
+ Period for pulse ventilation (min)
+ Time
+
+
+
+
+ Switch
+
+ Availability of analog input
+ Switch
+
+
+
+
+ Switch
+
+ Availability of RF input
+ Switch
+
+
+
+
+ Switch
+
+ State of analog input
+ Switch
+
+
+
+
+ Switch
+
+ State of RF input
+ Switch
+
+
+
+
+ Switch
+
+ Postive/Negative state of analog input
+ Switch
+
+
+
+
+ Switch
+
+ Postive/Negative state of RF input
+ Switch
+
+
+
+
+ Number:ElectricPotential
+
+ Voltage level of analog input
+ Number
+
+
+
+
+ Number
+
+ Minimum setting for analog input
+ Number
+
+
+
+
+ Number
+
+ Maximum setting for analog input
+ Number
+
+
+
+
+ Number
+
+ Target setting for analog input
+ Number
+
+
+
+
+ Number
+
+ Minimum setting for RF input
+ Number
+
+
+
+
+ Number
+
+ Maximum setting for RF input
+ Number
+
+
+
+
+ Number
+
+ Target setting for RF input
+ Number
+
+
+
+
+ Number
+
+ Priority of control
+ Number
+
+
+
+
+
+
+
+
+
+ String
+
+ Current errors
+ Text
+
+
+
+
+ String
+
+ Last errors
+ Text
+
+
+
+
+ String
+
+ Prelast errors
+ Text
+
+
+
+
+ String
+
+ Pre-Prelast errors
+ Text
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index b24200f4f1428..4f94e72db62b1 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -67,6 +67,7 @@
org.openhab.binding.cbus
org.openhab.binding.chromecast
org.openhab.binding.cm11a
+ org.openhab.binding.comfoair
org.openhab.binding.coolmasternet
org.openhab.binding.coronastats
org.openhab.binding.daikin