From b30bfcc3da7dcd8e28be58b02a58b44a97651346 Mon Sep 17 00:00:00 2001 From: boehan Date: Wed, 5 Feb 2020 23:57:45 +0100 Subject: [PATCH] comfoair - first version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hans Böhm --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../org.openhab.binding.comfoair/.classpath | 32 + bundles/org.openhab.binding.comfoair/.project | 23 + bundles/org.openhab.binding.comfoair/NOTICE | 13 + .../org.openhab.binding.comfoair/README.md | 899 +++++++ bundles/org.openhab.binding.comfoair/pom.xml | 17 + .../src/main/feature/feature.xml | 10 + .../internal/ComfoAirBindingConstants.java | 30 + .../comfoair/internal/ComfoAirCommand.java | 133 + .../internal/ComfoAirCommandType.java | 2263 +++++++++++++++++ .../internal/ComfoAirConfiguration.java | 35 + .../comfoair/internal/ComfoAirHandler.java | 290 +++ .../internal/ComfoAirHandlerFactory.java | 68 + .../internal/ComfoAirSerialConnector.java | 655 +++++ .../internal/datatypes/ComfoAirDataType.java | 44 + .../internal/datatypes/DataTypeBoolean.java | 83 + .../internal/datatypes/DataTypeMessage.java | 113 + .../internal/datatypes/DataTypeNumber.java | 108 + .../internal/datatypes/DataTypeRPM.java | 79 + .../datatypes/DataTypeTemperature.java | 72 + .../internal/datatypes/DataTypeVolt.java | 72 + .../resources/ESH-INF/binding/binding.xml | 10 + .../ESH-INF/i18n/comfoair_xx_XX.properties | 17 + .../resources/ESH-INF/thing/thing-types.xml | 1596 ++++++++++++ bundles/pom.xml | 3 +- 26 files changed, 6670 insertions(+), 1 deletion(-) create mode 100644 bundles/org.openhab.binding.comfoair/.classpath create mode 100644 bundles/org.openhab.binding.comfoair/.project create mode 100644 bundles/org.openhab.binding.comfoair/NOTICE create mode 100644 bundles/org.openhab.binding.comfoair/README.md create mode 100644 bundles/org.openhab.binding.comfoair/pom.xml create mode 100644 bundles/org.openhab.binding.comfoair/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirBindingConstants.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommand.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommandType.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirConfiguration.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandler.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandlerFactory.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialConnector.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/ComfoAirDataType.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeBoolean.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeMessage.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeNumber.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeRPM.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeTemperature.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeVolt.java create mode 100644 bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/i18n/comfoair_xx_XX.properties create mode 100644 bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index d385f6b3d1531..8e20c69a76f1a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,7 @@ /bundles/org.openhab.binding.buienradar/ @gedejong /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.daikin/ @caffineehacker /bundles/org.openhab.binding.darksky/ @cweitkamp diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 929adcee84384..c272bb2ef41c5 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -119,6 +119,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..38fc228134713 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/README.md @@ -0,0 +1,899 @@ +# ComfoAir Binding + +This binding allows to monitor and control Zehnder ComfoAir serial controlled ventilation systems. + +## Supported Things + +The binding supports ComfoAir ventilation systems supporting control 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. + +### 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 +``` + +### 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. + +## 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 + +| 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 | +| CC Ease Functions | | | | | | +| ccease#fanLevel | Number | Fan Level | Fan level | false | no | +| ccease#targetTemperature | Number:Temperature | Target Temperature | Target (comfort) temperature | false | no | +| ccease#filterError | Switch | Filter Error | Filter full | false | no | +| ccease#errorMessage | String | Error Message | Current errors | false | no | +| ccease#filterReset | Switch | Filter Reset | Reset filter operating hours | false | no | +| ccease#errorReset | Switch | Error Reset | Reset errors | false | no | +| Ventilation Values | | | | | | +| ventilation#fanOut0 | Number | Fan Out Level 0 (away) | Fan level 0 performance (%) of outgoing fan | true | yes | +| ventilation#fanOut1 | Number | Fan Out Level 1 | Fan level 1 performance (%) of outgoing fan | true | yes | +| ventilation#fanOut2 | Number | Fan Out Level 2 | Fan level 2 performance (%) of outgoing fan | true | yes | +| ventilation#fanOut3 | Number | Fan Out Level 3 | Fan level 3 performance (%) of outgoing fan | true | yes | +| ventilation#fanIn0 | Number | Fan In Level 0 (away) | Fan level 0 performance (%) of incoming fan | true | yes | +| ventilation#fanIn1 | Number | Fan In Level 1 | Fan level 1 performance (%) of incoming fan | true | yes | +| ventilation#fanIn2 | Number | Fan In Level 2 | Fan level 2 performance (%) of incoming fan | true | yes | +| ventilation#fanIn3 | Number | Fan In Level 3 | Fan level 3 performance (%) of incoming fan | true | yes | +| ventilation#fanInPercent | Number | Fan In (%) | Current relative speed (%) of incoming fan | true | 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#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 | +| Operating Hours | | | | true | yes | +| times#level0Time | Number | Level 0 duration | Operating hours at level 0 (away) | true | no | +| times#level1Time | Number | Level 1 duration | Operating hours at level 1 | true | no | +| times#level2Time | Number | Level 2 duration | Operating hours at level 2 | true | no | +| times#level3Time | Number | Level 3 duration | Operating hours at level 3 | true | no | +| times#freezeTime | Number | Antifrost Duration | Operating hours of antifrost | true | no | +| times#preheaterTime | Number | Preheater Duration | Operating hours of preheater | true | no | +| times#bypassTime | Number | Bypass Duration | Hours of bypass open | true | no | +| times#filterHours | Number | Filter Duration | Operating hours 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 | Switch | Chimney Control State | State of the chimney control | true | yes | +| menuP9#bypassState | Switch | Bypass State | State of the bypass (ON = open / OFF = closed) | true | yes | +| menuP9#ewtState | Switch | EWT State | State of the EWT valve (ON = open / OFF = closed) | true | yes | +| menuP9#heaterState | 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 | Switch | Antifrost State | State of the antifrost control | true | yes | +| menuP9#cookerhoodState | Switch | Cookerhood State | State of the cookerhood control | true | yes | +| menuP9#enthalpyState | Switch | Enthalpy State | State of the enthalpy module | true | yes | +| Error States | | | | | | +| error#errorACurrent | String | Error A Current | Current error A | true | no | +| error#errorECurrent | String | Error E Current | Current error E | true | no | +| error#errorALast | String | Error A Last | Last error A | true | no | +| error#errorELast | String | Error E Last | Last error E | true | no | +| error#errorAPrelast | String | Error A Prelast | Prelast error A | true | no | +| error#errorEPrelast | String | Error E Prelast | Prelast error E | true | no | +| error#errorAPrePrelast | String | Error A Pre-Prelast | Pre-Prelast error A | true | no | +| error#errorEPrePrelast | String | Error E Pre-Prelast | Pre-Prelast error E | true | no | +| error#errorEACurrent | String | Error EA Current | Current error EA | true | no | +| error#errorEALast | String | Error EA Last | Last error EA | true | no | +| error#errorEAPrelast | String | Error EA Prelast | Prelast error EA | true | no | +| error#errorEAPrePrelast | String | Error EA Pre-Prelast | Pre-Prelast error EA | true | no | +| error#errorAHighCurrent | String | Error A (high) Current | Current error A (high) | true | no | +| error#errorAHighLast | String | Error A (high) Last | Last error A (high) | true | no | +| error#errorAHighPrelast | String | Error A (high) Prelast | Prelast error A (high) | true | no | +| error#errorAHighPrePrelast | String | Error A (high) Pre-Prelast | Pre-Prelast error A (high) | true | no | +| Bypass Values | | | | | | +| 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 | | | | | | +| 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 | | | | | | +| 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 | | | | | | +| 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 | | | | | | +| cookerhood#cookerhoodSpeed | Number | Cookerhood Speed Up (%) | Speed up of the cookerhood | true | yes | +| Enthalpy Values | | | | | | +| 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 | +| Option States | | | | | | +| options#isPreheater | Switch | Preheater | Preheater option installed | false | yes | +| options#isBypass | Switch | Bypass | Bypass option installed | false | yes | +| options#recuType | Number | Comfoair Type | Type of the ComfoAir (1 = left / 2 = right) | false | yes | +| options#recuSize | Number | Comfoair Size | Size of the ComfoAir (1 = big / 2 = small) | false | yes | +| options#isChimney | Switch | Chimney | Chimney option installed | false | yes | +| options#isCookerhood | Switch | Cookerhood | Cookerhood option installed | false | yes | +| options#isHeater | Switch | Heater | Heater option installed | false | yes | +| options#isEnthalpy | Switch | Enthalpy | Enthalpy option installed | false | yes | +| options#isEWT | Switch | EWT | EWT option installed | false | 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 | +| Analog Inputs | | | | | | +| analog#isAnalog1 | Switch | Analog Input 1 Availability | Availability of analog input 1 | false | yes | +| analog#isAnalog2 | Switch | Analog Input 2 Availability | Availability of analog input 2 | false | yes | +| analog#isAnalog3 | Switch | Analog Input 3 Availability | Availability of analog input 3 | false | yes | +| analog#isAnalog4 | Switch | Analog Input 4 Availability | Availability of analog input 4 | false | yes | +| analog#isRF | Switch | RF Input Availability | Availability of RF input | false | yes | +| analog#analog1Mode | Switch | Analog Input 1 State | State of analog input 1 | false | yes | +| analog#analog2Mode | Switch | Analog Input 2 State | State of analog input 2 | false | yes | +| analog#analog3Mode | Switch | Analog Input 3 State | State of analog input 3 | false | yes | +| analog#analog4Mode | Switch | Analog Input 4 State | State of analog input 1 | false | yes | +| analog#RFMode | Switch | RF Input State | State of RF input | false | yes | +| analog#analog1Negative | Switch | Analog Input 1 Postive/Negative | Postive/Negative state of analog input 1 | false | yes | +| analog#analog2Negative | Switch | Analog Input 2 Postive/Negative | Postive/Negative state of analog input 2 | false | yes | +| analog#analog3Negative | Switch | Analog Input 3 Postive/Negative | Postive/Negative state of analog input 3 | false | yes | +| analog#analog4Negative | Switch | Analog Input 4 Postive/Negative | Postive/Negative state of analog input 1 | false | yes | +| analog#RFNegative | Switch | RF Input Postive/Negative | Postive/Negative state of RF input | false | yes | +| analog#analog1Volt | Number:ElectricPotential | Analog Input 1 Voltage Level | Voltage level of analog input 1 | false | yes | +| analog#analog1Min | Number | Analog Input 1 Min | Minimum setting for analog input 1 | false | yes | +| analog#analog1Max | Number | Analog Input 1 Max | Maximum setting for analog input 1 | false | yes | +| analog#analog1Value | Number | Analog Input 1 Target | Target setting for analog input 1 | false | yes | +| analog#analog2Volt | Number:ElectricPotential | Analog Input 2 Voltage Level | Voltage level of analog input 2 | false | yes | +| analog#analog2Min | Number | Analog Input 2 Min | Minimum setting for analog input 2 | false | yes | +| analog#analog2Max | Number | Analog Input 2 Max | Maximum setting for analog input 2 | false | yes | +| analog#analog2Value | Number | Analog Input 2 Target | Target setting for analog input 2 | false | yes | +| analog#analog3Volt | Number:ElectricPotential | Analog Input 3 Voltage Level | Voltage level of analog input 3 | false | yes | +| analog#analog3Min | Number | Analog Input 3 Min | Minimum setting for analog input 3 | false | yes | +| analog#analog3Max | Number | Analog Input 3 Max | Maximum setting for analog input 3 | false | yes | +| analog#analog3Value | Number | Analog Input 3 Target | Target setting for analog input 3 | false | yes | +| analog#analog4Volt | Number:ElectricPotential | Analog Input 4 Voltage Level | Voltage level of analog input 4 | false | yes | +| analog#analog4Min | Number | Analog Input 4 Min | Minimum setting for analog input 4 | false | yes | +| analog#analog4Max | Number | Analog Input 4 Max | Maximum setting for analog input 4 | false | yes | +| analog#analog4Value | Number | Analog Input 4 Target | Target setting for analog input 4 | false | yes | +| analog#RFMin | Number | RF Input Min | Minimum setting for RF input | false | yes | +| analog#RFMax | Number | RF Input Max | Maximum setting for RF input | false | yes | +| analog#RFValue | Number | RF Input Target | Target setting for RF input | false | yes | +| Software Version | | | | | | +| software#softwareMainVersion | Number | Software Main Version | Main version of the device software | true | yes | +| software#softwareMinorVersion | Number | Software Minor Version | Minor version of the device software | true | yes | +| software#softwareBetaVersion | Number | Software Beta Version | Beta version of the device software | true | yes | + +## Full Example + +`.things` file: +``` +Thing comfoair:comfoair:myComfoAir "ComfoAir" [serialPort="/dev/ttyUSB0", refreshInterval="60"] +``` + +`.items` file: +``` +// ComfoAir +Group ComfoAir "ComfoAir" (devices) + +// Temperatures chart +Group comfoairTemps_Chart (ComfoAir) +Number comfoairTemps_Chart_Period "Period" + +// Control +Switch comfoairControl "Activate" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:bindingControl#activate"} +Number comfoairFanLevel "Ventilation level [%d]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ccease#fanLevel"} +Switch comfoairErrorReset "Error reset" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ccease#errorReset"} +Switch comfoairFilterReset "Filter reset" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ccease#filterReset"} +Number comfoairReset "Reset" (ComfoAir) +Number comfoairMode "Manual - Auto [%d]" (ComfoAir) +Switch comfoairControl_Switch "Activate" (ComfoAir) +Number comfoairFilterPeriod "Filter period [%d weeks]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:menuP2#filterWeeks"} +Switch comfoairChimney "Fire programme [MAP(comfoair_is-not.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isChimney"} +Switch comfoairPreheater "Preheater [MAP(comfoair_is-not.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isPreheater"} +Switch comfoairCookerHood "Extractor hood [MAP(comfoair_is-not.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isCookerhood"} +Switch comfoairBypass "Bypass [MAP(comfoair_is-not.map):%s]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isBypass"} +Switch comfoairEWT "EWT" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isEWT"} +Switch comfoairEnthalpy "Enthalpy" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:options#isEnthalpy"} + +// Messages +String comfoairError (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ccease#errorMessage"} +String comfoairError_Message "Messages [%s]" (ComfoAir) +Number comfoairFilterRuntime (ComfoAir) {channel="comfoair:comfoair:myComfoAir:times#filterHours"} +String comfoairFilterRuntime_Message "Filter time [%s]" (ComfoAir) +String comfoairFrozenError +String comfoairInletError + +// State +Number:Temperature comfoairTargetTemperature "Comfort temperature [%.1f °C]" (ComfoAir) {channel="comfoair:comfoair:myComfoAir:ccease#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 capasity [%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"} +Number comfoairEfficiency "Efficiency [%.1f %%]" (ComfoAir) +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_Message 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" { + Switch item=comfoairMode mappings=[0="Manual", 1="Auto"] + Switch item=comfoairFanMode_Message mappings=[0="Sup + Exh", 1="Supply", 2="Exhaust"] + Switch item=comfoairFanLevel_Message mappings=[2="Level 1", 3="Level 2", 4="Level 3"] + Setpoint item=comfoairTargetTemperature_Message 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"] + } + Frame label="Results" { + Text label="Charts" icon="chart" { + Switch item=comfoairTemps_Chart_Period mappings=[0="Day", 1="Week", 2="Month", 3="Year"] + Chart item=comfoairTemps_Chart period=D refresh=10000 visibility=[comfoairTemps_Chart_Period==0] + Chart item=comfoairTemps_Chart period=W refresh=60000 visibility=[comfoairTemps_Chart_Period==1, comfoairTemps_Chart_Period=="Uninitialized"] + Chart item=comfoairTemps_Chart period=M refresh=60000 visibility=[comfoairTemps_Chart_Period==2] + Chart item=comfoairTemps_Chart period=Y refresh=600000 visibility=[comfoairTemps_Chart_Period==3] + } + } +} +``` + +`comfoair_bypass.map` file: +``` +1=Opened +0=Closed +undefined=unknown +-=unknown +``` + +`comfoair_on-off.map` file: +``` +ON=active +OFF=inactive +undefined=unknown +-=unknown +``` + +`comfoair_is-not.map` file: +``` +ON=installed +OFF=not installed +undefined=unknown +-=unknown +``` + +`comfoair_freeze.map` file: +``` +ON=frozen +OFF=OK +undefined=unknown +-=unknown +``` + +`comfoair.rules` file: +``` +import java.lang.Math +import java.util.Date +import java.util.List +import java.util.ArrayList + +// variable for auto_mode state +var Boolean isAutoMode = false + +// based capacity for absent level +// absent level is used for supply/exhaust mode +var Number comfoairFanIn0BaseValue = 15 +var Number comfoairFanOut0BaseValue = 15 +var Number comfoairFanLevelLastValue +var Number comfoairFanIn0LastValue +var Number comfoairFanOut0LastValue + + +rule "setAutoModeForStart" +when + System started +then + if( comfoairControl.state == ON ) { + comfoairMode.postUpdate( 1 ) + isAutoMode = true + comfoairFanMode.postUpdate( 0 ) + } +end + + +rule "setControlToCCEaseWhenSystemClose" +when + System shuts down +then + comfoairControl.sendCommand( OFF ) +end + + +rule "messageWhenFrozen" +when + Item comfoairControl changed to ON + or + Item comfoairIncomingFan changed + or + Item comfoairOutgoingFan changed + or + Item comfoairOutdoorIncomingTemperature changed +then + if( comfoairControl.state == ON ) { + var String msg = "" + + if( comfoairIncomingFan.state instanceof DecimalType && comfoairOutgoingFan.state instanceof DecimalType && comfoairOutdoorIncomingTemperature.state instanceof QuantityType ) { + + if( comfoairOutdoorIncomingTemperature.state < "0|°C" && ( comfoairIncomingFan.state == 0 || comfoairOutgoingFan.state == 0 ) ) { + msg = "FROZEN" + } else msg = "OK" + + } else msg = "communication failed" + + comfoairFrozenError.postUpdate( msg ) + } +end + +rule "showMessage" +when + Item comfoairControl changed to ON + or + Item comfoairError changed + or + Item comfoairInletError changed + or + Item comfoairFreezeError changed +then + if( comfoairControl.state == ON ) { + var String msg = "" + callScript( "comfoair_errorMsg" ) + var String msgToSend = "" + callScript( "comfoair_errorMsgToSend" ) + + comfoairError_Message.postUpdate( msg ) + + if( comfoairError.state != "Ok" || comfoairInletError.state != "OK" || comfoairFrozenError.state != "OK" ) { + sendMail( "xyz@gmail.com", "ComfoAir message", msgToSend ) + } + } +end + + +rule "showFilterTime" +when + Item comfoairFilterRuntime changed + or + Item comfoairFilterPeriod changed + or + Item comfoairControl changed to ON +then + if( comfoairFilterRuntime.state instanceof DecimalType && comfoairFilterPeriod.state instanceof DecimalType ) { + var Number filterPeriodWeeks = comfoairFilterPeriod.state + var Number filterPeriodHours = filterPeriodWeeks * 7 * 24 + + if( comfoairFilterRuntime.state instanceof DecimalType ){ + var Number passedHours = comfoairFilterRuntime.state as DecimalType + var Boolean isTimeExceeded + + if( filterPeriodHours - passedHours < 0) { + isTimeExceeded = true + } else isTimeExceeded = false + + var Number passedWeeks = Math::floor( ( passedHours / 168 ).doubleValue ) + var Number passedDays = Math::floor( ( ( passedHours - (passedWeeks * 168 ) ) / 24).doubleValue ) + var Number remainedHours = Math::abs( ( filterPeriodHours - passedHours ).intValue ) + var Number remainedWeeks = Math::floor( ( remainedHours / 168 ).doubleValue ) + var Number remainedDays = Math::floor( ( ( remainedHours - ( remainedWeeks * 168 ) ) / 24).doubleValue ) + + var String msg = "Passed: " + + if( passedWeeks > 0 ) { + + if( passedWeeks == 1 ) msg = msg + "1 week" + else msg = msg + passedWeeks.intValue + " weeks" + + if( passedDays > 0) msg = msg + " " + else msg = msg + "," + } + if( passedDays > 0 ) { + if( passedDays == 1 ) msg = msg + "1 day," + else msg = msg + passedDays.intValue + " days," + } + if( passedWeeks == 0 && passedDays == 0 ) msg = msg + "0 days, " + + if( isTimeExceeded ) msg = msg + " Exceeded: " + else msg = msg + " Remained: " + + if( remainedWeeks != 0 ) { + + if( remainedWeeks == 1 ) msg = msg + "1 week" + else msg = msg + remainedWeeks.intValue + " weeks" + + if( remainedDays > 0 ) msg = msg + " " + } + if( remainedDays != 0 ) { + if( remainedDays == 1 ) msg = msg + "1 day" + else msg = msg + remainedDays.intValue + " days" + } + + if( remainedWeeks == 0 && remainedDays == 0 ) msg = msg + "0 days" + + comfoairFilterRuntime_Message.postUpdate( msg ) + + } else { + comfoairFilterRuntime_Message.postUpdate( "communication failed" ) + } + } +end + + +rule "errorReset" +when + Item comfoairReset changed +then + var String error + + if( comfoairControl.state == ON ) { + + if( comfoairReset.state == "0" ) { + comfoairFilterReset.sendCommand( ON ) + logInfo( "ComfoAir", "Filter was reset !" ) + + } else if( comfoairReset.state == "1" ) { + comfoairErrorReset.sendCommand( ON ) + logInfo( "ComfoAir", "Error was reset !" ) + } + + createTimer(now.plusSeconds( 5 ), [ | + error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } else { + if( comfoairReset.state == "0" || comfoairReset.state == "1" ) { + comfoairError_Message.postUpdate( "PC control is not active" ) + + createTimer(now.plusSeconds( 10 ), [ | + error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } + } +end + + +rule "calculateEfficiency" +when + Item comfoairOutdoorIncomingTemperature changed + or + Item comfoairIndoorOutgoingTemperature changed + or + Item comfoairIndoorIncomingTemperature changed + or + Item comfoairControl changed to ON +then + var Number efficiency + + if( comfoairOutdoorIncomingTemperature.state instanceof QuantityType && comfoairIndoorOutgoingTemperature.state instanceof QuantityType && comfoairIndoorIncomingTemperature.state instanceof QuantityType && comfoairBypassMode.state == OFF ) { + var Number tempOutIn = comfoairOutdoorIncomingTemperature.state as DecimalType + var Number tempInOut = comfoairIndoorOutgoingTemperature.state as DecimalType + var Number tempInIn = comfoairIndoorIncomingTemperature.state as DecimalType + + if( tempInOut != tempOutIn ) { + efficiency = ( tempInIn - tempOutIn ) / ( tempInOut - tempOutIn ) * 100 + efficiency = Math::round( efficiency.doubleValue ); + } else efficiency = null + } else efficiency = null + + comfoairEfficiency.postUpdate( efficiency ) +end + + +rule "changeSupply-Exhaust" +when + Item comfoairFanMode_Message changed +then + var Number newFanMode = comfoairFanMode_Message.state as DecimalType + + if( comfoairControl.state == ON ) { + + if( comfoairChimney.state == OFF ) { + + if( comfoairFanMode_Message.state != comfoairFanMode.state ) { + comfoairFanMode.sendCommand( newFanMode ) + } + + } else if( comfoairFanMode_Message.state != comfoairFanMode.state ) { + comfoairError_Message.postUpdate( "Fire programme is on" ) + + createTimer(now.plusMillis( 200 ), [ | + comfoairFanMode_Message.postUpdate( comfoairFanMode.state ) + ]) + + createTimer(now.plusSeconds( 10 ), [ | + var String error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } + } else if( comfoairFanMode_Message.state != comfoairFanMode.state ) { + comfoairError_Message.postUpdate( "PC control is not active" ) + + createTimer(now.plusMillis( 200 ), [ | + comfoairFanMode_Message.postUpdate( comfoairFanMode.state ) + ]) + + createTimer(now.plusSeconds( 10 ), [ | + var String error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } +end + + +rule "changeSupply-ExhaustSwitch" +when + Item comfoairFanMode changed +then + if( comfoairFanMode.state instanceof DecimalType ) { + comfoairFanMode_Message.postUpdate( comfoairFanMode.state ) + } +end + + +rule "changeSupply-ExhaustMode" +when + Item comfoairFanMode changed +then + if( comfoairControl.state instanceof OnOffType && comfoairChimney.state instanceof OnOffType ) { + + if( comfoairFanMode.state instanceof Number ) { + + if( comfoairFanLevel.state > 1 ) { + comfoairFanLevelLastValue = comfoairFanLevel.state + + Thread::sleep( 100 ) + comfoairFanIn0LastValue = comfoairIncomingFan.state + comfoairFanOut0LastValue = comfoairOutgoingFan.state + } + + // Supply + Exhaust + if( comfoairFanMode.state == 0 ) { + + if( comfoairIncomingFan.state == 0 || comfoairOutgoingFan.state == 0 ) { + comfoairFanIn0.sendCommand( comfoairFanIn0BaseValue ) + + createTimer(now.plusSeconds( 1 ), [ | + comfoairFanOut0.sendCommand( comfoairFanOut0BaseValue ) + ]) + + if( comfoairFanLevel.state < 2 ) { + comfoairFanLevel.sendCommand( comfoairFanLevelLastValue.toString ) + } + } + } else { + comfoairFanLevel.sendCommand( "1" ) + + // Supply + if( comfoairFanMode.state == 1 ) { + + if( comfoairIncomingFan.state == 0 ) { + comfoairFanIn0.sendCommand( comfoairFanIn0LastValue ) + } else { + comfoairFanIn0.sendCommand( comfoairIncomingFan.state ) + } + + createTimer( now.plusSeconds( 1 ), [ | comfoairFanOut0.sendCommand( 0 ) ]) + + comfoairMode.postUpdate( 0 ) + isAutoMode = false + } + + // Exhaust + if( comfoairFanMode.state == 2 ) { + + comfoairFanIn0.sendCommand( 0 ) + + createTimer(now.plusSeconds( 1 ), [ | + if( comfoairOutgoingFan.state == 0 ) { + comfoairFanOut0.sendCommand( comfoairFanOut0LastValue ) + } else { + comfoairFanOut0.sendCommand( comfoairOutgoingFan.state ) + } + ]) + + comfoairMode.postUpdate( 0 ) + isAutoMode = false + } + } + } + } +end + + +rule "changeAuto-Manual" +when + Item comfoairMode changed +then + if( comfoairControl.state == 0 ) { + comfoairError_Message.postUpdate( "PC control is not active" ) + + createTimer(now.plusSeconds( 10 ), [ | + var String error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } +end + + +rule "changeVentilationLevel" +when + Item comfoairFanLevel_Message changed +then + var Number newLevel = comfoairFanLevel_Message.state as DecimalType + + if( comfoairControl.state == ON && comfoairFanLevel_Message.state != comfoairFanLevel.state ) { + comfoairFanLevel.sendCommand( newLevel ) + + comfoairMode.postUpdate( 0 ) + isAutoMode = false + + if( newLevel > 1 ) { + comfoairFanMode.postUpdate( 0 ) + } + } else { + + if( comfoairFanLevel_Message.state != comfoairFanLevel.state ) { + comfoairError_Message.postUpdate( "PC control is not active" ) + + createTimer(now.plusMillis( 200 ), [ | + comfoairFanLevel_Message.postUpdate( comfoairFanLevel.state ) + ]) + + createTimer(now.plusSeconds( 10 ), [ | + var String error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } + } +end + + +rule "changeVentilationLevelSwitch" +when + Item comfoairFanLevel changed +then + if( comfoairFanLevel.state instanceof DecimalType ) { + comfoairFanLevel_Message.postUpdate( comfoairFanLevel.state ) + } +end + + +rule "changeComfortTemperature" +when + Item comfoairTargetTemperature_Message changed +then + var Number newTemp = comfoairTargetTemperature_Message.state as DecimalType + + if( comfoairControl.state == ON ) { + + if( comfoairTargetTemperature_Message.state != comfoairTargetTemperature.state ) { + comfoairTargetTemperature.sendCommand( newTemp ) + } + } else { + + if( comfoairTargetTemperature_Message.state != comfoairTargetTemperature.state ) { + comfoairError_Message.postUpdate( "PC control is not active" ) + + createTimer( now.plusMillis( 200 ), [ | + comfoairTargetTemperature_Message.postUpdate( comfoairTargetTemperature.state ) + ]) + + createTimer( now.plusSeconds( 10 ), [ | + var String error = "" + callScript( "comfoair_errorMsg" ) + comfoairError_Message.postUpdate( error ) + ]) + } + } +end + + +rule "changeComfortTemperatureSetpoint" +when + Item comfoairTargetTemperature changed +then + if( comfoairTargetTemperature.state instanceof DecimalType ) { + comfoairTargetTemperature_Message.postUpdate( comfoairTargetTemperature.state ) + } +end + + +rule "AutoProgram" +when + Item comfoairMode changed + or + Time cron "0 0/1 * * * ?" +then + if( comfoairControl.state instanceof OnOffType && comfoairMode.state instanceof DecimalType && comfoairFanLevel.state instanceof DecimalType ) { + var Number day = now.getDayOfWeek + var Number hour = now.getHourOfDay + var Number minute = now.getMinuteOfHour + + if( comfoairControl.state == ON && comfoairMode.state == 1 ) { + var Number currentLevel = comfoairFanLevel.state as DecimalType + var Number newLevel + + // Summer energy time - from 1.04 to 30.09 + if( EnergySummerTime.state == ON ) { + + // First set level 1 and 2 + + // All days a week + // if you want Monday to Friday remove day==6 and day==7 + if( day == 1 || day == 2 || day == 3 || day == 4 || day == 5 || day == 6 || day == 7 ) { + + // Level 2 : 5:00 - 7:00, 16:00 - 23:00, level 1 : in the other time + if( ( hour >= 5 && hour < 7 ) + || + ( hour >= 16 && hour < 23 ) + ) { + newLevel = 3 + } else newLevel = 2 + + // Saterdey and Sunday + } else if( day == 6 || day == 7 ) { + + if( ( hour >= 12 && hour < 14 ) + ) { + newLevel = 3 + } else newLevel = 2 + } + + // Now set level 3 + + // All days a week + if( day == 1 || day == 2 || day == 3 || day == 4 || day == 5 || day == 6 || day == 7 ) { + + // Level 3 : 13:00 - 13:30 + if( ( hour == 13 && minute < 30 ) + ) { + newLevel = 4 + } + } + + // Winter energy time - from 1.10 to 31.03 + } else { + + // All days a week + // podwyzszenie 0:00-2:00, 3:00-5:00, 7:00-8:00, 10:00-11:00, 13:00-15:00, 16:30- 18:30, 20:00-21:00, 22:00-23:00 + if( day == 1 || day == 2 || day == 3 || day == 4 || day == 5 || day == 6 || day == 7 ) { + + // Level 2 : 6:00 - 8:00, 16:00 - 21:00, level 1 : in the other time + if( ( hour >= 6 && hour < 8 ) + || + ( hour >= 16 && hour < 21 ) + ) { + newLevel = 3 + } else newLevel = 2 + + // Saterdey and Sunday + } else if( day == 6 || day == 7 ) { + + if( ( hour >= 12 && hour < 14 ) + ) { + newLevel = 3 + } else newLevel = 2 + } + + // Now set level 3 + + // All days a week + if( day == 1 || day == 2 || day == 3 || day == 4 || day == 5 || day == 6 || day == 7 ) { + + // Level 3 : 13:00 - 13:30 + if( ( hour == 13 && minute < 30 ) + ) { + newLevel = 4 + } + } + } + + if( newLevel != currentLevel ) { + comfoairFanLevel.sendCommand( newLevel ) + isAutoMode = true + comfoairFanMode.postUpdate( 0 ) + } + + logInfo( "comfoair", "ComfoAir - AUTO Mode" ) + + } else logInfo( "comfoair", "ComfoAir - MANUAL Mode" ) + } +end +``` + +## 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..8635c8fc13a0c --- /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.2-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..e778ec5deb668 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirBindingConstants.java @@ -0,0 +1,30 @@ +/** + * 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"); +} 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..ca17c7d4f081b --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommand.java @@ -0,0 +1,133 @@ +/** + * 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 List keys; + private @Nullable Integer requestCmd; + private @Nullable Integer replyCmd; + private int[] requestData; + private @Nullable Integer requestValue; + private @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.requestData = data; + this.requestValue = requestValue; + this.dataPosition = dataPosition; + this.replyCmd = replyCmd; + } + + /** + * @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..81235f0e5fa17 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirCommandType.java @@ -0,0 +1,2263 @@ +/** + * 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.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.types.State; +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.DataTypeVolt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents all valid commands which could be processed by this binding + * + * @author Holger Hees - Initial Contribution + * @author Hans Böhm - Refactoring + */ +public enum ComfoAirCommandType { + + /** + * Below all valid commands to change or read parameters from ComfoAir + * + * @param key + * command name + * @param data_tape + * data type (can be: DataTypeBoolean.class, DataTypeMessage.class, + * DataTypeNumber.class, DataTypeRPM.class, + * DataTypeTemperature.class, DataTypeVolt.class) + * @param possible_values + * possible values for write command, if it can only take certain values + * @param change_command + * byte number for ComfoAir write command + * @param change_data_size + * size of bytes list for ComfoAir write command + * @param change_data_pos + * position in bytes list to change + * @param change_affected + * list of affected commands (can be empty) + * is mandatory for read-write command + * @param read_command + * request byte number for ComfoAir read command + * @param read_reply_command + * reply byte list size for ComfoAir read command (list of values only) + * @param read_reply_data_pos + * list of byte positions in reply bytes list from ComfoAir + * @param read_reply_data_bits + * byte value on read_reply_data_pos position to be considered by command (used with + * DataTypeBoolean.class data_type) + */ + ACTIVATE { + { + key = "bindingControl#activate"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x03 }; + change_command = 0x9b; + change_data_size = 1; + change_data_pos = 0; + change_affected = new String[] {}; + read_command = 0x9c; + read_reply_command = 0x9c; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x03; + } + }, + + MENU20_MODE { + { + key = "menuP1#menu20Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x01; + } + }, + + MENU21_MODE { + { + key = "menuP1#menu21Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x02; + } + }, + + MENU22_MODE { + { + key = "menuP1#menu22Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x04; + } + }, + + MENU23_MODE { + { + key = "menuP1#menu23Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x08; + } + }, + + MENU24_MODE { + { + key = "menuP1#menu24Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x10; + } + }, + + MENU25_MODE { + { + key = "menuP1#menu25Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x20; + } + }, + + MENU26_MODE { + { + key = "menuP1#menu26Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x40; + } + }, + + MENU27_MODE { + { + key = "menuP1#menu27Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 6 }; + read_reply_data_bits = 0x80; + } + }, + + MENU28_MODE { + { + key = "menuP1#menu28Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 7 }; + read_reply_data_bits = 0x01; + } + }, + + MENU29_MODE { + { + key = "menuP1#menu29Mode"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 7 }; + read_reply_data_bits = 0x02; + } + }, + + BATHROOM_START_DELAY { + { + key = "menuP2#bathroomStartDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 0; + change_affected = new String[] { "menuP1#menu21Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 0 }; + } + }, + + BATHROOM_END_DELAY { + { + key = "menuP2#bathroomEndDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 1; + change_affected = new String[] { "menuP1#menu22Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 1 }; + } + }, + + L1_END_DELAY { + { + key = "menuP2#L1EndDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 2; + change_affected = new String[] { "menuP1#menu27Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 2 }; + } + }, + + PULSE_VENTILATION { + { + key = "menuP2#pulseVentilation"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 3; + change_affected = new String[] { "menuP1#menu23Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 3 }; + } + }, + + FILTER_WEEKS { + { + key = "menuP2#filterWeeks"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 4; + change_affected = new String[] { "menuP1#menu24Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 4 }; + } + }, + + RF_SHORT_DELAY { + { + key = "menuP2#RFShortDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 5; + change_affected = new String[] { "menuP1#menu25Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 5 }; + } + }, + + RF_LONG_DELAY { + { + key = "menuP2#RFLongDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 6; + change_affected = new String[] { "menuP1#menu26Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 6 }; + } + }, + + COOKERHOOD_DELAY { + { + key = "menuP2#cookerhoodDelay"; + data_type = DataTypeNumber.class; + change_command = 0xcb; + change_data_size = 8; + change_data_pos = 7; + change_affected = new String[] { "menuP1#menu20Mode" }; + read_command = 0xc9; + read_reply_command = 0xca; + read_reply_data_pos = new int[] { 7 }; + } + }, + + CHIMNEY_STATE { + { + key = "menuP9#chimneyState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x01; + } + }, + + BYPASS_STATE { + { + key = "menuP9#bypassState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x02; + } + }, + + EWT_STATE { + { + key = "menuP9#EWTState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x04; + } + }, + + HEATER_STATE { + { + key = "menuP9#heaterState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x08; + } + }, + + V_CONTROL_STATE { + { + key = "menuP9#vControlState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x10; + } + }, + + FROST_STATE { + { + key = "menuP9#frostState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x20; + } + }, + + COOKERHOOD_STATE { + { + key = "menuP9#cookerhoodState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x40; + } + }, + + ENTHALPY_STATE { + { + key = "menuP9#enthalpyState"; + data_type = DataTypeBoolean.class; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x80; + } + }, + + FAN_OUT_0 { + { + key = "ventilation#fanOut0"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 0; + change_affected = new String[] { "ventilation#fanOutPercent", "ventilation#fanOutRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 0 }; + } + }, + + FAN_OUT_1 { + { + key = "ventilation#fanOut1"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 1; + change_affected = new String[] { "ventilation#fanOutPercent", "ventilation#fanOutRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 1 }; + } + }, + + FAN_OUT_2 { + { + key = "ventilation#fanOut2"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 2; + change_affected = new String[] { "ventilation#fanOutPercent", "ventilation#fanOutRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 2 }; + } + }, + + FAN_OUT_3 { + { + key = "ventilation#fanOut3"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 6; + change_affected = new String[] { "ventilation#fanOutPercent", "ventilation#fanOutRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 10 }; + } + }, + + FAN_IN_0 { + { + key = "ventilation#fanIn0"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 3; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanInRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 3 }; + } + }, + + FAN_IN_1 { + { + key = "ventilation#fanIn1"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 4; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanInRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 4 }; + } + }, + + FAN_IN_2 { + { + key = "ventilation#fanIn2"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 5; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanInRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 5 }; + } + }, + + FAN_IN_3 { + { + key = "ventilation#fanIn3"; + data_type = DataTypeNumber.class; + change_command = 0xcf; + change_data_size = 9; + change_data_pos = 7; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanInRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 11 }; + } + }, + + FAN_IN_PERCENT { + { + key = "ventilation#fanInPercent"; + data_type = DataTypeNumber.class; + read_command = 0x0b; + read_reply_command = 0x0c; + read_reply_data_pos = new int[] { 0 }; + } + }, + + FAN_OÚT_PERCENT { + { + key = "ventilation#fanOutPercent"; + data_type = DataTypeNumber.class; + read_command = 0x0b; + read_reply_command = 0x0c; + read_reply_data_pos = new int[] { 1 }; + } + }, + + FAN_IN_RPM { + { + key = "ventilation#fanInRPM"; + data_type = DataTypeRPM.class; + read_command = 0x0b; + read_reply_command = 0x0c; + read_reply_data_pos = new int[] { 2, 3 }; + } + }, + + FAN_OUT_RPM { + { + key = "ventilation#fanOutRPM"; + data_type = DataTypeRPM.class; + read_command = 0x0b; + read_reply_command = 0x0c; + read_reply_data_pos = new int[] { 4, 5 }; + } + }, + + FAN_LEVEL { + { + key = "ccease#fanLevel"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x01, 0x02, 0x03, 0x04 }; + change_command = 0x99; + change_data_size = 1; + change_data_pos = 0; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanOutPercent", + "ventilation#fanInRPM", "ventilation#fanOutRPM" }; + read_command = 0xcd; + read_reply_command = 0xce; + read_reply_data_pos = new int[] { 8 }; + } + }, + + TARGET_TEMPERATUR { + { + key = "ccease#targetTemperature"; + data_type = DataTypeTemperature.class; + change_command = 0xd3; + change_data_size = 1; + change_data_pos = 0; + change_affected = new String[] { "bypass#bypassFactor", "bypass#bypassLevel", "bypass#bypassSummer" }; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 0 }; + } + }, + + OUTDOOR_TEMPERATURE_IN { + { + key = "temperatures#outdoorTemperatureIn"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 1 }; + } + }, + + OUTDOOR_TEMPERATURE_OUT { + { + key = "temperatures#outdoorTemperatureOut"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 4 }; + } + }, + + INDOOR_TEMPERATURE_IN { + { + key = "temperatures#indoorTemperatureIn"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 2 }; + } + }, + + INDOOR_TEMPERATURE_OUT { + { + key = "temperatures#indoorTemperatureOut"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 3 }; + } + }, + + IS_T1_SENSOR { + { + key = "temperatures#isT1Sensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x01; + } + }, + + IS_T2_SENSOR { + { + key = "temperatures#isT2Sensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x02; + } + }, + + IS_T3_SENSOR { + { + key = "temperatures#isT3Sensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x04; + } + }, + + IS_T4_SENSOR { + { + key = "temperatures#isT4Sensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x08; + } + }, + + IS_EWT_SENSOR { + { + key = "temperatures#isEWTSensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x10; + } + }, + + IS_HEATER_SENSOR { + { + key = "temperatures#isHeaterSensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x20; + } + }, + + IS_COOKERHOOD_SENSOR { + { + key = "temperatures#isCookerhoodSensor"; + data_type = DataTypeBoolean.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 5 }; + read_reply_data_bits = 0x40; + } + }, + + EWT_TEMPERATUR { + { + key = "temperatures#ewtTemperature"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 6 }; + } + }, + + HEATER_TEMPERATUR { + { + key = "temperatures#heaterTemperature"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 7 }; + } + }, + + COOKERHOOD_TEMPERATUR { + { + key = "temperatures#cookerhoodTemperature"; + data_type = DataTypeTemperature.class; + read_command = 0xd1; + read_reply_command = 0xd2; + read_reply_data_pos = new int[] { 8 }; + } + }, + + IS_PREHEATER { + { + key = "options#isPreheater"; + data_type = DataTypeBoolean.class; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 1; + change_affected = new String[] { "temperatures#outdoorTemperatureIn", "temperatures#indoorTemperatureIn", + "preheater#preheaterFrostProtect", "preheater#preheaterFrostTime", "preheater#preheaterHeating", + "menuP9#frostState", "preheater#preheaterSafety", "times#preheaterTime", + "preheater#preheaterValve" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 0 }; + } + }, + + IS_BYPASS { + { + key = "options#isBypass"; + data_type = DataTypeBoolean.class; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 1; + change_affected = new String[] { "temperatures#indoorTemperatureIn", "temperatures#outdoorTemperatureOut" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 1 }; + } + }, + + RECU_TYPE { + { + key = "options#recuType"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x01, 0x02 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 2; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanOutPercent", + "temperatures#indoorTemperatureIn", "temperatures#outdoorTemperatureOut", + "temperatures#indoorTemperatureOut", "temperatures#outdoorTemperatureIn" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 2 }; + } + }, + + RECU_SIZE { + { + key = "options#recuSize"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x01, 0x02 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 3; + change_affected = new String[] { "ventilation#fanInPercent", "ventilation#fanOutPercent", + "ventilation#fanOut0", "ventilation#fanOut1", "ventilation#fanOut2", "ventilation#fanOut3", + "ventilation#fanIn0", "ventilation#fanIn1", "ventilation#fanIn2", "ventilation#fanIn3" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 3 }; + } + }, + + IS_CHIMNEY { + { + key = "options#isChimney"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 4; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 4 }; + read_reply_data_bits = 0x01; + } + }, + + IS_COOKERHOOD { + { + key = "options#isCookerhood"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x02 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 4; + change_affected = new String[] { "menuP2#cookerhoodDelay", "menuP9#cookerhoodState", + "cookerhood#cookerhoodSpeed", "temperatures#cookerhoodTemperature" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 4 }; + read_reply_data_bits = 0x02; + } + }, + + IS_HEATER { + { + key = "options#isHeater"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x04 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 4; + change_affected = new String[] { "heater#heaterTargetTemperature", "heater#heaterPower", + "menuP9#heaterState", "heater#heaterPowerI", "temperatures#heaterTemperature" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 4 }; + read_reply_data_bits = 0x04; + } + }, + + IS_ENTHALPY { + { + key = "options#isEnthalpy"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x00, 0x01, 0x02 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 6; + change_affected = new String[] { "enthalpy#enthalpyTemperature", "enthalpy#enthalpyHumidity", + "enthalpy#enthalpyLevel", "menuP9#enthalpyState", "enthalpy#enthalpyTime" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 9 }; + } + }, + + IS_EWT { + { + key = "options#isEWT"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x00, 0x01, 0x02 }; + change_command = 0xd7; + change_data_size = 8; + change_data_pos = 7; + change_affected = new String[] { "ewt#ewtSpeed", "ewt#ewtTemperatureLow", "menuP9#ewtState", + "ewt#ewtTemperatureHigh", "temperatures#ewtTemperature" }; + read_command = 0xd5; + read_reply_command = 0xd6; + read_reply_data_pos = new int[] { 10 }; + } + }, + + EWT_SPEED { + { + key = "ewt#ewtSpeed"; + data_type = DataTypeNumber.class; + change_command = 0xed; + change_data_size = 5; + change_data_pos = 2; + change_affected = new String[] { "menuP9#ewtState", "temperatures#ewtTemperature" }; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 2 }; + } + }, + + EWT_TEMPERATURE_LOW { + { + key = "ewt#ewtTemperatureLow"; + data_type = DataTypeTemperature.class; + change_command = 0xed; + change_data_size = 5; + change_data_pos = 0; + change_affected = new String[] { "menuP9#ewtState" }; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 0 }; + } + }, + + EWT_TEMPERATURE_HIGH { + { + key = "ewt#ewtTemperatureHigh"; + data_type = DataTypeTemperature.class; + change_command = 0xed; + change_data_size = 5; + change_data_pos = 1; + change_affected = new String[] { "menuP9#ewtState" }; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 1 }; + } + }, + + COOKERHOOD_SPEED { + { + key = "cookerhood#cookerhoodSpeed"; + data_type = DataTypeNumber.class; + change_command = 0xed; + change_data_size = 5; + change_data_pos = 3; + change_affected = new String[] { "menuP9#cookerhoodState", "temperatures#cookerhoodTemperature" }; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 3 }; + } + }, + + HEATER_POWER { + { + key = "heater#heaterPower"; + data_type = DataTypeNumber.class; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 4 }; + } + }, + + HEATER_POWER_I { + { + key = "heater#heaterPowerI"; + data_type = DataTypeNumber.class; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 5 }; + } + }, + + HEATER_TARGET_TEMPERATUR { + { + key = "heater#heaterTargetTemperature"; + data_type = DataTypeTemperature.class; + change_command = 0xed; + change_data_size = 5; + change_data_pos = 4; + change_affected = new String[] { "menuP9#heaterState", "heater#heaterPower", + "temperatures#heaterTemperature" }; + read_command = 0xeb; + read_reply_command = 0xec; + read_reply_data_pos = new int[] { 6 }; + } + }, + + SOFTWARE_MAIN_VERSION { + { + key = "software#softwareMainVersion"; + data_type = DataTypeNumber.class; + read_command = 0x69; + read_reply_command = 0x6a; + read_reply_data_pos = new int[] { 0 }; + } + }, + + SOFTWARE_MINOR_VERSION { + { + key = "software#softwareMinorVersion"; + data_type = DataTypeNumber.class; + read_command = 0x69; + read_reply_command = 0x6a; + read_reply_data_pos = new int[] { 1 }; + } + }, + + SOFTWARE_BETA_VERSION { + { + key = "software#softwareBetaVersion"; + data_type = DataTypeNumber.class; + read_command = 0x69; + read_reply_command = 0x6a; + read_reply_data_pos = new int[] { 2 }; + } + }, + + ERROR_MESSAGE { + { + key = "ccease#errorMessage"; + data_type = DataTypeMessage.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 0, 1, 9, 13 }; + } + }, + + ERRORA_CURRENT { + { + key = "error#errorACurrent"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 0 }; + } + }, + + ERRORA_LAST { + { + key = "error#errorALast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 2 }; + } + }, + + ERRORA_PRELAST { + { + key = "error#errorAPrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 4 }; + } + }, + + ERRORA_PREPRELAST { + { + key = "error#errorAPrePrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 6 }; + } + }, + + ERRORAHIGH_CURRENT { + { + key = "error#errorAHighCurrent"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 13 }; + } + }, + + ERRORAHIGH_LAST { + { + key = "error#errorAHighLast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 14 }; + } + }, + + ERRORAHIGH_PRELAST { + { + key = "error#errorAHighPrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 15 }; + } + }, + + ERRORAHIGH_PREPRELAST { + { + key = "error#errorAHighPrePrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 16 }; + } + }, + + ERRORE_CURRENT { + { + key = "error#errorECurrent"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 1 }; + } + }, + + ERRORE_LAST { + { + key = "error#errorELast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 3 }; + } + }, + + ERRORE_PRELAST { + { + key = "error#errorEPrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 5 }; + } + }, + + ERRORE_PREPRELAST { + { + key = "error#errorEPrePrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 7 }; + } + }, + + ERROREA_CURRENT { + { + key = "error#errorEACurrent"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 9 }; + } + }, + + ERROREA_LAST { + { + key = "error#errorEALast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 10 }; + } + }, + + ERROREA_PRELAST { + { + key = "error#errorEAPrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 11 }; + } + }, + + ERROREA_PREPRELAST { + { + key = "error#errorEAPrePrelast"; + data_type = DataTypeNumber.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 12 }; + } + }, + + ERROR_RESET { + { + key = "ccease#errorReset"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0xdb; + change_data_size = 4; + change_data_pos = 0; + change_affected = new String[] { "ccease#errorMessage" }; + } + }, + + FILTER_HOURS { + { + key = "times#filterHours"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 15, 16 }; + } + }, + + FILTER_RESET { + { + key = "ccease#filterReset"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0xdb; + change_data_size = 4; + change_data_pos = 3; + change_affected = new String[] { "times#filterHours", "ccease#filterError" }; + } + }, + + FILTER_ERROR { + { + key = "ccease#filterError"; + data_type = DataTypeBoolean.class; + read_command = 0xd9; + read_reply_command = 0xda; + read_reply_data_pos = new int[] { 8 }; + read_reply_data_bits = 0x01; + } + }, + + BYPASS_FACTOR { + { + key = "bypass#bypassFactor"; + data_type = DataTypeNumber.class; + read_command = 0xdf; + read_reply_command = 0xe0; + read_reply_data_pos = new int[] { 2 }; + } + }, + + BYPASS_LEVEL { + { + key = "bypass#bypassLevel"; + data_type = DataTypeNumber.class; + read_command = 0xdf; + read_reply_command = 0xe0; + read_reply_data_pos = new int[] { 3 }; + } + }, + + BYPASS_CORRECTION { + { + key = "bypass#bypassCorrection"; + data_type = DataTypeNumber.class; + read_command = 0xdf; + read_reply_command = 0xe0; + read_reply_data_pos = new int[] { 4 }; + } + }, + + BYPASS_SUMMER { + { + key = "bypass#bypassSummer"; + data_type = DataTypeBoolean.class; + read_command = 0xdf; + read_reply_command = 0xe0; + read_reply_data_pos = new int[] { 6 }; + } + }, + + ENTHALPY_TEMPERATUR { + { + key = "enthalpy#enthalpyTemperature"; + data_type = DataTypeTemperature.class; + read_command = 0x97; + read_reply_command = 0x98; + read_reply_data_pos = new int[] { 0 }; + } + }, + + ENTHALPY_HUMIDITY { + { + key = "enthalpy#enthalpyHumidity"; + data_type = DataTypeNumber.class; + read_command = 0x97; + read_reply_command = 0x98; + read_reply_data_pos = new int[] { 1 }; + } + }, + + ENTHALPY_LEVEL { + { + key = "enthalpy#enthalpyLevel"; + data_type = DataTypeNumber.class; + read_command = 0x97; + read_reply_command = 0x98; + read_reply_data_pos = new int[] { 4 }; + } + }, + + ENTHALPY_TIME { + { + key = "enthalpy#enthalpyTime"; + data_type = DataTypeNumber.class; + read_command = 0x97; + read_reply_command = 0x98; + read_reply_data_pos = new int[] { 5 }; + } + }, + + PREHEATER_VALVE { + { + key = "preheater#preheaterValve"; + data_type = DataTypeNumber.class; + read_command = 0xe1; + read_reply_command = 0xe2; + read_reply_data_pos = new int[] { 0 }; + } + }, + + PREHEATER_FROST_PROTECT { + { + key = "preheater#preheaterFrostProtect"; + data_type = DataTypeBoolean.class; + read_command = 0xe1; + read_reply_command = 0xe2; + read_reply_data_pos = new int[] { 1 }; + } + }, + + PREHEATER_HEATING { + { + key = "preheater#preheaterHeating"; + data_type = DataTypeBoolean.class; + read_command = 0xe1; + read_reply_command = 0xe2; + read_reply_data_pos = new int[] { 2 }; + } + }, + + PREHEATER_FROST_TIME { + { + key = "preheater#preheaterFrostTime"; + data_type = DataTypeNumber.class; + read_command = 0xe1; + read_reply_command = 0xe2; + read_reply_data_pos = new int[] { 3, 4 }; + } + }, + + PREHEATER_OPTION { + { + key = "preheater#preheaterSafety"; + data_type = DataTypeNumber.class; + read_command = 0xe1; + read_reply_command = 0xe2; + read_reply_data_pos = new int[] { 5 }; + } + }, + + LEVEL0_TIME { + { + key = "times#level0Time"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 0, 1, 2 }; + } + }, + + LEVEL1_TIME { + { + key = "times#level1Time"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 3, 4, 5 }; + } + }, + + LEVEL2_TIME { + { + key = "times#level2Time"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 6, 7, 8 }; + } + }, + + LEVEL3_TIME { + { + key = "times#level3Time"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 17, 18, 19 }; + } + }, + + FREEZE_TIME { + { + key = "times#freezeTime"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 9, 10 }; + } + }, + + PREHEATER_TIME { + { + key = "times#preheaterTime"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 11, 12 }; + } + }, + + BYPASS_TIME { + { + key = "times#bypassTime"; + data_type = DataTypeNumber.class; + read_command = 0xdd; + read_reply_command = 0xde; + read_reply_data_pos = new int[] { 13, 14 }; + } + }, + + IS_ANALOG1 { + { + key = "analog#isAnalog1"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 0; + change_affected = new String[] { "analog#analog1Mode", "analog#analog1Negative", "analog#analog1Min", + "analog#analog1Max", "analog#analog1Value", "analog#analog1Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x01; + } + }, + + IS_ANALOG2 { + { + key = "analog#isAnalog2"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x02 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 0; + change_affected = new String[] { "analog#analog2Mode", "analog#analog2Negative", "analog#analog2Min", + "analog#analog2Max", "analog#analog2Value", "analog#analog2Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x02; + } + }, + + IS_ANALOG3 { + { + key = "analog#isAnalog3"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x04 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 0; + change_affected = new String[] { "analog#analog3Mode", "analog#analog3Negative", "analog#analog3Min", + "analog#analog3Max", "analog#analog3Value", "analog#analog3Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x04; + } + }, + + IS_ANALOG4 { + { + key = "analog#isAnalog4"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x08 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 0; + change_affected = new String[] { "analog#analog4Mode", "analog#analog4Negative", "analog#analog4Min", + "analog#analog4Max", "analog#analog4Value", "analog#analog4Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x08; + } + }, + + IS_RF { + { + key = "analog#isRF"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x10 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 0; + change_affected = new String[] { "analog#RFMode", "analog#RFNegative", "analog#RFMin", "analog#RFMax", + "analog#RFValue" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x10; + } + }, + + ANALOG1_MODE { + { + key = "analog#analog1Mode"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 1; + change_affected = new String[] { "analog#analog1Negative", "analog#analog1Min", "analog#analog1Max", + "analog#analog1Value", "analog#analog1Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x01; + } + }, + + ANALOG2_MODE { + { + key = "analog#analog2Mode"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x02 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 1; + change_affected = new String[] { "analog#analog2Negative", "analog#analog2Min", "analog#analog2Max", + "analog#analog2Value", "analog#analog2Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x02; + } + }, + + ANALOG3_MODE { + { + key = "analog#analog3Mode"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x04 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 1; + change_affected = new String[] { "analog#analog3Negative", "analog#analog3Min", "analog#analog3Max", + "analog#analog3Value", "analog#analog3Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x04; + } + }, + + ANALOG4_MODE { + { + key = "analog#analog4Mode"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x08 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 1; + change_affected = new String[] { "analog#analog4Negative", "analog#analog4Min", "analog#analog4Max", + "analog#analog4Value", "analog#analog4Volt" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x08; + } + }, + + RF_MODE { + { + key = "analog#RFMode"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x10 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 1; + change_affected = new String[] { "analog#RFNegative", "analog#RFMin", "analog#RFMax", "analog#RFValue" }; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x10; + } + }, + + ANALOG1_NEGATIVE { + { + key = "analog#analog1Negative"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x01 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 2; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 2 }; + read_reply_data_bits = 0x01; + } + }, + + ANALOG2_NEGATIVE { + { + key = "analog#analog2Negative"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x02 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 2; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 2 }; + read_reply_data_bits = 0x02; + } + }, + + ANALOG3_NEGATIVE { + { + key = "analog#analog3Negative"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x04 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 2; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 2 }; + read_reply_data_bits = 0x04; + } + }, + + ANALOG4_NEGATIVE { + { + key = "analog#analog4Negative"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x08 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 2; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 2 }; + read_reply_data_bits = 0x08; + } + }, + + RF_NEGATIVE { + { + key = "analog#RFNegative"; + data_type = DataTypeBoolean.class; + possible_values = new int[] { 0x10 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 2; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 2 }; + read_reply_data_bits = 0x10; + } + }, + + ANALOG1_MIN { + { + key = "analog#analog1Min"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 3; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 3 }; + } + }, + + ANALOG1_MAX { + { + key = "analog#analog1Max"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 4; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 4 }; + } + }, + + ANALOG1_VALUE { + { + key = "analog#analog1Value"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 5; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 5 }; + } + }, + + ANALOG2_MIN { + { + key = "analog#analog2Min"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 6; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 6 }; + } + }, + + ANALOG2_MAX { + { + key = "analog#analog2Max"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 7; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 7 }; + } + }, + + ANALOG2_VALUE { + { + key = "analog#analog2Value"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 8; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 8 }; + } + }, + + ANALOG3_MIN { + { + key = "analog#analog3Min"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 9; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 9 }; + } + }, + + ANALOG3_MAX { + { + key = "analog#analog3Max"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 10; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 10 }; + } + }, + + ANALOG3_VALUE { + { + key = "analog#analog3Value"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 11; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 11 }; + } + }, + + ANALOG4_MIN { + { + key = "analog#analog4Min"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 12; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 12 }; + } + }, + + ANALOG4_MAX { + { + key = "analog#analog4Max"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 13; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 13 }; + } + }, + + ANALOG4_VALUE { + { + key = "analog#analog4Value"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 14; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 14 }; + } + }, + + RF_MIN { + { + key = "analog#RFMin"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 15; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 15 }; + } + }, + + RF_MAX { + { + key = "analog#RFMax"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 16; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 16 }; + } + }, + + RF_VALUE { + { + key = "analog#RFValue"; + data_type = DataTypeNumber.class; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 17; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 17 }; + } + }, + + ANALOG_MODE { + { + key = "analog#analogMode"; + data_type = DataTypeNumber.class; + possible_values = new int[] { 0x00, 0x01 }; + change_command = 0x9f; + change_data_size = 19; + change_data_pos = 18; + change_affected = new String[] {}; + read_command = 0x9d; + read_reply_command = 0x9e; + read_reply_data_pos = new int[] { 18 }; + } + }, + + ANALOG1_VOLT { + { + key = "analog#analog1Volt"; + data_type = DataTypeVolt.class; + read_command = 0x13; + read_reply_command = 0x14; + read_reply_data_pos = new int[] { 0 }; + } + }, + + ANALOG2_VOLT { + { + key = "analog#analog2Volt"; + data_type = DataTypeVolt.class; + read_command = 0x13; + read_reply_command = 0x14; + read_reply_data_pos = new int[] { 1 }; + } + }, + + ANALOG3_VOLT { + { + key = "analog#analog3Volt"; + data_type = DataTypeVolt.class; + read_command = 0x13; + read_reply_command = 0x14; + read_reply_data_pos = new int[] { 2 }; + } + }, + + ANALOG4_VOLT { + { + key = "analog#analog4Volt"; + data_type = DataTypeVolt.class; + read_command = 0x13; + read_reply_command = 0x14; + read_reply_data_pos = new int[] { 3 }; + } + }, + + IS_L1_SWITCH { + { + key = "inputs#isL1Switch"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x01; + } + }, + + IS_L2_SWITCH { + { + key = "inputs#isL2Switch"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 0 }; + read_reply_data_bits = 0x02; + } + }, + + IS_BATHROOM_SWITCH { + { + key = "inputs#isBathroomSwitch"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x01; + } + }, + + IS_COOKERHOOD_SWITCH { + { + key = "inputs#isCookerhoodSwitch"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x02; + } + }, + + IS_EXTERNAL_FILTER { + { + key = "inputs#isExternalFilter"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x04; + } + }, + + IS_WTW { + { + key = "inputs#isWTW"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x08; + } + }, + + IS_BATHROOM2_SWITCH { + { + key = "inputs#isBathroom2Switch"; + data_type = DataTypeBoolean.class; + read_command = 0x03; + read_reply_command = 0x04; + read_reply_data_pos = new int[] { 1 }; + read_reply_data_bits = 0x10; + } + }; + + Logger logger = LoggerFactory.getLogger(ComfoAirCommandType.class); + String key; + Class data_type; + + /* + * Possible values + */ + int[] possible_values; + + /* + * Cmd code to change properties on the comfoair. + */ + int change_command; + /* + * The size of the data block. + */ + int change_data_size; + /* + * The byte inside the data block which holds the crucial value. + */ + int change_data_pos; + /* + * Affected commands which should be refreshed after a successful change + * command call. + */ + String[] change_affected; + + /* + * Command for reading properties. + */ + int read_command; + + /* + * ACK Command which identifies the matching response. + */ + int read_reply_command; + + /* + * The byte position inside the response data. + */ + int[] read_reply_data_pos; + + /* + * Bit mask for boolean response properties to identify a true value. + */ + int read_reply_data_bits; + + /** + * @return command key + */ + public String getKey() { + return key; + } + + /** + * @return data type for this command key + */ + public ComfoAirDataType getDataType() { + try { + return data_type.newInstance(); + } catch (Exception e) { + logger.debug("Creating new DataType went wrong ", e); + } + return null; + } + + /** + * @return possible byte values + */ + public int[] getPossibleValues() { + return possible_values; + } + + /** + * @return relevant byte position inside the response byte value array + */ + public int getChangeDataPos() { + return change_data_pos; + } + + /** + * @return generate a byte value sequence for the response stream + */ + public int[] getChangeDataTemplate() { + int[] template = new int[change_data_size]; + for (int i = 0; i < template.length; i++) { + template[i] = 0x00; + } + return template; + } + + /** + * @return byte position inside the request byte value array + */ + public int[] getGetReplyDataPos() { + return read_reply_data_pos; + } + + /** + * @return bit mask for the response byte value + */ + public int getGetReplyDataBits() { + return read_reply_data_bits; + } + + /** + * Get single read command to update item. + * + * @param key + * + * @return ComfoAirCommand identified by key + */ + public static ComfoAirCommand getReadCommand(String key) { + ComfoAirCommandType commandType = ComfoAirCommandType.getCommandTypeByKey(key); + + if (commandType != null) { + Integer getCmd = commandType.read_command == 0 ? null : new Integer(commandType.read_command); + Integer replyCmd = new Integer(commandType.read_reply_command); + + return new ComfoAirCommand(key, getCmd, replyCmd, new int[0], null, null); + } + return null; + } + + /** + * Get a command to change properties on the comfoair. + * + * @param key + * command key + * @param value + * new state + * @return initialized ComfoAirCommand + */ + public static ComfoAirCommand getChangeCommand(String key, State value) { + ComfoAirCommandType commandType = ComfoAirCommandType.getCommandTypeByKey(key); + DecimalType decimalValue = value.as(DecimalType.class); + + if (commandType != null && decimalValue != null) { + ComfoAirDataType dataType = commandType.getDataType(); + int[] data = dataType.convertFromState(value, commandType); + int dataPossition = commandType.getChangeDataPos(); + int intValue = decimalValue.intValue(); + + return new ComfoAirCommand(key, commandType.change_command, null, data, dataPossition, 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 = ComfoAirCommandType.getCommandTypeByKey(key); + if (commandType.read_reply_command != 0) { + Integer getCmd = commandType.read_command == 0 ? null : new Integer(commandType.read_command); + Integer replyCmd = new Integer(commandType.read_reply_command); + + ComfoAirCommand command = new ComfoAirCommand(key, getCmd, replyCmd, new int[0], null, null); + commands.put(command.getReplyCmd(), command); + } + + for (String affectedKey : commandType.change_affected) { + // refresh affected event keys only when they are used + if (!usedKeys.contains(affectedKey)) { + continue; + } + + ComfoAirCommandType affectedCommandType = ComfoAirCommandType.getCommandTypeByKey(affectedKey); + + Integer getCmd = affectedCommandType.read_command == 0 ? null + : new Integer(affectedCommandType.read_command); + Integer replyCmd = new Integer(affectedCommandType.read_reply_command); + + ComfoAirCommand command = commands.get(replyCmd); + + if (command == null) { + command = new ComfoAirCommand(affectedKey, getCmd, replyCmd, new int[0], null, null); + commands.put(command.getReplyCmd(), command); + } else { + command.addKey(affectedKey); + } + } + + 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)) { + continue; + } + if (entry.read_reply_command == 0) { + continue; + } + + Integer getCmd = entry.read_command == 0 ? null : new Integer(entry.read_command); + Integer replyCmd = new Integer(entry.read_reply_command); + + ComfoAirCommand command = commands.get(replyCmd); + + if (command == null) { + command = new ComfoAirCommand(entry.key, getCmd, replyCmd, new int[0], null, null); + commands.put(command.getReplyCmd(), command); + } else { + command.addKey(entry.key); + } + } + + 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.read_reply_command != replyCmd) { + continue; + } + commands.add(entry); + } + return commands; + } + + /** + * Get a specific command. + * + * @param key + * command key + * @return ComfoAirCommandType identified by key + */ + public static ComfoAirCommandType getCommandTypeByKey(String key) { + for (ComfoAirCommandType entry : values()) { + if (entry.key.equals(key)) { + return entry; + } + } + return null; + } + +} 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..3404b4c5099be --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirConfiguration.java @@ -0,0 +1,35 @@ +/** + * 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.jdt.annotation.Nullable; + +/** + * 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 @Nullable 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..686edaa775edb --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirHandler.java @@ -0,0 +1,290 @@ +/** + * 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.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +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.library.types.QuantityType; +import org.eclipse.smarthome.core.library.unit.SIUnits; +import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; +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.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.openhab.binding.comfoair.internal.datatypes.DataTypeBoolean; +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.DataTypeVolt; +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 = 60; + + private final Logger logger = LoggerFactory.getLogger(ComfoAirHandler.class); + private @Nullable ScheduledFuture poller; + private SerialPortManager serialPortManager; + 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 { + try { + Set channelsLinked = getThing().getChannels().stream().map(Channel::getUID) + .filter(this::isLinked).collect(Collectors.toSet()); + Set keysToUpdate = channelsLinked.stream().map(ChannelUID::getId).collect(Collectors.toSet()); + + ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(channelId); + ComfoAirDataType dataType = comfoAirCommandType.getDataType(); + State state = null; + + if (dataType instanceof DataTypeBoolean) { + state = (OnOffType) command; + } else if (dataType instanceof DataTypeNumber || dataType instanceof DataTypeRPM) { + state = (DecimalType) command; + } else if (dataType instanceof DataTypeTemperature) { + if (command instanceof QuantityType) { + QuantityType celsius = ((QuantityType) command).toUnit(SIUnits.CELSIUS); + if (celsius != null) { + state = new DecimalType(celsius.doubleValue()); + } + } else { + state = (DecimalType) command; + } + } else if (dataType instanceof DataTypeVolt) { + if (command instanceof QuantityType) { + QuantityType volts = ((QuantityType) command).toUnit(SmartHomeUnits.VOLT); + if (volts != null) { + state = new DecimalType(volts.doubleValue()); + } + } else { + state = (DecimalType) command; + } + } + + if (state != null) { + ComfoAirCommand changeCommand = ComfoAirCommandType.getChangeCommand(channelId, state); + sendCommand(changeCommand, channelId); + + Collection affectedReadCommands = ComfoAirCommandType + .getAffectedReadCommands(channelId, keysToUpdate); + + if (affectedReadCommands.size() > 0) { + Runnable updateThread = new AffectedItemsUpdateThread(affectedReadCommands); + scheduler.schedule(updateThread, 3, TimeUnit.SECONDS); + } + } else { + logger.warn("Unhandled command type: {}", command.toString()); + } + } catch (final RuntimeException e) { + logger.warn("Updating ComfoAir failed: ", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + + @Override + public void initialize() { + ComfoAirConfiguration config = getConfigAs(ComfoAirConfiguration.class); + String serialPort = (config.serialPort != null) ? config.serialPort : ""; + + if (StringUtils.isNotEmpty(serialPort)) { + comfoAirConnector = new ComfoAirSerialConnector(serialPortManager, serialPort, BAUDRATE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + return; + } + if (comfoAirConnector != null) { + comfoAirConnector.open(); + updateStatus(ThingStatus.ONLINE); + + List channels = this.thing.getChannels(); + + poller = scheduler.scheduleWithFixedDelay(() -> { + for (Channel channel : channels) { + updateChannelState(channel); + } + }, 0, (config.refreshInterval > 0) ? config.refreshInterval : DEFAULT_REFRESH_INTERVAL, TimeUnit.SECONDS); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } + } + + @Override + public void dispose() { + if (comfoAirConnector != null) { + comfoAirConnector.close(); + } + + final ScheduledFuture localPoller = poller; + + if (localPoller != null && !localPoller.isCancelled()) { + localPoller.cancel(true); + poller = null; + } + } + + private void updateChannelState(Channel channel) { + try { + if (!isLinked(channel.getUID())) { + return; + } + String commandKey = channel.getUID().getId(); + ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(commandKey); + State state = sendCommand(readCommand, commandKey); + + updateState(channel.getUID(), state); + } catch (IllegalArgumentException e) { + logger.warn("Unknown channel {}", channel.getUID().getId()); + } + } + + 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 = new int[0]; + + if (requestCmd != null) { + switch (requestCmd) { + case 0x9f: + preRequestCmd = 0x9d; + preReplyCmd = 0x9e; + break; + case 0xcb: + preRequestCmd = 0xc9; + preReplyCmd = 0xca; + break; + case 0xcf: + preRequestCmd = 0xcd; + preReplyCmd = 0xce; + break; + case 0xd7: + preRequestCmd = 0xd5; + preReplyCmd = 0xd6; + break; + case 0xed: + preRequestCmd = 0xeb; + preReplyCmd = 0xec; + break; + default: + preRequestCmd = requestCmd; + preReplyCmd = replyCmd; + } + + if (!preRequestCmd.equals(requestCmd)) { + command.setRequestCmd(preRequestCmd); + command.setReplyCmd(preReplyCmd); + command.setRequestData(new int[0]); + + preResponse = comfoAirConnector.sendCommand(command, new int[0]); + + 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); + + ComfoAirDataType dataType = comfoAirCommandType.getDataType(); + State value = dataType.convertToState(response, comfoAirCommandType); + + if (value == null) { + logger.warn("unexpected value for DATA: {}", ComfoAirSerialConnector.dumpData(response)); + return UnDefType.UNDEF; + } else { + return value; + } + } + } + } + return UnDefType.UNDEF; + } + + private class AffectedItemsUpdateThread extends Thread { + + 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..08213ae09c214 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/ComfoAirSerialConnector.java @@ -0,0 +1,655 @@ +/** + * 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.OutputStream; +import java.util.TooManyListenersException; + +import org.apache.commons.io.IOUtils; +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.SerialPortEvent; +import org.eclipse.smarthome.io.transport.serial.SerialPortEventListener; +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 implements SerialPortEventListener { + + private final Logger logger = LoggerFactory.getLogger(ComfoAirSerialConnector.class); + + private static byte[] START = { (byte) 0x07, (byte) 0xf0 }; + private static byte[] END = { (byte) 0x07, (byte) 0x0f }; + private static byte[] ACK = { (byte) 0x07, (byte) 0xf3 }; + + private boolean isSuspended = true; + + private boolean connected = false; + 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. + */ + public void open() { + 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.addEventListener(this); + serialPort.notifyOnDataAvailable(true); + this.serialPort = serialPort; + + inputStream = new DataInputStream(new BufferedInputStream(serialPort.getInputStream())); + outputStream = serialPort.getOutputStream(); + + ComfoAirCommand command = ComfoAirCommandType.getChangeCommand(ComfoAirCommandType.ACTIVATE.key, + OnOffType.ON); + + if (command != null) { + sendCommand(command, new int[0]); + } else { + logger.debug("Failure while creating COMMAND: {}", command); + } + setConnected(true); + } else { + logger.debug("open(): No such Port: {}", serialPortName); + setConnected(false); + } + } catch (PortInUseException e) { + logger.debug("open(): Port in Use Exception: {}", e.getMessage(), e); + setConnected(false); + } catch (UnsupportedCommOperationException e) { + logger.debug("open(): Unsupported Comm Operation Exception: {}", e.getMessage(), e); + setConnected(false); + } catch (IOException e) { + logger.debug("open(): IO Exception: {}", e.getMessage(), e); + setConnected(false); + } catch (TooManyListenersException e) { + logger.debug("open(): Too Many Listeners Exception: {}", e.getMessage(), 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.key, + OnOffType.OFF); + + if (command != null) { + sendCommand(command, new int[0]); + } else { + logger.debug("Failure while creating COMMAND: {}", command); + } + + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outputStream); + 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 == 0x9b) { + isSuspended = !isSuspended; + } else if (requestCmd == 0x9c) { + return new int[] { isSuspended ? 0x00 : 0x03 }; + } else if (isSuspended) { + logger.trace("Ignore cmd. Service is currently suspended"); + return new int[0]; + } + + 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 new int[0]; + } + } + + // Fake read request for ccease properties + if (requestData.length <= 0 && requestCmd == 0x37) { + requestData = new int[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + } + + byte[] requestBlock = calculateRequest(requestCmd, requestData); + logger.trace("send DATA: {}", dumpData(requestBlock)); + + if (!send(requestBlock)) { + return new int[0]; + } + + 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) { + // ignore interruption + } + + } while (inputStream != null && inputStream.available() > 0); + + // check for ACK + if (responseBlock.length >= 2 && responseBlock[0] == (byte) 0x07 + && responseBlock[1] == (byte) 0xf3) { + if (command.getReplyCmd() == null) { + // confirm additional data with an ACK + if (responseBlock.length > 2) { + send(ACK); + } + return new int[0]; + } + + // 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] == (byte) 0x07 + && responseBlock[3] == (byte) 0xf0 + && responseBlock[responseBlock.length - 2] == (byte) 0x07 + && responseBlock[responseBlock.length - 1] == (byte) 0x0f + && (responseBlock[5] & 0xff) == command.getReplyCmd()) { + + 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 = new byte[3 + replyData.length]; + System.arraycopy(cleanedBlock, 0, _block, 0, _block.length); + + // validate calculated checksum against submitted + // checksum + if (calculateChecksum(_block) == checksum) { + + 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"); + } + + logger.trace("skip CMD: {} DATA: {}", String.format("%02x", command.getReplyCmd()), + dumpData(cleanedBlock)); + } + } + + } catch (IOException e) { + logger.debug("IO error: {}", e.getMessage()); + } + + try { + + Thread.sleep(1000); + logger.debug("Retry cmd. Last call was not successful. Request: {} Response: {}", + dumpData(requestBlock), (responseBlock.length > 0 ? dumpData(responseBlock) : "null")); + + } catch (InterruptedException e) { + // ignore interruption + } + + } while (retry++ < 5); + + if (retry == 5) { + logger.debug("Unable to send command. {} retries failed.", retry); + } + } + return new int[0]; + } + + /** + * 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; + + String hexString = Integer.toHexString(datasum); + if (hexString.length() > 2) { + hexString = hexString.substring(hexString.length() - 2); + } + + return (byte) Integer.parseInt(hexString, 16); + } + + /** + * 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[50]; + + for (int i = 4; i < processBuffer.length - 2; i++) { + + if ((byte) 0x07 == processBuffer[i] && (byte) 0x07 == processBuffer[i + 1]) { + i++; + } + + cleanedBuffer[pos] = processBuffer[i]; + pos++; + } + + byte[] _block = new byte[pos]; + System.arraycopy(cleanedBuffer, 0, _block, 0, _block.length); + + return _block; + } + + /** + * 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 ((byte) 0x07 == cleanedBuffer[i]) { + processBuffer[pos] = (byte) 0x07; + pos++; + } + + processBuffer[pos] = cleanedBuffer[i]; + pos++; + } + + byte[] _block = new byte[pos]; + System.arraycopy(processBuffer, 0, _block, 0, _block.length); + + return _block; + } + + /** + * Send the byte values. + * + * @param request + * @return successful flag + */ + private boolean send(byte[] request) { + 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; + } catch (NullPointerException 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) { + + StringBuffer sb = new StringBuffer(); + for (int ch : replyData) { + sb.append(String.format(" %02x", ch)); + } + return sb.toString(); + } + + private String dumpData(byte[] data) { + + StringBuffer sb = new StringBuffer(); + 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(); + logger.debug("Building request data: requestCmd = {}, dataPosition = {}, requestValue = {}", requestCmd, + dataPosition, requestValue); + + if (requestCmd != null && dataPosition != null && requestValue != null) { + if (requestCmd == 0xcb) { + newRequestData = new int[8]; + + if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) { + + for (int i = 0; i < newRequestData.length; i++) { + + if (dataPosition == i) { + newRequestData[i] = requestValue; + } else { + newRequestData[i] = preRequestData[i]; + } + } + + } else { + return new int[0]; + } + + } else if (requestCmd == 0xcf) { + newRequestData = new int[9]; + + if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) { + + for (int i = 0; i < newRequestData.length; i++) { + int j = i > 5 ? i + 4 : i; + + if (dataPosition == i) { + newRequestData[i] = requestValue; + } else { + newRequestData[i] = preRequestData[j]; + } + } + + } else { + return new int[0]; + } + + } else if (requestCmd == 0xd7) { + newRequestData = new int[8]; + + if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) { + + for (int i = 0; i < newRequestData.length; i++) { + int j = i > 5 ? i + 3 : i; + + if (dataPosition == i) { + + if (dataPosition == 4) { + requestValue = checkByteAndCalculateValue(command, requestValue, preRequestData[j]); + + newRequestData[i] = preRequestData[j] + requestValue; + + } else { + newRequestData[i] = requestValue; + } + + } else { + newRequestData[i] = preRequestData[j]; + } + } + + } else { + return new int[0]; + } + + } else if (requestCmd == 0xed) { + newRequestData = new int[5]; + + if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) { + + for (int i = 0; i < newRequestData.length; i++) { + int j = i > 3 ? i + 2 : i; + + if (dataPosition == i) { + newRequestData[i] = requestValue; + } else { + newRequestData[i] = preRequestData[j]; + } + } + + } else { + return new int[0]; + } + + } else if (requestCmd == 0x9f) { + newRequestData = new int[19]; + + if (preRequestData.length > 0 && newRequestData.length <= preRequestData.length) { + + for (int i = 0; i < newRequestData.length; i++) { + + if (dataPosition == i) { + + if (dataPosition == 0 || dataPosition == 1 || dataPosition == 2) { + requestValue = checkByteAndCalculateValue(command, requestValue, preRequestData[i]); + + newRequestData[i] = preRequestData[i] + requestValue; + + } else { + newRequestData[i] = requestValue; + } + + } else { + newRequestData[i] = preRequestData[i]; + } + } + + } else { + return new int[0]; + } + + } else { + return new int[0]; + } + + return newRequestData; + } else { + return new int[0]; + } + } + + /** + * 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); + int possibleValue = commandType.getPossibleValues()[0]; + + boolean isActive = (preValue & possibleValue) == possibleValue; + int newValue; + + if (isActive) { + newValue = requestValue == 1 ? 0 : -possibleValue; + } else { + newValue = requestValue == 1 ? possibleValue : 0; + } + return newValue; + } + + /** + * @return true if connected or false if not + */ + public boolean isConnected() { + return connected; + } + + /** + * Set the connection state + * + * @param connected true if connected or false if not + */ + public void setConnected(boolean connected) { + this.connected = connected; + } + + @Override + public void serialEvent(SerialPortEvent event) { + try { + logger.trace("RXTX library CPU load workaround, sleep forever"); + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException 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..4640d42bb7528 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/ComfoAirDataType.java @@ -0,0 +1,44 @@ +/** + * 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.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 + */ +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[] convertFromState(State value, ComfoAirCommandType commandType); + +} 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..a7916943961c4 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeBoolean.java @@ -0,0 +1,83 @@ +/** + * 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.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.types.State; +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 + */ +public class DataTypeBoolean implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeBoolean.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeBoolean\" class \"convertToState\" method parameter: null"); + return null; + } else { + + int[] get_reply_data_pos = commandType.getGetReplyDataPos(); + int get_reply_data_bits = commandType.getGetReplyDataBits(); + + if (get_reply_data_pos[0] < data.length) { + boolean result = (data[get_reply_data_pos[0]] & get_reply_data_bits) == get_reply_data_bits; + return (result) ? OnOffType.ON : OnOffType.OFF; + } else { + return null; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + + if (value == null || commandType == null) { + logger.trace("\"DataTypeBoolean\" class \"convertFromState\" method parameter: null"); + return null; + } else { + DecimalType decimalValue = value.as(DecimalType.class); + + if (decimalValue != null) { + int[] template = commandType.getChangeDataTemplate(); + + template[commandType.getChangeDataPos()] = decimalValue.intValue() == 1 + ? commandType.getPossibleValues()[0] + : 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..a87e54091ee3f --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeMessage.java @@ -0,0 +1,113 @@ +/** + * 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.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.State; +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 + */ +public class DataTypeMessage implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeMessage.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeMessage\" class \"convertToState\" method parameter: null"); + return null; + } else { + + int[] get_reply_data_pos = commandType.getGetReplyDataPos(); + + int errorAlo = data[get_reply_data_pos[0]]; + int errorE = data[get_reply_data_pos[1]]; + int errorEA = data[get_reply_data_pos[2]]; + int errorAhi = data[get_reply_data_pos[3]]; + + String errorCode = ""; + + if (errorAlo > 0) { + errorCode = "A:" + convertToCode(errorAlo); + } + + else if (errorAhi > 0) { + if (errorAhi == 0x80) { + errorCode = "A0"; + } + errorCode = "A:" + (convertToCode(errorAhi) + 8); + } + + if (errorE > 0) { + if (errorCode.length() > 0) { + errorCode += " "; + } + errorCode += "E:" + convertToCode(errorE); + } else if (errorEA > 0) { + if (errorCode.length() > 0) { + errorCode += " "; + } + errorCode += "EA:" + convertToCode(errorEA); + } + + return new StringType(errorCode.length() > 0 ? errorCode : "Ok"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + return null; + } + + private int convertToCode(int code) { + if (code == 0x1) { + return 1; + } + if (code == 0x2) { + return 2; + } + if (code == 0x4) { + return 3; + } + if (code == 0x8) { + return 4; + } + if (code == 0xF) { + return 5; + } + if (code == 0x20) { + return 6; + } + if (code == 0x40) { + return 7; + } + if (code == 0x80) { + return 8; + } + 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..34611625bf698 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeNumber.java @@ -0,0 +1,108 @@ +/** + * 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.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.types.State; +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 + */ +public class DataTypeNumber implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeNumber.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeNumber\" class \"convertToState\" method parameter: null"); + return null; + } else { + + int[] get_reply_data_pos = commandType.getGetReplyDataPos(); + + int value = 0; + int base = 0; + + for (int i = get_reply_data_pos.length - 1; i >= 0; i--) { + + if (get_reply_data_pos[i] < data.length) { + value += data[get_reply_data_pos[i]] << base; + base += 8; + } else { + return 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 null; + } + + return new DecimalType(value); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + + if (value == null || commandType == null) { + logger.trace("\"DataTypeNumber\" class \"convertFromState\" method parameter: null"); + 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..ed11b98c8cfa1 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/java/org/openhab/binding/comfoair/internal/datatypes/DataTypeRPM.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.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.types.State; +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 + */ +public class DataTypeRPM implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeRPM.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeRPM\" class \"convertToState\" method parameter: null"); + return null; + } else { + + int[] get_reply_data_pos = commandType.getGetReplyDataPos(); + + int value = 0; + int base = 0; + + for (int i = get_reply_data_pos.length - 1; i >= 0; i--) { + + if (get_reply_data_pos[i] < data.length) { + value += data[get_reply_data_pos[i]] << base; + base += 8; + } else { + return null; + } + } + + return new DecimalType((int) (1875000.0 / value)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + + if (value == null || commandType == null) { + logger.trace("\"DataTypeRPM\" class \"convertFromState\" method parameter: null"); + return null; + } else { + + int[] template = commandType.getChangeDataTemplate(); + + 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..36e8b6b4ae585 --- /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.smarthome.core.library.types.DecimalType; +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.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 + */ +public class DataTypeTemperature implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeTemperature.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeTemperature\" class \"convertToState\" method parameter: null"); + return null; + } else { + + if (commandType.getGetReplyDataPos()[0] < data.length) { + return new QuantityType<>((((double) data[commandType.getGetReplyDataPos()[0]]) / 2) - 20, + SIUnits.CELSIUS); + } else { + return null; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + + if (value == null || commandType == null) { + logger.trace("\"DataTypeTemperature\" class \"convertFromState\" method parameter: null"); + return null; + } else { + + int[] template = commandType.getChangeDataTemplate(); + + template[commandType.getChangeDataPos()] = (int) (((DecimalType) value).doubleValue() + 20) * 2; + + return template; + } + } + +} 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..5f0f03327012c --- /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.smarthome.core.library.types.DecimalType; +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.openhab.binding.comfoair.internal.ComfoAirCommandType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class to handle volt values + * + * @author Grzegorz Miasko + * @author Hans Böhm - QuantityTypes + */ +public class DataTypeVolt implements ComfoAirDataType { + + private Logger logger = LoggerFactory.getLogger(DataTypeVolt.class); + + /** + * {@inheritDoc} + */ + @Override + public State convertToState(int[] data, ComfoAirCommandType commandType) { + + if (data == null || commandType == null) { + logger.trace("\"DataTypeVolt\" class \"convertToState\" method parameter: null"); + return null; + } else { + + if (commandType.getGetReplyDataPos()[0] < data.length) { + return new QuantityType<>((double) data[commandType.getGetReplyDataPos()[0]] * 10 / 255, + SmartHomeUnits.VOLT); + } else { + return null; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public int[] convertFromState(State value, ComfoAirCommandType commandType) { + + if (value == null || commandType == null) { + logger.trace("\"DataTypeVolt\" class \"convertFromState\" method parameter: null"); + return null; + } else { + + int[] template = commandType.getChangeDataTemplate(); + + template[commandType.getChangeDataPos()] = (int) (((DecimalType) value).doubleValue() * 255 / 10); + + return template; + } + } + +} 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/i18n/comfoair_xx_XX.properties b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/i18n/comfoair_xx_XX.properties new file mode 100644 index 0000000000000..2a4a48cb9cd84 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/i18n/comfoair_xx_XX.properties @@ -0,0 +1,17 @@ +# FIXME: please substitute the xx_XX with a proper locale, ie. de_DE +# FIXME: please do not add the file to the repo if you add or change no content +# binding +binding.comfoair.name = +binding.comfoair.description = + +# thing types +thing-type.comfoair.sample.label = +thing-type.comfoair.sample.description = + +# thing type config description +thing-type.config.comfoair.sample.config1.label = +thing-type.config.comfoair.sample.config1.description = + +# channel types +channel-type.comfoair.sample-channel.label = +channel-type.comfoair.sample-channel.description = 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..107186709c1d9 --- /dev/null +++ b/bundles/org.openhab.binding.comfoair/src/main/resources/ESH-INF/thing/thing-types.xml @@ -0,0 +1,1596 @@ + + + + + + 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 + + + + + String + + Current errors + Text + + + + + Switch + + Reset filter operating hours + Switch + + + + + Switch + + 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 + + Operating hours at level 0 (away) + Number + + + + + Number + + Operating hours at level 1 + Number + + + + + Number + + Operating hours at level 2 + Number + + + + + Number + + Operating hours at level 3 + Number + + + + + Number + + Operating hours of antifrost + Number + + + + + Number + + Operating hours of preheater + Number + + + + + Number + + Hours of bypass open + Number + + + + + Number + + Operating hours of the filter + Number + + + + + 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 + + Preheater option installed + Switch + + + + + Switch + + Bypass option installed + Switch + + + + + Number + + Type of the ComfoAir (1 = left / 2 = right) + Number + + + + + + + + + + Number + + Size of the ComfoAir (1 = big / 2 = small) + Number + + + + + + + + + + Switch + + Chimney option installed + Switch + + + + + Switch + + Cookerhood option installed + Switch + + + + + Switch + + Heater option installed + Switch + + + + + Number + + Enthalpy option installed + Number + + + + + + + + + + + Number + + EWT option installed + Number + + + + + + + + + + + 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 1 + Switch + + + + + Switch + + Availability of analog input 2 + Switch + + + + + Switch + + Availability of analog input 3 + Switch + + + + + Switch + + Availability of analog input 4 + Switch + + + + + Switch + + Availability of RF input + Switch + + + + + Switch + + State of analog input 1 + Switch + + + + + Switch + + State of analog input 2 + Switch + + + + + Switch + + State of analog input 3 + Switch + + + + + Switch + + State of analog input 1 + Switch + + + + + Switch + + State of RF input + Switch + + + + + Switch + + Postive/Negative state of analog input 1 + Switch + + + + + Switch + + Postive/Negative state of analog input 2 + Switch + + + + + Switch + + Postive/Negative state of analog input 3 + Switch + + + + + Switch + + Postive/Negative state of analog input 1 + Switch + + + + + Switch + + Postive/Negative state of RF input + Switch + + + + + Number:ElectricPotential + + Voltage level of analog input 1 + Number + + + + + Number + + Minimum setting for analog input 1 + Number + + + + + Number + + Maximum setting for analog input 1 + Number + + + + + Number + + Target setting for analog input 1 + Number + + + + + Number:ElectricPotential + + Voltage level of analog input 2 + Number + + + + + Number + + Minimum setting for analog input 2 + Number + + + + + Number + + Maximum setting for analog input 2 + Number + + + + + Number + + Target setting for analog input 2 + Number + + + + + Number:ElectricPotential + + Voltage level of analog input 3 + Number + + + + + Number + + Minimum setting for analog input 3 + Number + + + + + Number + + Maximum setting for analog input 3 + Number + + + + + Number + + Target setting for analog input 3 + Number + + + + + Number:ElectricPotential + + Voltage level of analog input 4 + Number + + + + + Number + + Minimum setting for analog input 4 + Number + + + + + Number + + Maximum setting for analog input 4 + Number + + + + + Number + + Target setting for analog input 4 + Number + + + + + Number + + Minimum setting for RF input + Number + + + + + Number + + Maximum setting for RF input + Number + + + + + Number + + Target setting for RF input + Number + + + + + String + + Current error A + Text + + + + + String + + Current error E + Text + + + + + String + + Last error A + Text + + + + + String + + Last error E + Text + + + + + String + + Prelast error A + Text + + + + + String + + Prelast error E + Text + + + + + String + + Pre-Prelast error A + Text + + + + + String + + Pre-Prelast error E + Text + + + + + String + + Current error EA + Text + + + + + String + + Last error EA + Text + + + + + String + + Prelast error EA + Text + + + + + String + + Pre-Prelast error EA + Text + + + + + String + + Current error A (high) + Text + + + + + String + + Last error A (high) + Text + + + + + String + + Prelast error A (high) + Text + + + + + String + + Pre-Prelast error A (high) + Text + + + + + Number + + Main version of the device software + Number + + + + + Number + + Minor version of the device software + Number + + + + + Number + + Beta version of the device software + Number + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 607fca0a3904a..5874c3e0388c2 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -58,6 +58,7 @@ org.openhab.binding.buienradar org.openhab.binding.chromecast org.openhab.binding.cm11a + org.openhab.binding.comfoair org.openhab.binding.coolmasternet org.openhab.binding.daikin org.openhab.binding.darksky @@ -207,7 +208,7 @@ org.openhab.binding.systeminfo org.openhab.binding.tado org.openhab.binding.tankerkoenig - org.openhab.binding.telegram + org.openhab.binding.telegram org.openhab.binding.tellstick org.openhab.binding.tesla org.openhab.binding.tplinksmarthome