mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[airq] Air-Q binding Initial contribution (#10048)
Signed-off-by: Aurelio Caliaro <aurelio@caliaro.net>
This commit is contained in:
parent
541a71d1a1
commit
315964a55a
@ -9,6 +9,7 @@
|
||||
/bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers
|
||||
/bundles/org.openhab.automation.pidcontroller/ @fwolter
|
||||
/bundles/org.openhab.binding.adorne/ @theiding
|
||||
/bundles/org.openhab.binding.airq/ @aurelio1
|
||||
/bundles/org.openhab.binding.airquality/ @kubawolanin
|
||||
/bundles/org.openhab.binding.airvisualnode/ @3cky
|
||||
/bundles/org.openhab.binding.alarmdecoder/ @bobadair @billfor
|
||||
|
@ -36,6 +36,11 @@
|
||||
<artifactId>org.openhab.binding.adorne</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.airq</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.airquality</artifactId>
|
||||
|
13
bundles/org.openhab.binding.airq/NOTICE
Normal file
13
bundles/org.openhab.binding.airq/NOTICE
Normal file
@ -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/openhab-addons
|
223
bundles/org.openhab.binding.airq/README.md
Normal file
223
bundles/org.openhab.binding.airq/README.md
Normal file
@ -0,0 +1,223 @@
|
||||
# air-Q Binding
|
||||
|
||||
The air-Q Binding integrates the air analyzer [air-Q](http://www.air-q.com) device into the openHAB system.
|
||||
|
||||
With the binding, it is possible to subscribe to all data delivered by the air-Q device.
|
||||
|
||||
![air-Q image](doc/image_air-Q.png)
|
||||
|
||||
## Supported Things
|
||||
|
||||
Only one Thing is supported: The `airq` device.
|
||||
This Binding was tested with an `air-Q Pro` device with 14 sensors. It also works with an `air-Q` device with 11 sensors.
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto-discovery is not supported.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The air-Q Thing must be configured with (both mandatory):
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|------------------------------------|
|
||||
| ipAddress | Network address, e.g. 192.168.0.68 |
|
||||
| password | Password of the air-Q device |
|
||||
|
||||
The Thing provides the following properties:
|
||||
|
||||
| Parameter | Description |
|
||||
|------------------------|-------------------------------|
|
||||
| id | Device ID |
|
||||
| hardwareVersion | Hardware version |
|
||||
| softwareVersion | Firmware version |
|
||||
| sensorList | Available sensors |
|
||||
| sensorInfo | Information about the sensors |
|
||||
| industry | Industry version |
|
||||
|
||||
## Channels
|
||||
|
||||
The air-Q Thing offers access to all sensor data of the air-Q, according to its version.
|
||||
This includes also the Maximum Error per sensor value.
|
||||
For the Maximum Error channels just add `_maxerr` to the channel names.
|
||||
|
||||
The rw column is empty if the channel is only readable, w if the channel can be written and rw if it allows both to be read and written.
|
||||
|
||||
| channel | type | rw | description |
|
||||
|---------------------------|----------------------|--------------------------------------------------------------------------|
|
||||
| status | String | | Status of the sensors (usually "OK") |
|
||||
| avgFineDustSize | Number:Length | | Average size of Fine Dust [experimental] |
|
||||
| fineDustCnt00_3 | Number:Dimensionless | | Fine Dust >0,3 µm |
|
||||
| fineDustCnt00_5 | Number:Dimensionless | | Fine Dust >0,5 µm |
|
||||
| fineDustCnt01 | Number:Dimensionless | | Fine Dust >1 µm |
|
||||
| fineDustCnt02_5 | Number:Dimensionless | | Fine Dust >2,5 µm |
|
||||
| fineDustCnt05 | Number:Dimensionless | | Fine Dust >5 µm |
|
||||
| fineDustCnt10 | Number:Dimensionless | | Fine Dust >10 µm |
|
||||
| co | Number | | CO concentration |
|
||||
| co2 | Number:Dimensionless | | CO₂ concentration |
|
||||
| dCO2dt | Number | | Change of CO₂ concentration |
|
||||
| dHdt | Number | | Change of Humidity |
|
||||
| dewpt | Number:Temperature | | Dew Point |
|
||||
| doorEvent | Number | | Door Event (experimental, might not work reliably) |
|
||||
| health | Number:Dimensionless | | Health Index (0 to 1000, -200 for gas alarm, -800 for fire alarm) |
|
||||
| humidityRelative | Number:Dimensionless | | Humidity in percent |
|
||||
| humidityAbsolute | Number | | Absolute Humidity |
|
||||
| measureTime | Number:Time | | Milliseconds needed for measurement |
|
||||
| no2 | Number | | NO₂ concentration |
|
||||
| o3 | Number | | Ozone (O₃) concentration |
|
||||
| o2 | Number:Dimensionless | | Oxygen (O₂) concentration |
|
||||
| performance | Number:Dimensionless | | Performance Index (0 to 1000) |
|
||||
| fineDustConc01 | Number | | Fine Dust concentration >1 µm |
|
||||
| fineDustConc02_5 | Number | | Fine Dust concentration >2.5 µm |
|
||||
| fineDustConc10 | Number | | Fine Dust concentration >10 µm fni |
|
||||
| pressure | Number:Pressure | | Pressure |
|
||||
| so2 | Number | | SO₂ concentration |
|
||||
| sound | Number:Dimensionless | | Noise |
|
||||
| temperature | Number:Temperature | | Temperature |
|
||||
| timestamp | DateTime | | Timestamp of measurement |
|
||||
| tvoc | Number:Dimensionless | | VOC concentration |
|
||||
| uptime | Number:Time | | uptime in seconds |
|
||||
| wifi | Switch | | WLAN on or off |
|
||||
| ssid | String | | WLAN SSID |
|
||||
| password | String | w | Device Password |
|
||||
| wifiInfo | Switch | rw | Show WLAN status with LED |
|
||||
| timeServer | String | rw | Name of Timeserver address |
|
||||
| location | Location | rw | Location of air-Q device |
|
||||
| nightmodeStartDay | String | rw | Time to start day operation |
|
||||
| nightmodeStartNight | String | rw | End of day operation |
|
||||
| nightmodeBrightnessDay | Number:Dimensionless | rw | Brightness of LED during the day |
|
||||
| nightmodeBrightnessNight | Number:Dimensionless | rw | Brightness of LED at night |
|
||||
| nightmodeFanNightOff | Switch | rw | Switch off fan at night |
|
||||
| nightmodeWifiNightOff | Switch | rw | Switch off WLAN at night |
|
||||
| deviceName | String | | Device Name |
|
||||
| roomType | String | rw | Type of room |
|
||||
| logLevel | String | w | Logging level |
|
||||
| deleteKey | String | w | Settings to be deleted |
|
||||
| fireAlarm | Switch | rw | Send Fire Alarm if certain levels are met |
|
||||
| wlanConfigGateway | String | rw | Network Gateway |
|
||||
| wlanConfigMac | String | rw | MAC Address |
|
||||
| wlanConfigSsid | String | rw | WLAN SSID |
|
||||
| wlanConfigIPAddress | String | rw | Assigned IP address |
|
||||
| wlanConfigNetMask | String | rw | Network mask |
|
||||
| wlanConfigBssid | String | rw | Network BSSID |
|
||||
| cloudUpload | Switch | rw | Upload to air-Q cloud |
|
||||
| averagingRhythm | Number | rw | Rhythm of measurement for historic average |
|
||||
| powerFreqSuppression | String | rw | Power Frequency |
|
||||
| autoDriftCompensation | Switch | rw | Compensate automatic drift |
|
||||
| autoUpdate | Switch | rw | Install Firmware updates automatically |
|
||||
| advancedDataProcessing | Switch | rw | Use advanced algorithms eg. for open window or presence of a person |
|
||||
| ppm_and_ppb | Switch | rw | Output CO as ppm and NO₂, O₃ and SO₂ as ppb value instead of mg/m3 |
|
||||
| gasAlarm | Switch | rw | Send Gas Alarm if certain levels are met |
|
||||
| soundPressure | Switch | rw | Sound Pressure Level |
|
||||
| alarmForwarding | Switch | rw | Forward gas or fire alarm to other air-Q devices in the household |
|
||||
| userCalib | String | | Last sensor calibration |
|
||||
| initialCalFinished | Switch | | Initial calibration has finished |
|
||||
| averaging | Switch | rw | Do an average |
|
||||
| errorBars | Switch | rw | Calculate Maximum Errors |
|
||||
| warmupPhase | Switch | rw | Output data as Warmup Phase |
|
||||
|
||||
## Example
|
||||
|
||||
### air-Q.things
|
||||
|
||||
```
|
||||
Thing airq:airq:1 "air-Q" [ ipAddress="192.168.0.68", password="myAirQPassword" ]
|
||||
```
|
||||
|
||||
### air-Q.items
|
||||
|
||||
```
|
||||
String airQ_status "Status of Sensors" {channel="airq:airq:1:status"}
|
||||
Number:Length airQ_avgFineDustSize "Average Size of Fine Dust" {channel="airq:airq:1:avgFineDustSize"}
|
||||
Number:Dimensionless airQ_fineDustCnt00_3 "Fine Dust >0,3 µm" {channel="airq:airq:1:fineDustCnt00_3"}
|
||||
Number:Dimensionless airQ_fineDustCnt00_5 "Fine Dust >0,5 µm" {channel="airq:airq:1:fineDustCnt00_5"}
|
||||
Number:Dimensionless airQ_fineDustCnt01 "Fine Dust >1,0 µm" {channel="airq:airq:1:fineDustCnt01"}
|
||||
Number:Dimensionless airQ_fineDustCnt02_5 "Fine Dust >2,5 µm" {channel="airq:airq:1:fineDustCnt02_5"}
|
||||
Number:Dimensionless airQ_fineDustCnt05 "Fine Dust >5 µm" {channel="airq:airq:1:fineDustCnt05"}
|
||||
Number:Dimensionless airQ_fineDustCnt10 "Fine Dust >10 µm" {channel="airq:airq:1:fineDustCnt10"}
|
||||
Number airQ_co "CO Concentration" {channel="airq:airq:1:co"}
|
||||
Number:Dimensionless airQ_co2 "CO2 Concentration" {channel="airq:airq:1:co2"}
|
||||
Number airQ_dCO2dt "Change of CO2 Concentration" {channel="airq:airq:1:dCO2dt"}
|
||||
Number airQ_dHdt "Change of Humidity" {channel="airq:airq:1:dHdt"}
|
||||
Number:Temperature airQ_dewpt "Dew Point" {channel="airq:airq:1:dewpt"}
|
||||
Number airQ_doorEvent "Door Event (exp.)" {channel="airq:airq:1:doorEvent"}
|
||||
Number:Dimensionless airQ_health "Health Index" {channel="airq:airq:1:health"}
|
||||
Number:Dimensionless airQ_humidityRelative "Humidity" {channel="airq:airq:1:humidityRelative"}
|
||||
Number airQ_humidityAbsolute "Absolute Humidity" {channel="airq:airq:1:humidityAbsolute"}
|
||||
Number:Time airQ_measureTime "Time needed for measurement" {channel="airq:airq:1:measureTime"}
|
||||
Number airQ_no2 "NO2 concentration" {channel="airq:airq:1:no2"}
|
||||
Number airQ_o3 "O3 concentration" {channel="airq:airq:1:o3"}
|
||||
Number:Dimensionless airQ_o2 "Oxygen concentration" {channel="airq:airq:1:o2"}
|
||||
Number:Dimensionless airQ_performance "Performance Index" {channel="airq:airq:1:performance"}
|
||||
Number airQ_fineDustConc01 "Fine Dust Concentration >1µ" {channel="airq:airq:1:fineDustConc01"}
|
||||
Number airQ_fineDustConc02_5 "Fine Dust Concentration >2.5µ" {channel="airq:airq:1:fineDustConc02_5"}
|
||||
Number airQ_fineDustConc10 "Fine Dust Concentration >10µ" {channel="airq:airq:1:fineDustConc10"}
|
||||
Number:Pressure airQ_pressure "Pressure" {channel="airq:airq:1:pressure"}
|
||||
Number airQ_so2 "SO2 concentration" {channel="airq:airq:1:so2"}
|
||||
Number:Dimensionless airQ_sound "Noise" {channel="airq:airq:1:sound"}
|
||||
Number:Temperature airQ_temperature "Temperature" {channel="airq:airq:1:temperature"}
|
||||
DateTime airQ_timestamp "TimeStamp [%1$td.%1$tm.%1$tY %1$tH:%1$tM]" {channel="airq:airq:1:timestamp"}
|
||||
Number:Dimensionless airQ_voc "VOC concentration" {channel="airq:airq:1:tvoc"}
|
||||
Number:Time airQ_uptime "Uptime" {channel="airq:airq:1:uptime"}
|
||||
|
||||
Number:Dimensionless airQ_cnt03_maxerr "Maximum error of Fine Dust >0,3 µm" {channel="airq:airq:1:cnt0_3_maxerr"}
|
||||
Number:Dimensionless airQ_cnt05_maxerr "Maximum error of Fine Dust >0,5 µm" {channel="airq:airq:1:cnt0_5_maxerr"}
|
||||
Number:Dimensionless airQ_cnt1_maxerr "Maximum error of Fine Dust >1,0 µm" {channel="airq:airq:1:cnt1_maxerr"}
|
||||
Number:Dimensionless airQ_cnt25_maxerr "Maximum error of Fine Dust >2,5 µm" {channel="airq:airq:1:cnt2_5_maxerr"}
|
||||
Number:Dimensionless airQ_cnt5_maxerr "Maximum error of Fine Dust >5 µm" {channel="airq:airq:1:cnt5_maxerr"}
|
||||
Number:Dimensionless airQ_cnt10_maxerr "Maximum error of Fine Dust >10 µm" {channel="airq:airq:1:cnt10_maxerr"}
|
||||
Number:Dimensionless airQ_co2_maxerr "Maximum error of CO2 Concentration" {channel="airq:airq:1:co2_maxerr"}
|
||||
Number:Dimensionless airQ_dewpt_maxerr "Maximum error of Dew Point" {channel="airq:airq:1:dewpt_maxerr"}
|
||||
Number:Dimensionless airQ_humidity_maxerr "Maximum error of Humidity" {channel="airq:airq:1:humidity_maxerr"}
|
||||
Number:Dimensionless airQ_humidity_abs_maxerr "Maximum error of Absolute Humidity" {channel="airq:airq:1:humidity_abs_maxerr"}
|
||||
Number:Dimensionless airQ_no2_maxerr "Maximum error of NO2 concentration" {channel="airq:airq:1:no2_maxerr"}
|
||||
Number:Dimensionless airQ_o3_maxerr "Maximum error of O3 concentration" {channel="airq:airq:1:o3_maxerr"}
|
||||
Number:Dimensionless airQ_oxygen_maxerr "Maximum error of Oxygen concentration" {channel="airq:airq:1:o2_maxerr"}
|
||||
Number:Dimensionless airQ_pm1_maxerr "Maximum error of Fine Dust Concentration >1µ" {channel="airq:airq:1:pm1_maxerr"}
|
||||
Number:Dimensionless airQ_pm2_5_maxerr "Maximum error of Fine Dust Concentration >2.5µ" {channel="airq:airq:1:pm2_5_maxerr"}
|
||||
Number:Dimensionless airQ_pm10_maxerr "Maximum error of Fine Dust Concentration >10µ" {channel="airq:airq:1:pm10_maxerr"}
|
||||
Number:Dimensionless airQ_pressure_maxerr "Maximum error of Pressure" {channel="airq:airq:1:pressure_maxerr"}
|
||||
Number:Dimensionless airQ_so2_maxerr "Maximum error of SO2 concentration" {channel="airq:airq:1:so2_maxerr"}
|
||||
Number:Dimensionless airQ_sound_maxerr "Maximum error of Noise" {channel="airq:airq:1:sound_maxerr"}
|
||||
Number:Dimensionless airQ_temperature_maxerr "Maximum error of Temperature" {channel="airq:airq:1:temperature_maxerr"}
|
||||
Number:Dimensionless airQ_voc_maxerr "Maximum error of VOC concentration" {channel="airq:airq:1:tvoc_maxerr"}
|
||||
|
||||
Switch airQ_wifi "WLAN on or off" {channel="airq:airq:1:wifi"}
|
||||
String airQ_SSID "WLAN SSID" {channel="airq:airq:1:ssid"}
|
||||
String airQ_password "Device Password" {channel="airq:airq:1:password"}
|
||||
Switch airQ_wifiInfo "Show WLAN status with LED" {channel="airq:airq:1:wifiInfo"}
|
||||
String airQ_timeServer "Name of Timeserver address" {channel="airq:airq:1:timeServer"}
|
||||
Location airQ_location "Location of air-Q device" {channel="airq:airq:1:location"}
|
||||
String airQ_nightMode_startDay "Time to start day operation" {channel="airq:airq:1:nightModeStartDay"}
|
||||
String airQ_nightMode_startNight "End of day operation" {channel="airq:airq:1:nightModeStartNight"}
|
||||
Number:Dimensionless airQ_nightMode_brightnessDay "Brightness of LED during the day" {channel="airq:airq:1:nightModeBrightnessDay"}
|
||||
Number:Dimensionless airQ_nightMode_brightnessNight "Brightness of LED at night" {channel="airq:airq:1:nightModeBrightnessNight"}
|
||||
Switch airQ_nightMode_fanNightOff "Switch off fan at night" {channel="airq:airq:1:nightModeFanNightOff"}
|
||||
Switch airQ_nightMode_wifiNightOff "Switch off WLAN at night" {channel="airq:airq:1:nightModeWifiNightOff"}
|
||||
String airQ_deviceName "Device Name" {channel="airq:airq:1:deviceName"}
|
||||
String airQ_roomType "Type of room" {channel="airq:airq:1:roomType"}
|
||||
String airQ_logLevel "Logging level" {channel="airq:airq:1:logLevel"}
|
||||
String airQ_deleteKey "Settings to be deleted" {channel="airq:airq:1:deleteKey"}
|
||||
Switch airQ_fireAlarm "Send Fire Alarm if certain levels are met" {channel="airq:airq:1:fireAlarm"}
|
||||
String airQ_WLAN_config_gateway "Network Gateway" {channel="airq:airq:1:wlanConfigGateway"}
|
||||
String airQ_WLAN_config_MAC "MAC Address" {channel="airq:airq:1:wlanConfigMac"}
|
||||
String airQ_WLAN_config_SSID "WLAN SSID" {channel="airq:airq:1:wlanConfigSsid"}
|
||||
String airQ_WLAN_config_IPAddress "Assigned IP address" {channel="airq:airq:1:wlanConfigIPAddress"}
|
||||
String airQ_WLAN_config_netMask "Network mask" {channel="airq:airq:1:wlanConfigNetMask"}
|
||||
String airQ_WLAN_config_BSSID "Network BSSID" {channel="airq:airq:1:wlanConfigBssid"}
|
||||
Switch airQ_cloudUpload "Upload to air-Q cloud" {channel="airq:airq:1:cloudUpload"}
|
||||
Number airQ_averagingRhythm "Rhythm of measurement for historic average" {channel="airq:airq:1:averagingRhythm"}
|
||||
String airQ_powerFreqSuppression "Power Frequency" {channel="airq:airq:1:powerFreqSuppression"}
|
||||
Switch airQ_autoDriftCompensation "Compensate automatic drift" {channel="airq:airq:1:autoDriftCompensation"}
|
||||
Switch airQ_autoUpdate "Install Firmware updates automatically" {channel="airq:airq:1:autoUpdate"}
|
||||
Switch airQ_advancedDataProcessing "Use advanced algorithms eg. for open window or presence of a person" {channel="airq:airq:1:advancedDataProcessing"}
|
||||
Switch airQ_ppm_and_ppb "Output CO as ppm and NO2, O3 and SO2 as ppb value instead of mg/m3" {channel="airq:airq:1:ppm_and_ppb"}
|
||||
Switch airQ_gasAlarm "Send Gas Alarm if certain levels are met" {channel="airq:airq:1:gasAlarm"}
|
||||
Switch airQ_soundPressure "Sound Pressure Level" {channel="airq:airq:1:soundPressure"}
|
||||
Switch airQ_alarmForwarding "Forward gas or fire alarm to other air-Q devices in the household" {channel="airq:airq:1:alarmForwarding"}
|
||||
String airQ_userCalib "Last sensor calibration" {channel="airq:airq:1:userCalib"}
|
||||
Switch airQ_initialCalFinished "Initial calibration has finished" {channel="airq:airq:1:initialCalFinished"}
|
||||
Switch airQ_averaging "Do an average" {channel="airq:airq:1:averaging"}
|
||||
Switch airQ_errorBars "Calculate Maximum Errors" {channel="airq:airq:1:errorBars"}
|
||||
Switch airQ_warmupPhase "Output Data as Warmup Phase" {channel="airq:airq:1:warmupPhase"}
|
||||
```
|
BIN
bundles/org.openhab.binding.airq/doc/image_air-Q.png
Normal file
BIN
bundles/org.openhab.binding.airq/doc/image_air-Q.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
17
bundles/org.openhab.binding.airq/pom.xml
Normal file
17
bundles/org.openhab.binding.airq/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.airq</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: air-Q Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.airq-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-airq" description="air-Q Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.airq/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.airq.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link AirqBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Aurelio Caliaro - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirqBindingConstants {
|
||||
private static final String BINDING_ID = "airq";
|
||||
public static final ThingTypeUID THING_TYPE_AIRQ = new ThingTypeUID(BINDING_ID, "airq");
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.airq.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AirqConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Aurelio Caliaro - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class AirqConfiguration {
|
||||
public String ipAddress = "";
|
||||
public String password = "";
|
||||
}
|
@ -0,0 +1,789 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.airq.internal;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link $AirqHandler} is responsible for retrieving all information from the air-Q device
|
||||
* and change properties and channels accordingly.
|
||||
*
|
||||
* @author Aurelio Caliaro - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AirqHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AirqHandler.class);
|
||||
private final Gson gson = new Gson();
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private @Nullable ScheduledFuture<?> getConfigDataJob;
|
||||
protected static final int POLLING_PERIOD_DATA_MSEC = 15000; // in milliseconds
|
||||
protected static final int POLLING_PERIOD_CONFIG = 1; // in minutes
|
||||
protected final HttpClient httpClient;
|
||||
AirqConfiguration config = new AirqConfiguration();
|
||||
|
||||
final class ResultPair {
|
||||
private final float value;
|
||||
private final float maxdev;
|
||||
|
||||
public float getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public float getMaxdev() {
|
||||
return maxdev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects a string consisting of two values as sent by the air-Q device
|
||||
* and returns a corresponding object
|
||||
*
|
||||
* @param input string formed as this: [1234,56,789,012] (including the brackets)
|
||||
* @return ResultPair object with the two values
|
||||
*/
|
||||
public ResultPair(String input) {
|
||||
value = Float.parseFloat(input.substring(1, input.indexOf(',')));
|
||||
maxdev = Float.parseFloat(input.substring(input.indexOf(',') + 1, input.length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
public AirqHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
logger.warn("air-Q - airqHandler - constructor: httpClient={}", httpClient);
|
||||
}
|
||||
|
||||
private boolean isTimeFormat(String str) {
|
||||
try {
|
||||
LocalTime.parse(str);
|
||||
} catch (DateTimeParseException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if ((command instanceof OnOffType) || (command instanceof StringType)) {
|
||||
JsonObject newobj = new JsonObject();
|
||||
JsonObject subjson = new JsonObject();
|
||||
switch (channelUID.getId()) {
|
||||
case "wifi":
|
||||
// we do not allow to switch off Wifi because otherwise we can't connect to the air-Q device anymore
|
||||
break;
|
||||
case "wifiInfo":
|
||||
newobj.addProperty("WifiInfo", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "fireAlarm":
|
||||
newobj.addProperty("FireAlarm", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "cloudUpload":
|
||||
newobj.addProperty("cloudUpload", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "autoDriftCompensation":
|
||||
newobj.addProperty("AutoDriftCompensation", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "autoUpdate":
|
||||
// note that this property is binary but uses 1 and 0 instead of true and false
|
||||
newobj.addProperty("AutoUpdate", command == OnOffType.ON ? 1 : 0);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "advancedDataProcessing":
|
||||
newobj.addProperty("AdvancedDataProcessing", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "gasAlarm":
|
||||
newobj.addProperty("GasAlarm", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "soundPressure":
|
||||
newobj.addProperty("SoundInfo", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "alarmForwarding":
|
||||
newobj.addProperty("AlarmForwarding", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "averaging":
|
||||
newobj.addProperty("averaging", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "errorBars":
|
||||
newobj.addProperty("ErrorBars", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "ppm_and_ppb":
|
||||
newobj.addProperty("ppm&ppb", command == OnOffType.ON);
|
||||
changeSettings(newobj);
|
||||
case "nightmodeFanNightOff":
|
||||
subjson.addProperty("FanNightOff", command == OnOffType.ON);
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "nightmodeWifiNightOff":
|
||||
subjson.addProperty("WifiNightOff", command == OnOffType.ON);
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "SSID":
|
||||
JsonElement wifidatael = gson.fromJson(command.toString(), JsonElement.class);
|
||||
if (wifidatael != null) {
|
||||
JsonObject wifidataobj = wifidatael.getAsJsonObject();
|
||||
newobj.addProperty("WiFissid", wifidataobj.get("WiFissid").getAsString());
|
||||
newobj.addProperty("WiFipass", wifidataobj.get("WiFipass").getAsString());
|
||||
String bssid = wifidataobj.get("WiFibssid").getAsString();
|
||||
if (!bssid.isEmpty()) {
|
||||
newobj.addProperty("WiFibssid", bssid);
|
||||
}
|
||||
newobj.addProperty("reset", wifidataobj.get("reset").getAsString());
|
||||
changeSettings(newobj);
|
||||
} else {
|
||||
logger.warn("Cannot extract wlan data from this string: {}", wifidatael);
|
||||
}
|
||||
break;
|
||||
case "timeServer":
|
||||
newobj.addProperty(channelUID.getId(), command.toString());
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "nightmodeStartDay":
|
||||
if (isTimeFormat(command.toString())) {
|
||||
subjson.addProperty("StartDay", command.toString());
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} should be set to {} but it isn't a correct time format (eg. 08:00)",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "nightmodeStartNight":
|
||||
if (isTimeFormat(command.toString())) {
|
||||
subjson.addProperty("StartNight", command.toString());
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} should be set to {} but it isn't a correct time format (eg. 08:00)",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "location":
|
||||
PointType pt = (PointType) command;
|
||||
subjson.addProperty("lat", pt.getLatitude());
|
||||
subjson.addProperty("long", pt.getLongitude());
|
||||
newobj.add("geopos", subjson);
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "nightmodeBrightnessDay":
|
||||
try {
|
||||
subjson.addProperty("BrightnessDay", Float.parseFloat(command.toString()));
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
} catch (NumberFormatException exc) {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} only accepts a float value, and {} is not.",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "nightmodeBrightnessNight":
|
||||
try {
|
||||
subjson.addProperty("BrightnessNight", Float.parseFloat(command.toString()));
|
||||
newobj.add("NightMode", subjson);
|
||||
changeSettings(newobj);
|
||||
} catch (NumberFormatException exc) {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} only accepts a float value, and {} is not.",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "roomType":
|
||||
newobj.addProperty("RoomType", command.toString());
|
||||
changeSettings(newobj);
|
||||
break;
|
||||
case "logLevel":
|
||||
String ll = command.toString();
|
||||
if (ll.equals("Error") || ll.equals("Warning") || ll.equals("Info")) {
|
||||
newobj.addProperty("logging", ll);
|
||||
changeSettings(newobj);
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} should be set to {} but it isn't a correct setting for the power frequency suppression (only 50Hz or 60Hz)",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "averagingRhythm":
|
||||
try {
|
||||
newobj.addProperty("SecondsMeasurementDelay", Integer.parseUnsignedInt(command.toString()));
|
||||
} catch (NumberFormatException exc) {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} only accepts an integer value, and {} is not.",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
case "powerFreqSuppression":
|
||||
String newFreq = command.toString();
|
||||
if (newFreq.equals("50Hz") || newFreq.equals("60Hz") || newFreq.equals("50Hz+60Hz")) {
|
||||
newobj.addProperty("Rejection", newFreq);
|
||||
changeSettings(newobj);
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): {} should be set to {} but it isn't a correct setting for the power frequency suppression (only 50Hz or 60Hz)",
|
||||
channelUID.getId(), command.toString());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - handleCommand(): unknown command {} received (channelUID={}, value={})",
|
||||
command, channelUID, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getThing().getConfiguration().as(AirqConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
// We don't have to test if ipAddress and password have been set because we have defined them
|
||||
// as being 'required' in thing-types.xml and OpenHAB will only initialize the handler if both are set.
|
||||
String data = getDecryptedContentString("http://" + config.ipAddress + "/data", "GET", null);
|
||||
// we try if the device is reachable and the password is correct. Otherwise a corresponding message is
|
||||
// thrown in Thing manager.
|
||||
if (data == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Unable to retrieve get data from air-Q device. Probable cause: invalid password.");
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::pollData, 0, POLLING_PERIOD_DATA_MSEC,
|
||||
TimeUnit.MILLISECONDS);
|
||||
getConfigDataJob = scheduler.scheduleWithFixedDelay(this::getConfigData, 0, POLLING_PERIOD_CONFIG,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
// AES decoding based on this tutorial: https://www.javainterviewpoint.com/aes-256-encryption-and-decryption/
|
||||
public @Nullable String decrypt(byte[] base64text, String password) {
|
||||
String content = "";
|
||||
logger.trace("air-Q - airqHandler - decrypt(): content to decrypt: {}", base64text);
|
||||
byte[] encodedtextwithIV = Base64.getDecoder().decode(base64text);
|
||||
byte[] ciphertext = Arrays.copyOfRange(encodedtextwithIV, 16, encodedtextwithIV.length);
|
||||
byte[] passkey = Arrays.copyOf(password.getBytes(), 32);
|
||||
if (password.length() < 32) {
|
||||
Arrays.fill(passkey, password.length(), 32, (byte) '0');
|
||||
}
|
||||
SecretKey seckey = new SecretKeySpec(passkey, 0, passkey.length, "AES");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(seckey.getEncoded(), "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(Arrays.copyOf(encodedtextwithIV, 16));
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
byte[] decryptedText = cipher.doFinal(ciphertext);
|
||||
content = new String(decryptedText, StandardCharsets.UTF_8);
|
||||
logger.trace("air-Q - airqHandler - decrypt(): Text decoded as String: {}", content);
|
||||
} catch (BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException exc) {
|
||||
logger.warn("Error while decrypting. Probably the provided password is wrong.");
|
||||
return null;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
public String encrypt(byte[] toencode, String password) {
|
||||
String content = "";
|
||||
logger.trace("air-Q - airqHandler - encrypt(): text to encode: {}", new String(toencode));
|
||||
byte[] passkey = Arrays.copyOf(password.getBytes(StandardCharsets.UTF_8), 32);
|
||||
if (password.length() < 32) {
|
||||
Arrays.fill(passkey, password.length(), 32, (byte) '0');
|
||||
}
|
||||
byte[] iv = new byte[16];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(iv);
|
||||
SecretKey seckey = new SecretKeySpec(passkey, 0, passkey.length, "AES");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(seckey.getEncoded(), "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||
byte[] encryptedText = cipher.doFinal(toencode);
|
||||
byte[] totaltext = new byte[16 + encryptedText.length];
|
||||
System.arraycopy(iv, 0, totaltext, 0, 16);
|
||||
System.arraycopy(encryptedText, 0, totaltext, 16, encryptedText.length);
|
||||
byte[] encodedcontent = Base64.getEncoder().encode(totaltext);
|
||||
logger.trace("air-Q - airqHandler - encrypt(): encrypted text: {}", encodedcontent);
|
||||
content = new String(encodedcontent);
|
||||
} catch (Exception e) {
|
||||
logger.warn("air-Q - airqHandler - encrypt(): Error while encrypting: {}", e.toString());
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
// gets the data after online/offline management and does the JSON work, or at least the first step.
|
||||
protected @Nullable String getDecryptedContentString(String url, String requestMethod, @Nullable String body) {
|
||||
Result res = null;
|
||||
String jsonAnswer = null;
|
||||
res = getData(url, "GET", null);
|
||||
if (res != null) {
|
||||
String jsontext = res.getBody();
|
||||
logger.trace("air-Q - airqHandler - getDecryptedContentString(): Result from getData() is {} with body={}",
|
||||
res, res.getBody());
|
||||
// Gson code based on https://riptutorial.com/de/gson
|
||||
JsonElement ans = gson.fromJson(jsontext, JsonElement.class);
|
||||
if (ans != null) {
|
||||
JsonObject jsonObj = ans.getAsJsonObject();
|
||||
jsonAnswer = decrypt(jsonObj.get("content").getAsString().getBytes(), config.password);
|
||||
if (jsonAnswer == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Decryption not possible, probably wrong password");
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - getDecryptedContentString(): The air-Q data could not be extracted from this string: {}",
|
||||
ans);
|
||||
}
|
||||
}
|
||||
return jsonAnswer;
|
||||
}
|
||||
|
||||
// calls the networking job and in addition does additional tests for online/offline management
|
||||
protected @Nullable Result getData(String address, String requestMethod, @Nullable String body) {
|
||||
Result res = null;
|
||||
int timeout = 10;
|
||||
logger.debug("air-Q - airqHandler - getData(): connecting to {} with method {} and body {}", address,
|
||||
requestMethod, body);
|
||||
Request request = httpClient.newRequest(address).timeout(timeout, TimeUnit.SECONDS).method(requestMethod);
|
||||
if (body != null) {
|
||||
request = request.content(new StringContentProvider(body)).header(HttpHeader.CONTENT_TYPE,
|
||||
"application/json");
|
||||
}
|
||||
try {
|
||||
ContentResponse response = request.send();
|
||||
res = new Result(response.getContentAsString(), response.getStatus());
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException exc) {
|
||||
logger.warn("air-Q - airqHandler - doNetwork(): Error while accessing air-Q: {}", exc.toString());
|
||||
}
|
||||
if (res == null) {
|
||||
if (getThing().getStatus() != ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "air-Q device not reachable");
|
||||
} else {
|
||||
logger.warn("air-Q - airqHandler - getData(): retried but still cannot reach the air-Q device.");
|
||||
}
|
||||
} else {
|
||||
if (getThing().getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
private final String body;
|
||||
private final int responseCode;
|
||||
|
||||
public Result(String body, int responseCode) {
|
||||
this.body = body;
|
||||
this.responseCode = responseCode;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
}
|
||||
if (getConfigDataJob != null) {
|
||||
getConfigDataJob.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void pollData() {
|
||||
logger.trace("air-Q - airqHandler - run(): starting polled data handler");
|
||||
try {
|
||||
String url = "http://" + config.ipAddress + "/data";
|
||||
String jsonAnswer = getDecryptedContentString(url, "GET", null);
|
||||
if (jsonAnswer != null) {
|
||||
JsonElement decEl = gson.fromJson(jsonAnswer, JsonElement.class);
|
||||
if (decEl != null) {
|
||||
JsonObject decObj = decEl.getAsJsonObject();
|
||||
logger.debug("air-Q - airqHandler - run(): decObj={}, jsonAnswer={}", decObj, jsonAnswer);
|
||||
// 'bat' is a field that is already delivered by air-Q but as
|
||||
// there are no air-Q devices which are powered with batteries
|
||||
// it is obsolete at this moment. We implemented the code anyway
|
||||
// to make it easier to add afterwords, but for the moment it is not applicable.
|
||||
// processType(decObj, "bat", "battery", "pair");
|
||||
processType(decObj, "cnt0_3", "fineDustCnt00_3", "pair");
|
||||
processType(decObj, "cnt0_5", "fineDustCnt00_5", "pair");
|
||||
processType(decObj, "cnt1", "fineDustCnt01", "pair");
|
||||
processType(decObj, "cnt2_5", "fineDustCnt02_5", "pair");
|
||||
processType(decObj, "cnt5", "fineDustCnt05", "pair");
|
||||
processType(decObj, "cnt10", "fineDustCnt10", "pair");
|
||||
processType(decObj, "co", "co", "pair");
|
||||
processType(decObj, "co2", "co2", "pairPPM");
|
||||
processType(decObj, "dewpt", "dewpt", "pair");
|
||||
processType(decObj, "humidity", "humidityRelative", "pair");
|
||||
processType(decObj, "humidity_abs", "humidityAbsolute", "pair");
|
||||
processType(decObj, "no2", "no2", "pair");
|
||||
processType(decObj, "o3", "o3", "pair");
|
||||
processType(decObj, "oxygen", "o2", "pair");
|
||||
processType(decObj, "pm1", "fineDustConc01", "pair");
|
||||
processType(decObj, "pm2_5", "fineDustConc02_5", "pair");
|
||||
processType(decObj, "pm10", "fineDustConc10", "pair");
|
||||
processType(decObj, "pressure", "pressure", "pair");
|
||||
processType(decObj, "so2", "so2", "pair");
|
||||
processType(decObj, "sound", "sound", "pairDB");
|
||||
processType(decObj, "temperature", "temperature", "pair");
|
||||
// We have two places where the Device ID is delivered: with the measurement data and
|
||||
// with the configuration.
|
||||
// We take the info from the configuration and show it as a property, so we don't need
|
||||
// something like processType(decObj, "DeviceID", "DeviceID", "string") at this moment. We leave
|
||||
// this as a reminder in case for some reason it will be needed in future, e.g. when an air-Q
|
||||
// device also sends data from other devices (then with another Device ID)
|
||||
processType(decObj, "Status", "status", "string");
|
||||
processType(decObj, "TypPS", "avgFineDustSize", "number");
|
||||
processType(decObj, "dCO2dt", "dCO2dt", "number");
|
||||
processType(decObj, "dHdt", "dHdt", "number");
|
||||
processType(decObj, "door_event", "doorEvent", "number");
|
||||
processType(decObj, "health", "health", "number");
|
||||
processType(decObj, "measuretime", "measureTime", "number");
|
||||
processType(decObj, "performance", "performance", "number");
|
||||
processType(decObj, "timestamp", "timestamp", "datetime");
|
||||
processType(decObj, "uptime", "uptime", "numberTimePeriod");
|
||||
processType(decObj, "tvoc", "tvoc", "pairPPB");
|
||||
} else {
|
||||
logger.warn("The air-Q data could not be extracted from this string: {}", decEl);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("air-Q - airqHandler - polldata.run(): Error while retrieving air-Q data: {}", toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void getConfigData() {
|
||||
Result res = null;
|
||||
logger.trace("air-Q - airqHandler - getConfigData(): starting processing data");
|
||||
try {
|
||||
String url = "http://" + config.ipAddress + "/config";
|
||||
res = getData(url, "GET", null);
|
||||
if (res != null) {
|
||||
String jsontext = res.getBody();
|
||||
logger.trace("air-Q - airqHandler - getConfigData(): Result from getBody() is {} with body={}", res,
|
||||
res.getBody());
|
||||
JsonElement ans = gson.fromJson(jsontext, JsonElement.class);
|
||||
if (ans != null) {
|
||||
JsonObject jsonObj = ans.getAsJsonObject();
|
||||
String jsonAnswer = decrypt(jsonObj.get("content").getAsString().getBytes(), config.password);
|
||||
if (jsonAnswer != null) {
|
||||
JsonElement decEl = gson.fromJson(jsonAnswer, JsonElement.class);
|
||||
if (decEl != null) {
|
||||
JsonObject decObj = decEl.getAsJsonObject();
|
||||
logger.debug("air-Q - airqHandler - getConfigData(): decObj={}", decObj);
|
||||
processType(decObj, "Wifi", "wifi", "boolean");
|
||||
processType(decObj, "WLANssid", "ssid", "arr");
|
||||
processType(decObj, "pass", "password", "string");
|
||||
processType(decObj, "WifiInfo", "wifiInfo", "boolean");
|
||||
processType(decObj, "TimeServer", "timeServer", "string");
|
||||
processType(decObj, "geopos", "location", "coord");
|
||||
processType(decObj, "NightMode", "", "nightmode");
|
||||
processType(decObj, "devicename", "deviceName", "string");
|
||||
processType(decObj, "RoomType", "roomType", "string");
|
||||
processType(decObj, "logging", "logLevel", "string");
|
||||
processType(decObj, "DeleteKey", "deleteKey", "string");
|
||||
processType(decObj, "FireAlarm", "fireAlarm", "boolean");
|
||||
processType(decObj, "air-Q-Hardware-Version", "hardwareVersion", "property");
|
||||
processType(decObj, "WLAN config", "", "wlan");
|
||||
processType(decObj, "cloudUpload", "cloudUpload", "boolean");
|
||||
processType(decObj, "SecondsMeasurementDelay", "averagingRhythm", "number");
|
||||
processType(decObj, "Rejection", "powerFreqSuppression", "string");
|
||||
processType(decObj, "air-Q-Software-Version", "softwareVersion", "property");
|
||||
processType(decObj, "sensors", "sensorList", "proparr");
|
||||
processType(decObj, "AutoDriftCompensation", "autoDriftCompensation", "boolean");
|
||||
processType(decObj, "AutoUpdate", "autoUpdate", "boolean");
|
||||
processType(decObj, "AdvancedDataProcessing", "advancedDataProcessing", "boolean");
|
||||
processType(decObj, "Industry", "Industry", "property");
|
||||
processType(decObj, "ppm&ppb", "ppm_and_ppb", "boolean");
|
||||
processType(decObj, "GasAlarm", "gasAlarm", "boolean");
|
||||
processType(decObj, "id", "id", "property");
|
||||
processType(decObj, "SoundInfo", "soundPressure", "boolean");
|
||||
processType(decObj, "AlarmForwarding", "alarmForwarding", "boolean");
|
||||
processType(decObj, "usercalib", "userCalib", "calib");
|
||||
processType(decObj, "InitialCalFinished", "initialCalFinished", "boolean");
|
||||
processType(decObj, "Averaging", "averaging", "boolean");
|
||||
processType(decObj, "SensorInfo", "sensorInfo", "property");
|
||||
processType(decObj, "ErrorBars", "errorBars", "boolean");
|
||||
processType(decObj, "warmup-phase", "warmupPhase", "boolean");
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - getConfigData(): The air-Q data could not be extracted from this string: {}",
|
||||
decEl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - getConfigData(): The air-Q data could not be extracted from this string: {}",
|
||||
ans);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("air-Q - airqHandler - getConfigData(): Error in processConfigData(): {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void processType(JsonObject dec, String airqName, String channelName, String type) {
|
||||
logger.trace("air-Q - airqHandler - processType(): airqName={}, channelName={}, type={}", airqName, channelName,
|
||||
type);
|
||||
if (dec.get(airqName) == null) {
|
||||
logger.trace("air-Q - airqHandler - processType(): get({}) is null", airqName);
|
||||
updateState(channelName, UnDefType.UNDEF);
|
||||
if (type.contentEquals("pair")) {
|
||||
updateState(channelName + "_maxerr", UnDefType.UNDEF);
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
String itemval = dec.get(airqName).toString();
|
||||
if (itemval.contentEquals("true") || itemval.contentEquals("1")) {
|
||||
updateState(channelName, OnOffType.ON);
|
||||
} else if (itemval.contentEquals("false") || itemval.contentEquals("0")) {
|
||||
updateState(channelName, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
case "time":
|
||||
String strstr = dec.get(airqName).toString();
|
||||
updateState(channelName, new StringType(strstr.substring(1, strstr.length() - 1)));
|
||||
break;
|
||||
case "number":
|
||||
updateState(channelName, new DecimalType(dec.get(airqName).toString()));
|
||||
break;
|
||||
case "numberTimePeriod":
|
||||
updateState(channelName, new QuantityType<>(dec.get(airqName).getAsBigInteger(), Units.SECOND));
|
||||
break;
|
||||
case "pair":
|
||||
ResultPair pair = new ResultPair(dec.get(airqName).toString());
|
||||
updateState(channelName, new DecimalType(pair.getValue()));
|
||||
updateState(channelName + "_maxerr", new DecimalType(pair.getMaxdev()));
|
||||
break;
|
||||
case "pairPPM":
|
||||
ResultPair pairPPM = new ResultPair(dec.get(airqName).toString());
|
||||
updateState(channelName, new QuantityType<>(pairPPM.getValue(), Units.PARTS_PER_MILLION));
|
||||
updateState(channelName + "_maxerr", new DecimalType(pairPPM.getMaxdev()));
|
||||
break;
|
||||
case "pairPPB":
|
||||
ResultPair pairPPB = new ResultPair(dec.get(airqName).toString());
|
||||
updateState(channelName, new QuantityType<>(pairPPB.getValue(), Units.PARTS_PER_BILLION));
|
||||
updateState(channelName + "_maxerr", new DecimalType(pairPPB.getMaxdev()));
|
||||
break;
|
||||
case "pairDB":
|
||||
ResultPair pairDB = new ResultPair(dec.get(airqName).toString());
|
||||
logger.trace("air-Q - airqHandler - processType(): db transmitted as {} with unit {}",
|
||||
pairDB.getValue(), Units.DECIBEL);
|
||||
updateState(channelName, new QuantityType<>(pairDB.getValue(), Units.DECIBEL));
|
||||
updateState(channelName + "_maxerr", new DecimalType(pairDB.getMaxdev()));
|
||||
break;
|
||||
case "datetime":
|
||||
Long timest = Long.valueOf(dec.get(airqName).toString());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
||||
String timestampString = sdf.format(new Date(timest));
|
||||
updateState(channelName, DateTimeType.valueOf(timestampString));
|
||||
break;
|
||||
case "coord":
|
||||
JsonElement ansCoord = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if (ansCoord != null) {
|
||||
JsonObject jsonCoord = ansCoord.getAsJsonObject();
|
||||
Float latitude = jsonCoord.get("lat").getAsFloat();
|
||||
Float longitude = jsonCoord.get("long").getAsFloat();
|
||||
updateState(channelName, new PointType(new DecimalType(latitude), new DecimalType(longitude)));
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - processType(): Cannot extract coordinates from this data: {}",
|
||||
dec.get(airqName).toString());
|
||||
}
|
||||
break;
|
||||
case "nightmode":
|
||||
JsonElement daynightdata = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if (daynightdata != null) {
|
||||
JsonObject jsonDaynightdata = daynightdata.getAsJsonObject();
|
||||
processType(jsonDaynightdata, "StartDay", "nightModeStartDay", "string");
|
||||
processType(jsonDaynightdata, "StartNight", "nightModeStartNight", "string");
|
||||
processType(jsonDaynightdata, "BrightnessDay", "nightModeBrightnessDay", "number");
|
||||
processType(jsonDaynightdata, "BrightnessNight", "nightModeBrightnessNight", "number");
|
||||
processType(jsonDaynightdata, "FanNightOff", "nightModeFanNightOff", "boolean");
|
||||
processType(jsonDaynightdata, "WifiNightOff", "nightModeWifiNightOff", "boolean");
|
||||
} else {
|
||||
logger.warn("air-Q - airqHandler - processType(): Cannot extract day/night data: {}",
|
||||
dec.get(airqName).toString());
|
||||
}
|
||||
break;
|
||||
case "wlan":
|
||||
JsonElement wlandata = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if (wlandata != null) {
|
||||
JsonObject jsonWlandata = wlandata.getAsJsonObject();
|
||||
processType(jsonWlandata, "Gateway", "wlanConfigGateway", "string");
|
||||
processType(jsonWlandata, "MAC", "wlanConfigMac", "string");
|
||||
processType(jsonWlandata, "SSID", "wlanConfigSsid", "string");
|
||||
processType(jsonWlandata, "IP address", "wlanConfigIPAddress", "string");
|
||||
processType(jsonWlandata, "Net Mask", "wlanConfigNetMask", "string");
|
||||
processType(jsonWlandata, "BSSID", "wlanConfigBssid", "string");
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - processType(): Cannot extract WLAN data from this string: {}",
|
||||
dec.get(airqName).toString());
|
||||
}
|
||||
break;
|
||||
case "arr":
|
||||
JsonElement jsonarr = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if ((jsonarr != null) && (jsonarr.isJsonArray())) {
|
||||
JsonArray arr = jsonarr.getAsJsonArray();
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (JsonElement el : arr) {
|
||||
str.append(el.getAsString() + ", ");
|
||||
}
|
||||
updateState(channelName, new StringType(str.substring(0, str.length() - 2)));
|
||||
} else {
|
||||
logger.warn("air-Q - airqHandler - processType(): cannot handle this as an array: {}", jsonarr);
|
||||
}
|
||||
break;
|
||||
case "calib":
|
||||
JsonElement lastcalib = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if (lastcalib != null) {
|
||||
JsonObject calibobj = lastcalib.getAsJsonObject();
|
||||
String str = new String();
|
||||
Long timecalib;
|
||||
SimpleDateFormat sdfcalib = new SimpleDateFormat("dd.MM.yyyy' 'HH:mm:ss");
|
||||
for (Entry<String, JsonElement> entry : calibobj.entrySet()) {
|
||||
String attributeName = entry.getKey();
|
||||
JsonObject attributeValue = (JsonObject) entry.getValue();
|
||||
timecalib = Long.valueOf(attributeValue.get("timestamp").toString());
|
||||
String timecalibString = sdfcalib.format(new Date(timecalib * 1000));
|
||||
str = str + attributeName + ": offset=" + attributeValue.get("offset").getAsString() + " ["
|
||||
+ timecalibString + "]";
|
||||
}
|
||||
updateState(channelName, new StringType(str.substring(0, str.length() - 1)));
|
||||
} else {
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - processType(): Cannot extract calibration data from this string: {}",
|
||||
dec.get(airqName).toString());
|
||||
}
|
||||
break;
|
||||
case "property":
|
||||
String propstr = dec.get(airqName).toString();
|
||||
getThing().setProperty(channelName, propstr);
|
||||
break;
|
||||
case "proparr":
|
||||
JsonElement proparr = gson.fromJson(dec.get(airqName).toString(), JsonElement.class);
|
||||
if ((proparr != null) && proparr.isJsonArray()) {
|
||||
JsonArray arr = proparr.getAsJsonArray();
|
||||
String arrstr = new String();
|
||||
for (JsonElement el : arr) {
|
||||
arrstr = arrstr + el.getAsString() + ", ";
|
||||
}
|
||||
logger.trace("air-Q - airqHandler - processType(): property array {} set to {}", channelName,
|
||||
arrstr.substring(0, arrstr.length() - 2));
|
||||
getThing().setProperty(channelName, arrstr.substring(0, arrstr.length() - 2));
|
||||
} else {
|
||||
logger.warn("air-Q - airqHandler - processType(): cannot handle this as an array: {}", proparr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
"air-Q - airqHandler - processType(): a setting of type {} should be changed but I don't know this type.",
|
||||
type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void changeSettings(JsonObject jsonchange) {
|
||||
String jsoncmd = jsonchange.toString();
|
||||
logger.trace("air-Q - airqHandler - changeSettings(): called with jsoncmd={}", jsoncmd);
|
||||
Result res = null;
|
||||
String url = "http://" + config.ipAddress + "/config";
|
||||
String jsonbody = encrypt(jsoncmd.getBytes(StandardCharsets.UTF_8), config.password);
|
||||
String fullbody = "request=" + jsonbody;
|
||||
logger.trace("air-Q - airqHandler - changeSettings(): doing call to url={}, method=POST, body={}", url,
|
||||
fullbody);
|
||||
res = getData(url, "POST", fullbody);
|
||||
if (res != null) {
|
||||
JsonElement ans = gson.fromJson(res.getBody(), JsonElement.class);
|
||||
if (ans != null) {
|
||||
JsonObject jsonObj = ans.getAsJsonObject();
|
||||
String jsonAnswer = decrypt(jsonObj.get("content").getAsString().getBytes(), config.password);
|
||||
logger.trace("air-Q - airqHandler - changeSettings(): call returned {}", jsonAnswer);
|
||||
} else {
|
||||
logger.warn("The air-Q data could not be extracted from this string: {}", ans);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.airq.internal;
|
||||
|
||||
import static org.openhab.binding.airq.internal.AirqBindingConstants.THING_TYPE_AIRQ;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link AirqHandlerFactory} is responsible for creating the air-Q thing and its handlers.
|
||||
*
|
||||
* @author Aurelio Caliaro - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.airq", service = ThingHandlerFactory.class)
|
||||
public class AirqHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AIRQ);
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
|
||||
@Activate
|
||||
public AirqHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
@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 (THING_TYPE_AIRQ.equals(thingTypeUID)) {
|
||||
return new AirqHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="airq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>air-Q Binding</name>
|
||||
<description>This is the binding for air-Q devices. The air-Q device contains several sensors measuring gases in the
|
||||
air and other ambiance parameters like noise or temperature. With this binding you can integrate those values into
|
||||
your openHAB system and change parametrs of the air-Q device.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,125 @@
|
||||
# binding
|
||||
binding.airq.name = air-Q
|
||||
binding.airq.description = Binding für air-Q-Gerät
|
||||
|
||||
# thing types
|
||||
thing-type.airq.airq.label = air-Q
|
||||
thing-type.airq.airq.description = Thing für air-Q-Gerät
|
||||
|
||||
# thing type config description
|
||||
config.airq.airq.ipAddress.label = Netzwerk-Adresse
|
||||
config.airq.sample.config1.description = Netzwerk-Adresse, unter der das air-Q erreichbar ist
|
||||
|
||||
# channel types
|
||||
channel-type.airq.devid.label = Gerätenummer aus Datenbezug
|
||||
channel-type.airq.devid.description = Interne Nummer des air-Q
|
||||
channel-type.airq.status.label = Sensorenstatus
|
||||
channel-type.airq.status.description = Status der internen Sensoren
|
||||
channel-type.airq.typps.label = Durchschn. Staubgrösse (Experimentell)
|
||||
channel-type.airq.typps.description = Durchschnittliche Grösse des Feinstaubs
|
||||
channel-type.airq.bat.label = Batteriestatus
|
||||
channel-type.airq.bat.description = Stand der Batterie, sofern vorhanden
|
||||
channel-type.airq.cnt0_3.label = Feinstaub >0,3 μm
|
||||
channel-type.airq.cnt0_3.description = Feinstaubpartikel grösser 0,3 μm
|
||||
channel-type.airq.cnt0_5.label = Feinstaub >0,5 μm
|
||||
channel-type.airq.cnt0_5.description = Feinstaubpartikel grösser 0,5 μm
|
||||
channel-type.airq.cnt1.label = Feinstaub >1,0 μm
|
||||
channel-type.airq.cnt1.description = Feinstaubpartikel grösser 1,0 μm
|
||||
channel-type.airq.cnt2_5.label = Feinstaub >2,5 μm
|
||||
channel-type.airq.cnt2_5.description = Feinstaubpartikel grösser 2,5 μm
|
||||
channel-type.airq.cnt5.label = Feinstaub >5 μm
|
||||
channel-type.airq.cnt5.description = Feinstaubpartikel grösser 5 μm
|
||||
channel-type.airq.cnt10.label = Feinstaub >10 μm
|
||||
channel-type.airq.cnt10.description = Feinstaubpartikel grösser 10 μm
|
||||
channel-type.airq.co2.label = CO2
|
||||
channel-type.airq.co2.description = CO2
|
||||
channel-type.airq.dco2dt.label = Änderung CO2-Wert
|
||||
channel-type.airq.dco2dt.description = Änderung CO2-Wert
|
||||
channel-type.airq.dhdt.label = Feuchtigkeitsänderung
|
||||
channel-type.airq.dhdt.description = Feuchtigkeitsänderung
|
||||
channel-type.airq.dewpt.label = Taupunkt
|
||||
channel-type.airq.dewpt.description = Taupunkt
|
||||
channel-type.airq.door.label = Tür (experimentell)
|
||||
channel-type.airq.door.description = Tür wurde geöffnet
|
||||
channel-type.airq.health.label = Gesundheitsindex
|
||||
channel-type.airq.health.description = Gesundheitsindex
|
||||
channel-type.airq.humidity.label = Feuchtigkeit
|
||||
channel-type.airq.humidity.description = Feuchtigkeit
|
||||
channel-type.airq.humidity_abs.label = Absolute Feuchtigkeit
|
||||
channel-type.airq.humidity_abs.description = Absolute Feuchtigkeit
|
||||
channel-type.airq.mtime.label = Messdauer
|
||||
channel-type.airq.mtime.description = Dauer eines Messzyklus
|
||||
channel-type.airq.no2.label = NO2-Konzentration
|
||||
channel-type.airq.no2.description = NO2-Konzentration
|
||||
channel-type.airq.o3.label = O3-Konzentration
|
||||
channel-type.airq.o3.description = O3-Konzentration
|
||||
channel-type.airq.oxygen.label = Sauerstoff-Konzentration
|
||||
channel-type.airq.oxygen.description = O2-Konzentration (Sauerstoff)
|
||||
channel-type.airq.performance.label = Leistung
|
||||
channel-type.airq.performance.description = Leistungsindex
|
||||
channel-type.airq.pm1.label = Feinstaubkonzentration >1μ
|
||||
channel-type.airq.pm1.description = Konzentration Feinstaub >1μ
|
||||
channel-type.airq.pm10.label = Feinstaubkonzentration >10μ
|
||||
channel-type.airq.pm10.description = Konzentration Feinstaub >10μ
|
||||
channel-type.airq.pm2_5.label = Feinstaubkonzentration >2,5μ
|
||||
channel-type.airq.pm2_5.description = Konzentration Feinstaub >2,5μ
|
||||
channel-type.airq.pressure.label = Luftdruck
|
||||
channel-type.airq.pressure.description = Luftdruck
|
||||
channel-type.airq.so2.label = SO2-Konzentration
|
||||
channel-type.airq.so2.description = SO2-Konzentration
|
||||
channel-type.airq.sound.label = Lautstärke
|
||||
channel-type.airq.sound.description = Lautstärke
|
||||
channel-type.airq.temperature.label = Temperatur
|
||||
channel-type.airq.temperature.description = Temperatur
|
||||
channel-type.airq.timestamp.label = Messzeitpunkt
|
||||
channel-type.airq.timestamp.description = Messzeitpunkt
|
||||
channel-type.airq.tvoc.label = VOC-Konzentration
|
||||
channel-type.airq.tvoc.description = Konzentration organischer Chemikalien
|
||||
channel-type.airq.uptime.label = Laufzeit air-Q
|
||||
channel-type.airq.uptime.description = Laufzeit air-Q
|
||||
|
||||
channel-type.airq.bat_maxerr.label = Intervall Batteriestatus
|
||||
channel-type.airq.bat_maxerr.description = Intervall Stand der Batterie, sofern vorhanden
|
||||
channel-type.airq.cnt0_3_maxerr.label = Intervall Feinstaub >0,3 μm
|
||||
channel-type.airq.cnt0_3_maxerr.description = Intervall Feinstaubpartikel grösser 0,3 μm
|
||||
channel-type.airq.cnt0_5_maxerr.label = Intervall Feinstaub >0,5 μm
|
||||
channel-type.airq.cnt0_5_maxerr.description = Intervall Feinstaubpartikel grösser 0,5 μm
|
||||
channel-type.airq.cnt1_maxerr.label = Intervall Feinstaub >1,0 μm
|
||||
channel-type.airq.cnt1_maxerr.description = Intervall Feinstaubpartikel grösser 1,0 μm
|
||||
channel-type.airq.cnt2_5_maxerr.label = Intervall Feinstaub >2,5 μm
|
||||
channel-type.airq.cnt2_5_maxerr.description = Intervall Feinstaubpartikel grösser 2,5 μm
|
||||
channel-type.airq.cnt5_maxerr.label = Intervall Feinstaub >5 μm
|
||||
channel-type.airq.cnt5_maxerr.description = Intervall Feinstaubpartikel grösser 5 μm
|
||||
channel-type.airq.cnt10_maxerr.label = Intervall Feinstaub >10 μm
|
||||
channel-type.airq.cnt10_maxerr.description = Intervall Feinstaubpartikel grösser 10 μm
|
||||
channel-type.airq.co2_maxerr.label = Intervall CO2
|
||||
channel-type.airq.co2_maxerr.description = Intervall CO2
|
||||
channel-type.airq.dewpt_maxerr.label = Intervall Taupunkt
|
||||
channel-type.airq.dewpt_maxerr.description = Intervall Taupunkt
|
||||
channel-type.airq.humidity_maxerr.label = Intervall Feuchtigkeit
|
||||
channel-type.airq.humidity_maxerr.description = Intervall Feuchtigkeit
|
||||
channel-type.airq.humidity_abs_maxerr.label = Intervall Absolute Feuchtigkeit
|
||||
channel-type.airq.humidity_abs_maxerr.description = Intervall Absolute Feuchtigkeit
|
||||
channel-type.airq.no2_maxerr.label = Intervall NO2-Konzentration
|
||||
channel-type.airq.no2_maxerr.description = Intervall NO2-Konzentration
|
||||
channel-type.airq.o3_maxerr.label = Intervall O3-Konzentration
|
||||
channel-type.airq.o3_maxerr.description = Intervall O3-Konzentration
|
||||
channel-type.airq.oxygen_maxerr.label = Intervall Sauerstoff-Konzentration
|
||||
channel-type.airq.oxygen_maxerr.description = Intervall O2-Konzentration (Sauerstoff)
|
||||
channel-type.airq.pm1_maxerr.label = Intervall Feinstaubkonzentration >1μ
|
||||
channel-type.airq.pm1_maxerr.description = Intervall Konzentration Feinstaub >1μ
|
||||
channel-type.airq.pm10_maxerr.label = Intervall Feinstaubkonzentration >10μ
|
||||
channel-type.airq.pm10_maxerr.description = Intervall Konzentration Feinstaub >10μ
|
||||
channel-type.airq.pm2_5_maxerr.label = Intervall Feinstaubkonzentration >2,5μ
|
||||
channel-type.airq.pm2_5_maxerr.description = Intervall Konzentration Feinstaub >2,5μ
|
||||
channel-type.airq.pressure_maxerr.label = Intervall Luftdruck
|
||||
channel-type.airq.pressure_maxerr.description = Intervall Luftdruck
|
||||
channel-type.airq.so2_maxerr.label = Intervall SO2-Konzentration
|
||||
channel-type.airq.so2_maxerr.description = Intervall SO2-Konzentration
|
||||
channel-type.airq.sound_maxerr.label = Intervall Lautstärke
|
||||
channel-type.airq.sound_maxerr.description = Intervall Lautstärke
|
||||
channel-type.airq.temperature_maxerr.label = Intervall Temperatur
|
||||
channel-type.airq.temperature_maxerr.description = Intervall Temperatur
|
||||
channel-type.airq.tvoc_maxerr.label = Intervall VOC-Konzentration
|
||||
channel-type.airq.tvoc_maxerr.description = Intervall Konzentration organischer Chemikalien
|
||||
|
@ -0,0 +1,666 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="airq"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="airq">
|
||||
<label>air-Q</label>
|
||||
<description>Thing for air-Q Device</description>
|
||||
<category>Sensor</category>
|
||||
|
||||
<channels>
|
||||
<channel id="status" typeId="status"/>
|
||||
<channel id="avgFineDustSize" typeId="typps"/>
|
||||
<channel id="fineDustCnt00_3" typeId="cnt0_3"/>
|
||||
<channel id="fineDustCnt00_5" typeId="cnt0_5"/>
|
||||
<channel id="fineDustCnt01" typeId="cnt1"/>
|
||||
<channel id="fineDustCnt02_5" typeId="cnt2_5"/>
|
||||
<channel id="fineDustCnt05" typeId="cnt5"/>
|
||||
<channel id="fineDustCnt10" typeId="cnt10"/>
|
||||
<channel id="co" typeId="co"/>
|
||||
<channel id="co2" typeId="co2"/>
|
||||
<channel id="dCO2dt" typeId="dco2dt"/>
|
||||
<channel id="dHdt" typeId="dhdt"/>
|
||||
<channel id="dewpt" typeId="dewpt"/>
|
||||
<channel id="doorEvent" typeId="door"/>
|
||||
<channel id="health" typeId="health"/>
|
||||
<channel id="humidityRelative" typeId="humidity"/>
|
||||
<channel id="humidityAbsolute" typeId="humidity_abs"/>
|
||||
<channel id="measureTime" typeId="mtime"/>
|
||||
<channel id="no2" typeId="no2"/>
|
||||
<channel id="o3" typeId="o3"/>
|
||||
<channel id="o2" typeId="oxygen"/>
|
||||
<channel id="performance" typeId="performance"/>
|
||||
<channel id="fineDustConc01" typeId="pm1"/>
|
||||
<channel id="fineDustConc02_5" typeId="pm2_5"/>
|
||||
<channel id="fineDustConc10" typeId="pm10"/>
|
||||
<channel id="pressure" typeId="pressure"/>
|
||||
<channel id="so2" typeId="so2"/>
|
||||
<channel id="sound" typeId="sound"/>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="timestamp" typeId="timestamp"/>
|
||||
<channel id="tvoc" typeId="tvoc"/>
|
||||
<channel id="uptime" typeId="uptime"/>
|
||||
|
||||
<!-- Maximum error -->
|
||||
<channel id="fineDustCnt00_3_maxerr" typeId="cnt0_3_maxerr"/>
|
||||
<channel id="fineDustCnt00_5_maxerr" typeId="cnt0_5_maxerr"/>
|
||||
<channel id="fineDustCnt01_maxerr" typeId="cnt1_maxerr"/>
|
||||
<channel id="fineDustCnt02_5_maxerr" typeId="cnt2_5_maxerr"/>
|
||||
<channel id="fineDustCnt05_maxerr" typeId="cnt5_maxerr"/>
|
||||
<channel id="fineDustCnt10_maxerr" typeId="cnt10_maxerr"/>
|
||||
<channel id="co_maxerr" typeId="co_maxerr"/>
|
||||
<channel id="co2_maxerr" typeId="co2_maxerr"/>
|
||||
<channel id="dewpt_maxerr" typeId="dewpt_maxerr"/>
|
||||
<channel id="humidityRelative_maxerr" typeId="humidity_maxerr"/>
|
||||
<channel id="humidityAbsolute_maxerr" typeId="humidity_abs_maxerr"/>
|
||||
<channel id="no2_maxerr" typeId="no2_maxerr"/>
|
||||
<channel id="o3_maxerr" typeId="o3_maxerr"/>
|
||||
<channel id="o2_maxerr" typeId="oxygen_maxerr"/>
|
||||
<channel id="fineDustConc01_maxerr" typeId="pm1_maxerr"/>
|
||||
<channel id="fineDustConc02_5_maxerr" typeId="pm2_5_maxerr"/>
|
||||
<channel id="fineDustConc10_maxerr" typeId="pm10_maxerr"/>
|
||||
<channel id="pressure_maxerr" typeId="pressure_maxerr"/>
|
||||
<channel id="so2_maxerr" typeId="so2_maxerr"/>
|
||||
<channel id="sound_maxerr" typeId="sound_maxerr"/>
|
||||
<channel id="temperature_maxerr" typeId="temperature_maxerr"/>
|
||||
<channel id="tvoc_maxerr" typeId="tvoc_maxerr"/>
|
||||
|
||||
<!-- configuration data -->
|
||||
<channel id="wifi" typeId="Wifi"/>
|
||||
<channel id="ssid" typeId="WLANssid"/>
|
||||
<channel id="password" typeId="pass"/>
|
||||
<channel id="wifiInfo" typeId="WifiInfo"/>
|
||||
<channel id="timeServer" typeId="TimeServer"/>
|
||||
<channel id="location" typeId="geopos"/>
|
||||
<channel id="nightModeStartDay" typeId="nightmodeStartDay"/>
|
||||
<channel id="nightModeStartNight" typeId="nightmodeStartNight"/>
|
||||
<channel id="nightModeBrightnessDay" typeId="nightmodeBrightnessDay"/>
|
||||
<channel id="nightModeBrightnessNight" typeId="nightmodeBrightnessNight"/>
|
||||
<channel id="nightModeFanNightOff" typeId="nightmodeFanNightOff"/>
|
||||
<channel id="nightModeWifiNightOff" typeId="nightmodeWifiNightOff"/>
|
||||
<channel id="deviceName" typeId="deviceName"/>
|
||||
<channel id="roomType" typeId="RoomType"/>
|
||||
<channel id="logLevel" typeId="Logging"/>
|
||||
<channel id="deleteKey" typeId="DeleteKey"/>
|
||||
<channel id="fireAlarm" typeId="FireAlarm"/>
|
||||
<channel id="wlanConfigGateway" typeId="WLAN_config_Gateway"/>
|
||||
<channel id="wlanConfigMac" typeId="WLAN_config_MAC"/>
|
||||
<channel id="wlanConfigSsid" typeId="WLAN_config_SSID"/>
|
||||
<channel id="wlanConfigIPAddress" typeId="WLAN_config_IPAddress"/>
|
||||
<channel id="wlanConfigNetMask" typeId="WLAN_config_NetMask"/>
|
||||
<channel id="wlanConfigBssid" typeId="WLAN_config_BSSID"/>
|
||||
<channel id="cloudUpload" typeId="cloudUpload"/>
|
||||
<channel id="averagingRhythm" typeId="SecondsMeasurementDelay"/>
|
||||
<channel id="powerFreqSuppression" typeId="Rejection"/>
|
||||
<channel id="autoDriftCompensation" typeId="AutoDriftCompensation"/>
|
||||
<channel id="autoUpdate" typeId="AutoUpdate"/>
|
||||
<channel id="advancedDataProcessing" typeId="AdvancedDataProcessing"/>
|
||||
<channel id="ppm_and_ppb" typeId="ppm_and_ppb"/>
|
||||
<channel id="gasAlarm" typeId="GasAlarm"/>
|
||||
<channel id="soundPressure" typeId="SoundInfo"/>
|
||||
<channel id="alarmForwarding" typeId="AlarmForwarding"/>
|
||||
<channel id="userCalib" typeId="usercalib"/>
|
||||
<channel id="initialCalFinished" typeId="InitialCalFinished"/>
|
||||
<channel id="averaging" typeId="Averaging"/>
|
||||
<channel id="errorBars" typeId="ErrorBars"/>
|
||||
<channel id="warmupPhase" typeId="WarmupPhase"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="id">Unknown Device ID</property>
|
||||
<property name="hardwareVersion">Unknown Hardware version</property>
|
||||
<property name="softwareVersion">Unknown Software version</property>
|
||||
<property name="sensorList">Unknown sensor list</property>
|
||||
<property name="sensorInfo">No info about sensors</property>
|
||||
<property name="industry">No industry info</property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>IP Network Address where air-Q Can Be Reached.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>Password of air-Q Device.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="status" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>Status of Sensors</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="typps" advanced="false">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Average Size of Fine Dust</label>
|
||||
<state readOnly="true" pattern="%.2f μm"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt0_3" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >0,3 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt0_5" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >0,5 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt1" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >1 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt2_5" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >2,5 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt5" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >5 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt10" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fine Dust >10 μm</label>
|
||||
<state readOnly="true" pattern="%.0f"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>CO Concentration</label>
|
||||
<state readOnly="true" pattern="%.0f mg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co2" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>CO₂ Concentration</label>
|
||||
<state readOnly="true" pattern="%.0f %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dco2dt" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Change of CO₂ Concentr.</label>
|
||||
<state readOnly="true" pattern="%.2f ppm/s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dhdt" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Change of Humidity</label>
|
||||
<state readOnly="true" pattern="%.2f g/m³/s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dewpt" advanced="false">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Dew Point</label>
|
||||
<state readOnly="true" pattern="%.3f %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="door" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Door Event (exp)</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="health" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Health Index</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<state readOnly="true" pattern="%.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity_abs" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Absolute Humidity</label>
|
||||
<state readOnly="true" pattern="%.3f g/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mtime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Time Needed for Measurement</label>
|
||||
<state readOnly="true" pattern="%d ms"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="no2" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>NO₂ Concentration</label>
|
||||
<state readOnly="true" pattern="%.2f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="o3" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>O₃ Concentration</label>
|
||||
<state readOnly="true" pattern="%.2f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="oxygen" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Oxygen Concentration</label>
|
||||
<state readOnly="true" pattern="%.3f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="performance" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Performance Index</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm1" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fine Dust Concentr. >1μ</label>
|
||||
<state readOnly="true" pattern="%.0f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm10" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fine Dust Concentr. >10μ</label>
|
||||
<state readOnly="true" pattern="%.0f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm2_5" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>Fine Dust Concentr. >2,5μ</label>
|
||||
<state readOnly="true" pattern="%.0f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure" advanced="false">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<state readOnly="true" pattern="%.2f hPa"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="so2" advanced="false">
|
||||
<item-type>Number</item-type>
|
||||
<label>SO₂ Concentration</label>
|
||||
<state readOnly="true" pattern="%.2f μg/m³"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sound" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Noise</label>
|
||||
<state readOnly="true" pattern="%.1f %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature" advanced="false">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<state readOnly="true" pattern="%.2f %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timestamp" advanced="false">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Time Stamp</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tvoc" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>VOC Concentration</label>
|
||||
<state readOnly="true" pattern="%.0f %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="uptime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Uptime</label>
|
||||
<state readOnly="true" pattern="%d %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<!-- Maximum error -->
|
||||
<channel-type id="cnt0_3_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >0,3μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt0_5_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >0,5μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt1_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >1μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt2_5_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >2,5μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt5_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >5μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cnt10_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust >10μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error CO Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="co2_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error CO₂ Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="dewpt_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Dew Point</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Humidity</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity_abs_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Abs. Humidity</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="no2_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error NO₂ Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="o3_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error O₃ Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="oxygen_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Oxygen Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm1_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust Conc. >1μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm10_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust Conc. >10μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm2_5_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Fine Dust Conc. >2,5μm</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Pressure</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="so2_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error SO₂ Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sound_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Noise</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error Temperature</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tvoc_maxerr" advanced="true">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Max. Error VOC Conc.</label>
|
||||
<state readOnly="true" pattern="± %.2f %%"></state>
|
||||
</channel-type>
|
||||
|
||||
<!-- settings -->
|
||||
|
||||
<channel-type id="Wifi" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Use WLAN</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLANssid" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>WLAN SSID</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pass" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Device Password</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WifiInfo" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Show WLAN Status with LED</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="TimeServer" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Time Server</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="geopos" advanced="false">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location of air-Q Device</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeStartDay" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>Start of Day Operation</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeStartNight" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>End of Day Operation</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeBrightnessDay" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Day Brightness of LED</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeBrightnessNight" advanced="false">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Night Brightness of LED</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeFanNightOff" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch Off Fan at Night</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nightmodeWifiNightOff" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch Off WLAN at Night</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="deviceName" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>Device Name</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="RoomType" advanced="false">
|
||||
<item-type>String</item-type>
|
||||
<label>Room Type</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="Logging" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Logging Level</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="DeleteKey" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Settings to Be Deleted</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="FireAlarm" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fire Alarm</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_Gateway" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Network Gateway</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_MAC" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>MAC Address</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_SSID" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>WLAN SSID</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_IPAddress" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Assigned IP Address</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_NetMask" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Network Mask</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WLAN_config_BSSID" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Network BSSID</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cloudUpload" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Upload to air-Q Cloud</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="SecondsMeasurementDelay" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Rhythm Historic Average</label>
|
||||
<state pattern="%d s"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="Rejection" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Power Frequency</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="AutoDriftCompensation" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Compensate Automatic Drift</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="AutoUpdate" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Automatic Firmware Update</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="AdvancedDataProcessing" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Advanced Data Processing</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ppm_and_ppb" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Values in Particles</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="GasAlarm" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Gas Alarm</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="SoundInfo" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Sound Info</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="AlarmForwarding" advanced="false">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Share Alarms With Other air-Q</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="usercalib" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Sensor Calibration</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="InitialCalFinished" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Initial Calibration Done</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="Averaging" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Do Average</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ErrorBars" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Calculate Maximum Errors</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="WarmupPhase" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Send data as in Warmup Phase</label>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -41,6 +41,7 @@
|
||||
<module>org.openhab.transform.xslt</module>
|
||||
<!-- bindings -->
|
||||
<module>org.openhab.binding.adorne</module>
|
||||
<module>org.openhab.binding.airq</module>
|
||||
<module>org.openhab.binding.airquality</module>
|
||||
<module>org.openhab.binding.airvisualnode</module>
|
||||
<module>org.openhab.binding.alarmdecoder</module>
|
||||
|
Loading…
Reference in New Issue
Block a user