| Vibration Sensor | ZHAVibration | `vibrationsensor` |
| deCONZ Artificial Daylight Sensor | deCONZ specific: simulated sensor | `daylightsensor` |
| Carbon-Monoxide Sensor | ZHACarbonmonoxide | `carbonmonoxide` |
+| Air quality Sensor | ZHAAirQuality | `airqualitysensor` |
| Color Controller | ZBT-Remote-ALL-RGBW | `colorcontrol` |
| battery_level | Number | R | Battery level (in %) | any battery-powered sensor |
| battery_low | Switch | R | Battery level low: `ON`; `OFF` | any battery-powered sensor |
| carbonmonoxide | Switch | R | `ON` = carbon monoxide detected | carbonmonoxide |
+| airquality | String | R | Current air quality level | airqualitysensor |
+| airqualityppb | Number:Dimensionless | R | Current air quality ppb (parts per billion) | airqualitysensor |
| color | Color | R | Color set by remote | colorcontrol |
| windowopen | Contact | R | `windowopen` status is reported by some thermostats | thermostat |
presencesensor livingroom-presence "Livingroom Presence" [ id="1" ]
temperaturesensor livingroom-temperature "Livingroom Temperature" [ id="2" ]
humiditysensor livingroom-humidity "Livingroom Humidity" [ id="3" ]
+ airqualitysensor livingroom-voc "Livingroom Voc" [ id="9" ]
pressuresensor livingroom-pressure "Livingroom Pressure" [ id="4" ]
openclosesensor livingroom-window "Livingroom Window" [ id="5" ]
switch livingroom-hue-tap "Livingroom Hue Tap" [ id="6" ]
Switch Livingroom_Presence "Presence Livingroom [%s]" <motion> { channel="deconz:presencesensor:homeserver:livingroom-presence:presence" }
Number:Temperature Livingroom_Temperature "Temperature Livingroom [%.1f °C]" <temperature> { channel="deconz:temperaturesensor:homeserver:livingroom-temperature:temperature" }
Number:Dimensionless Livingroom_Humidity "Humidity Livingroom [%.1f %%]" <humidity> { channel="deconz:humiditysensor:homeserver:livingroom-humidity:humidity" }
+String Livingroom_voc_label "Air quality Livingroom [%s]" { channel="deconz:airqualitysensor:homeserver:livingroom-voc:airquality" }
+Number:Dimensionless Livingroom_voc "Air quality [%d ppb]" { channel="deconz:airqualitysensor:homeserver:livingroom-voc:airqualityppb" }
Number:Pressure Livingroom_Pressure "Pressure Livingroom [%.1f hPa]" <pressure> { channel="deconz:pressuresensor:homeserver:livingroom-pressure:pressure" }
Contact Livingroom_Window "Window Livingroom [%s]" <door> { channel="deconz:openclosesensor:homeserver:livingroom-window:open" }
Switch Basement_Water_Leakage "Basement Water Leakage [%s]" { channel="deconz:waterleakagesensor:homeserver:basement-water-leakage:waterleakage" }
public static final ThingTypeUID THING_TYPE_BATTERY_SENSOR = new ThingTypeUID(BINDING_ID, "batterysensor");
public static final ThingTypeUID THING_TYPE_CARBONMONOXIDE_SENSOR = new ThingTypeUID(BINDING_ID,
"carbonmonoxidesensor");
+ public static final ThingTypeUID THING_TYPE_AIRQUALITY_SENSOR = new ThingTypeUID(BINDING_ID, "airqualitysensor");
// Special sensor - Thermostat
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
public static final String CHANNEL_BATTERY_LEVEL = "battery_level";
public static final String CHANNEL_BATTERY_LOW = "battery_low";
public static final String CHANNEL_CARBONMONOXIDE = "carbonmonoxide";
+ public static final String CHANNEL_AIRQUALITY = "airquality";
+ public static final String CHANNEL_AIRQUALITYPPB = "airqualityppb";
public static final String CHANNEL_HEATSETPOINT = "heatsetpoint";
public static final String CHANNEL_THERMOSTAT_MODE = "mode";
public static final String CHANNEL_TEMPERATURE_OFFSET = "offset";
thingTypeUID = THING_TYPE_BATTERY_SENSOR; // ZHABattery
} else if (sensor.type.contains("ZHAThermostat")) {
thingTypeUID = THING_TYPE_THERMOSTAT; // ZHAThermostat
+ } else if (sensor.type.contains("ZHAAirQuality")) {
+ thingTypeUID = THING_TYPE_AIRQUALITY_SENSOR;
} else {
logger.debug("Unknown type {}", sensor.type);
return;
public @Nullable Boolean vibration;
/** carbonmonoxide sensors provide a boolean value. */
public @Nullable Boolean carbonmonoxide;
+ /** airquality sensors provide a string value. */
+ public @Nullable String airquality;
+ /** airquality sensors provide a integer value. */
+ public @Nullable Integer airqualityppb;
/** Pressure sensors provide a hPa value. */
public @Nullable Integer pressure;
/** Presence sensors provide this boolean. */
return "SensorState{" + "dark=" + dark + ", daylight=" + daylight + ", lightlevel=" + lightlevel + ", lux="
+ lux + ", temperature=" + temperature + ", humidity=" + humidity + ", open=" + open + ", fire=" + fire
+ ", water=" + water + ", alarm=" + alarm + ", tampered=" + tampered + ", vibration=" + vibration
- + ", carbonmonoxide=" + carbonmonoxide + ", pressure=" + pressure + ", presence=" + presence
- + ", power=" + power + ", battery=" + battery + ", consumption=" + consumption + ", voltage=" + voltage
- + ", current=" + current + ", status=" + status + ", buttonevent=" + buttonevent + ", gesture="
- + gesture + ", valve=" + valve + ", windowopen='" + windowopen + '\'' + ", lastupdated='" + lastupdated
- + '\'' + ", xy=" + Arrays.toString(xy) + '}';
+ + ", carbonmonoxide=" + carbonmonoxide + ", airquality=" + airquality + ", airqualityppb="
+ + airqualityppb + ", pressure=" + pressure + ", presence=" + presence + ", power=" + power
+ + ", battery=" + battery + ", consumption=" + consumption + ", voltage=" + voltage + ", current="
+ + current + ", status=" + status + ", buttonevent=" + buttonevent + ", gesture=" + gesture + ", valve="
+ + valve + ", windowopen='" + windowopen + '\'' + ", lastupdated='" + lastupdated + '\'' + ", xy="
+ + Arrays.toString(xy) + '}';
}
}
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
updateState(channelUID, OnOffType.from(value));
}
+ protected void updateStringChannel(ChannelUID channelUID, @Nullable String value) {
+ updateState(channelUID, new StringType(value));
+ }
+
protected void updateDecimalTypeChannel(ChannelUID channelUID, @Nullable Number value) {
if (value == null) {
return;
THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
THING_TYPE_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
- THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_COLOR_CONTROL);
+ THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_AIRQUALITY_SENSOR, THING_TYPE_COLOR_CONTROL);
private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
case CHANNEL_CARBONMONOXIDE:
updateSwitchChannel(channelUID, newState.carbonmonoxide);
break;
+ case CHANNEL_AIRQUALITY:
+ updateStringChannel(channelUID, newState.airquality);
+ break;
+ case CHANNEL_AIRQUALITYPPB:
+ updateDecimalTypeChannel(channelUID, newState.airqualityppb);
+ break;
case CHANNEL_BUTTON:
updateDecimalTypeChannel(channelUID, newState.buttonevent);
break;
thing-type.deconz.batterysensor.label = Battery Sensor
thing-type.deconz.batterysensor.description = A battery sensor
thing-type.deconz.carbonmonoxidesensor.label = Carbon-monoxide Sensor
+thing-type.deconz.airqualitysensor.label = Air quality Sensor
+thing-type.deconz.airqualitysensor.description = An air quality sensor
thing-type.deconz.colorcontrol.label = Color Controller
thing-type.deconz.colorlight.label = Color Light
thing-type.deconz.colorlight.description = A dimmable light with adjustable color.
channel-type.deconz.buttonevent.description = This channel is triggered on a button event. The trigger payload consists of the button event number.
channel-type.deconz.carbonmonoxide.label = Carbon-monoxide
channel-type.deconz.carbonmonoxide.description = Carbon-monoxide was detected.
+channel-type.deconz.airquality.label = Air quality level
+channel-type.deconz.airquality.description = Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor, ...
+channel-type.deconz.airqualityppb.label = Air quality in ppb
+channel-type.deconz.airqualityppb.description = Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is specified in ppb (parts per billion).
channel-type.deconz.consumption.label = Consumption
channel-type.deconz.consumption.description = Current consumption
channel-type.deconz.ct.label = Color Temperature
thing-type.deconz.batterysensor.label = Batteriesensor
thing-type.deconz.batterysensor.description = Ein Batteriesensor
thing-type.deconz.carbonmonoxidesensor.label = Kohlenmonoxid-Sensor
+thing-type.deconz.airqualitysensor.label = Luftqualit\u00e4tssensor
+thing-type.deconz.airqualitysensor.description = Ein Luftqualit\u00e4tssensor
thing-type.deconz.colorcontrol.label = Farbregler
thing-type.deconz.colorlight.label = Farbige Lampe
thing-type.deconz.colorlight.description = Dimmbare Lampe mit einstellbarer Farbe.
channel-type.deconz.buttonevent.description = Dieser Channel wird bei Bestätigung einer Schaltfläche ausgelöst. Der Trigger-Payload besteht aus der Nummer der Schaltfläche.
channel-type.deconz.carbonmonoxide.label = Kohlenmonoxid
channel-type.deconz.carbonmonoxide.description = Zeigt an, ob ein erhöhter Kohlenmonoxid-Wert erkannt wurde.
+channel-type.deconz.airquality.label = Luftqualit\u00e4t
+channel-type.deconz.airquality.description = Derzeitige Luftqualit\u00e4t auf der Grundlage von Messungen fl\u00fcchtiger organischer Verbindungen (VOCs). Beispiel: good oder poor, ...
+channel-type.deconz.airqualityppb.label = Luftqualit\u00e4t in ppb
+channel-type.deconz.airqualityppb.description = Derzeitige Luftqualit\u00e4t auf der Grundlage von Messungen der fl\u00fcchtigen organischen Verbindungen (VOC). Der Messwert wird in ppb (parts per billion) angegeben.
channel-type.deconz.consumption.label = Verbrauch
channel-type.deconz.consumption.description = Zeigt den aktuellen Verbrauch an.
channel-type.deconz.ct.label = Farbtemperatur
<state readOnly="true"/>
</channel-type>
+
+ <thing-type id="airqualitysensor">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="deconz"/>
+ </supported-bridge-type-refs>
+ <label>Air quality Sensor</label>
+ <description>An air quality sensor</description>
+ <channels>
+ <channel typeId="airquality" id="airquality"/>
+ <channel typeId="airqualityppb" id="airqualityppb"/>
+ <channel typeId="last_updated" id="last_updated"/>
+ </channels>
+
+ <representation-property>uid</representation-property>
+
+ <config-description-ref uri="thing-type:deconz:sensor"/>
+ </thing-type>
+
+ <channel-type id="airquality">
+ <item-type>String</item-type>
+ <label>Air quality level</label>
+ <description>Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor,
+ ...</description>
+ <state readOnly="true" pattern="%s"></state>
+ </channel-type>
+
+ <channel-type id="airqualityppb">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Air quality in ppb</label>
+ <description>Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is
+ specified in ppb (parts per billion).</description>
+ <state readOnly="true" pattern="%d"></state>
+ </channel-type>
+
+
<thing-type id="thermostat">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
*
* @author Jan N. Klug - Initial contribution
* @author Lukas Agethen - Added Thermostat
+ * @author Philipp Schneider - Added air quality sensor
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(OnOffType.ON));
}
+ @Test
+ public void airQualitySensorUpdateTest() throws IOException {
+ // ARRANGE
+ SensorMessage sensorMessage = DeconzTest.getObjectFromJson("airquality.json", SensorMessage.class, gson);
+ assertNotNull(sensorMessage);
+
+ ThingUID thingUID = new ThingUID("deconz", "sensor");
+ ChannelUID channelUID = new ChannelUID(thingUID, "airquality");
+ Thing sensor = ThingBuilder.create(THING_TYPE_AIRQUALITY_SENSOR, thingUID)
+ .withChannel(ChannelBuilder.create(channelUID, "String").build()).build();
+ SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
+ sensorThingHandler.setCallback(thingHandlerCallback);
+
+ // ACT
+ sensorThingHandler.messageReceived("", sensorMessage);
+
+ // ASSERT
+ Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(StringType.valueOf("good")));
+ }
+
+ @Test
+ public void airQualityPpbSensorUpdateTest() throws IOException {
+ // ARRANGE
+ SensorMessage sensorMessage = DeconzTest.getObjectFromJson("airquality.json", SensorMessage.class, gson);
+ assertNotNull(sensorMessage);
+
+ ThingUID thingUID = new ThingUID("deconz", "sensor");
+ ChannelUID channelUID = new ChannelUID(thingUID, "airqualityppb");
+ Thing sensor = ThingBuilder.create(THING_TYPE_AIRQUALITY_SENSOR, thingUID)
+ .withChannel(ChannelBuilder.create(channelUID, "Number").build()).build();
+ SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
+ sensorThingHandler.setCallback(thingHandlerCallback);
+
+ // ACT
+ sensorThingHandler.messageReceived("", sensorMessage);
+
+ // ASSERT
+ Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelUID), eq(new DecimalType(129)));
+ }
+
@Test
public void thermostatSensorUpdateTest() throws IOException {
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("thermostat.json", SensorMessage.class, gson);
--- /dev/null
+{
+ "config": {
+ "battery": 100,
+ "on": true,
+ "reachable": true
+ },
+ "ep": 38,
+ "etag": "ca904b42f63d0ccccccccccccccccccccc",
+ "lastannounced": "2021-12-28T23:59:02Z",
+ "lastseen": "2021-12-29T01:18Z",
+ "manufacturername": "Develco Products A/S",
+ "modelid": "AQSZB-110",
+ "name": "AirQuality 4",
+ "state": {
+ "airquality": "good",
+ "airqualityppb": 129,
+ "lastupdated": "2021-12-29T01:18:41.184"
+ },
+ "swversion": "2021-10-28 08:59",
+ "type": "ZHAAirQuality",
+ "uniqueid": "00:00:00:00:00:00:00:00-00-fc03"
+}
\ No newline at end of file