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 + + + + + Switch + + State of menu 20 mode (P10) + Switch + + + + + Switch + + State of menu 21 mode (P11) + Switch + + + + + Switch + + State of menu 22 mode (P12) + Switch + + + + + Switch + + State of menu 23 mode (P13) + Switch + + + + + Switch + + State of menu 24 mode (P14) + Switch + + + + + Switch + + State of menu 25 mode (P15) + Switch + + + + + Switch + + State of menu 26 mode (P16) + Switch + + + + + Switch + + State of menu 27 mode (P17) + Switch + + + + + Switch + + State of menu 28 mode (P18) + Switch + + + + + Switch + + State of menu 29 mode (P19) + 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