[deconz] Cleanup code and improve tests, edit channels to vibration sensor (#14641)

* [deconz] Cleanup code and improve tests

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2023-03-21 22:46:53 +01:00 committed by GitHub
parent 8e902f6324
commit 1786bb0eec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1130 additions and 1000 deletions

View File

@ -124,44 +124,47 @@ Bridge deconz:deconz:homeserver [ host="192.168.0.10", apikey="ABCDEFGHIJ" ]
The sensor devices support some of the following channels:
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|--------------------|--------------------------|-------------|-------------------------------------------------------------------------------------------|---------------------------------------------------|
| presence | Switch | R | Status of presence: `ON` = presence; `OFF` = no-presence | presencesensor |
| enabled | Switch | R/W | This channel activates or deactivates the sensor | presencesensor |
| last_updated | DateTime | R | Timestamp when the sensor was last updated | all, except daylightsensor |
| last_seen | DateTime | R | Timestamp when the sensor was last seen | all, except daylightsensor |
| power | Number:Power | R | Power usage in Watts | powersensor, sometimes for consumptionsensor |
| consumption | Number:Energy | R | Energy in Watt*Hour | consumptionsensor |
| voltage | Number:ElectricPotential | R | Voltage in V | some powersensors |
| current | Number:ElectricCurrent | R | Current in mA | some powersensors |
| button | Number | R | Last pressed button id on a switch | switch, colorcontrol |
| gesture | Number | R | A gesture that was performed with the switch | switch |
| lightlux | Number:Illuminance | R | Light illuminance in Lux | lightsensor |
| light_level | Number | R | Light level | lightsensor |
| dark | Switch | R | Light level is below the darkness threshold | lightsensor, sometimes for presencesensor |
| daylight | Switch | R | Light level is above the daylight threshold | lightsensor |
| temperature | Number:Temperature | R | Temperature in ˚C | temperaturesensor, some Xiaomi sensors,thermostat |
| humidity | Number:Dimensionless | R | Humidity in % | humiditysensor |
| pressure | Number:Pressure | R | Pressure in hPa | pressuresensor |
| open | Contact | R | Status of contacts: `OPEN`; `CLOSED` | openclosesensor |
| waterleakage | Switch | R | Status of water leakage: `ON` = water leakage detected; `OFF` = no water leakage detected | waterleakagesensor |
| fire | Switch | R | Status of a fire: `ON` = fire was detected; `OFF` = no fire detected | firesensor |
| alarm | Switch | R | Status of an alarm: `ON` = alarm was triggered; `OFF` = no alarm | alarmsensor |
| tampered | Switch | R | Status of a zone: `ON` = zone is being tampered; `OFF` = zone is not tampered | any IAS sensor |
| vibration | Switch | R | Status of vibration: `ON` = vibration was detected; `OFF` = no vibration | alarmsensor |
| light | String | R | Light level: `Daylight`; `Sunset`; `Dark` | daylightsensor |
| value | Number | R | Sun position: `130` = dawn; `140` = sunrise; `190` = sunset; `210` = dusk | daylightsensor |
| 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 |
| color | Color | R | Color set by remote | colorcontrol |
| windowopen | Contact | R | `windowopen` status is reported by some thermostats | thermostat |
| externalwindowopen | Contact | R/W | forward a status to a thermostat (some devices) | thermostat |
| on | Switch | R | some thermostats report their output state as switch | thermostat |
| locked | Switch | R/W | reports/sets the child lock on some thermostats | thermostat |
| airquality | String | R | Airquality as string | airqualitysensor |
| airqualityppb | Number:Dimensionless | R | Airquality (in parts-per-billion) | airqualitysensor |
| moisture | Number:Dimensionless | R | Moisture | moisturesensor |
| Channel Type ID | Item Type | Access Mode | Description | Thing types |
|-----------------------|--------------------------|-------------|-------------------------------------------------------------------------------------------|---------------------------------------------------|
| airquality | String | R | Airquality as string | airqualitysensor |
| airqualityppb | Number:Dimensionless | R | Airquality (in parts-per-billion) | airqualitysensor |
| alarm | Switch | R | Status of an alarm: `ON` = alarm was triggered; `OFF` = no alarm | alarmsensor |
| battery_level | Number | R | Battery level (in %) | any battery-powered sensor |
| battery_low | Switch | R | Battery level low: `ON`; `OFF` | any battery-powered sensor |
| button | Number | R | Last pressed button id on a switch | switch, colorcontrol |
| carbonmonoxide | Switch | R | `ON` = carbon monoxide detected | carbonmonoxide |
| color | Color | R | Color set by remote | colorcontrol |
| consumption | Number:Energy | R | Energy in Watt*Hour | consumptionsensor |
| current | Number:ElectricCurrent | R | Current in mA | some powersensors |
| dark | Switch | R | Light level is below the darkness threshold | lightsensor, sometimes for presencesensor |
| daylight | Switch | R | Light level is above the daylight threshold | lightsensor |
| enabled | Switch | R/W | This channel activates or deactivates the sensor | presencesensor |
| externalwindowopen | Contact | R/W | forward a status to a thermostat (some devices) | thermostat |
| fire | Switch | R | Status of a fire: `ON` = fire was detected; `OFF` = no fire detected | firesensor |
| gesture | Number | R | A gesture that was performed with the switch | switch |
| humidity | Number:Dimensionless | R | Humidity in % | humiditysensor |
| last_updated | DateTime | R | Timestamp when the sensor was last updated | all, except daylightsensor |
| last_seen | DateTime | R | Timestamp when the sensor was last seen | all, except daylightsensor |
| light | String | R | Light level: `Daylight`; `Sunset`; `Dark` | daylightsensor |
| lightlux | Number:Illuminance | R | Light illuminance in Lux | lightsensor |
| light_level | Number | R | Light level | lightsensor |
| locked | Switch | R/W | reports/sets the child lock on some thermostats | thermostat |
| moisture | Number:Dimensionless | R | Moisture | moisturesensor |
| on | Switch | R | some thermostats report their output state as switch | thermostat |
| open | Contact | R | Status of contacts: `OPEN`; `CLOSED` | openclosesensor |
| orientation_x, _y, _z | Number | R | Orientation of vibration sensor | vibrationsensor |
| power | Number:Power | R | Power usage in Watts | powersensor, sometimes for consumptionsensor |
| presence | Switch | R | Status of presence: `ON` = presence; `OFF` = no-presence | presencesensor |
| pressure | Number:Pressure | R | Pressure in hPa | pressuresensor |
| tampered | Switch | R | Status of a zone: `ON` = zone is being tampered; `OFF` = zone is not tampered | any IAS sensor |
| temperature | Number:Temperature | R | Temperature in ˚C | temperaturesensor, some Xiaomi sensors,thermostat |
| tiltangle | Number:Angle | R | Tilt angle of vibration sensor | vibrationsensor |
| value | Number | R | Sun position: `130` = dawn; `140` = sunrise; `190` = sunset; `210` = dusk | daylightsensor |
| vibration | Switch | R | Vibration detected | vibrationsensor |
| vibrationstrength | Number | R | Strength of detected vibration (value is device-dependent) | vibrationsensor |
| voltage | Number:ElectricPotential | R | Voltage in V | some powersensors |
| waterleakage | Switch | R | Status of water leakage: `ON` = water leakage detected; `OFF` = no water leakage detected | waterleakagesensor |
| windowopen | Contact | R | `windowopen` status is reported by some thermostats | thermostat |
**NOTE:** Beside other non-mandatory channels, the `battery_level` and `battery_low` channels will be added to the Thing during runtime if the sensor is battery-powered.
The specification of your sensor depends on the deCONZ capabilities.

View File

@ -72,64 +72,70 @@ public class BindingConstants {
public static final ThingTypeUID THING_TYPE_LIGHTGROUP = new ThingTypeUID(BINDING_ID, "lightgroup");
// sensor channel ids
public static final String CHANNEL_PRESENCE = "presence";
public static final String CHANNEL_ENABLED = "enabled";
public static final String CHANNEL_LAST_UPDATED = "last_updated";
public static final String CHANNEL_LAST_SEEN = "last_seen";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_AIRQUALITY = "airquality";
public static final String CHANNEL_AIRQUALITYPPB = "airqualityppb";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_BATTERY_LEVEL = "battery_level";
public static final String CHANNEL_BATTERY_LOW = "battery_low";
public static final String CHANNEL_BUTTON = "button";
public static final String CHANNEL_BUTTONEVENT = "buttonevent";
public static final String CHANNEL_CARBONMONOXIDE = "carbonmonoxide";
public static final String CHANNEL_CONSUMPTION = "consumption";
public static final String CHANNEL_CONSUMPTION_2 = "consumption2";
public static final String CHANNEL_VOLTAGE = "voltage";
public static final String CHANNEL_CURRENT = "current";
public static final String CHANNEL_VALUE = "value";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_DARK = "dark";
public static final String CHANNEL_DAYLIGHT = "daylight";
public static final String CHANNEL_ENABLED = "enabled";
public static final String CHANNEL_EXTERNAL_WINDOW_OPEN = "externalwindowopen";
public static final String CHANNEL_FIRE = "fire";
public static final String CHANNEL_GESTURE = "gesture";
public static final String CHANNEL_GESTUREEVENT = "gestureevent";
public static final String CHANNEL_HEATSETPOINT = "heatsetpoint";
public static final String CHANNEL_HUMIDITY = "humidity";
public static final String CHANNEL_PRESSURE = "pressure";
public static final String CHANNEL_LIGHT = "light";
public static final String CHANNEL_LIGHT_LUX = "lightlux";
public static final String CHANNEL_LIGHT_LEVEL = "light_level";
public static final String CHANNEL_DARK = "dark";
public static final String CHANNEL_DAYLIGHT = "daylight";
public static final String CHANNEL_BUTTON = "button";
public static final String CHANNEL_BUTTONEVENT = "buttonevent";
public static final String CHANNEL_GESTURE = "gesture";
public static final String CHANNEL_GESTUREEVENT = "gestureevent";
public static final String CHANNEL_OPENCLOSE = "open";
public static final String CHANNEL_WATERLEAKAGE = "waterleakage";
public static final String CHANNEL_FIRE = "fire";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_TAMPERED = "tampered";
public static final String CHANNEL_VIBRATION = "vibration";
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_PRESENCE = "presence";
public static final String CHANNEL_LAST_SEEN = "last_seen";
public static final String CHANNEL_LAST_UPDATED = "last_updated";
public static final String CHANNEL_MOISTURE = "moisture";
public static final String CHANNEL_HEATSETPOINT = "heatsetpoint";
public static final String CHANNEL_OPENCLOSE = "open";
public static final String CHANNEL_ORIENTATION_X = "orientation_x";
public static final String CHANNEL_ORIENTATION_Y = "orientation_y";
public static final String CHANNEL_ORIENTATION_Z = "orientation_z";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_PRESSURE = "pressure";
public static final String CHANNEL_TAMPERED = "tampered";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_TEMPERATURE_OFFSET = "offset";
public static final String CHANNEL_THERMOSTAT_MODE = "mode";
public static final String CHANNEL_THERMOSTAT_LOCKED = "locked";
public static final String CHANNEL_TEMPERATURE_OFFSET = "offset";
public static final String CHANNEL_THERMOSTAT_ON = "on";
public static final String CHANNEL_TILTANGLE = "tiltangle";
public static final String CHANNEL_VALVE_POSITION = "valve";
public static final String CHANNEL_VIBRATION = "vibration";
public static final String CHANNEL_VIBRATION_STRENGTH = "vibrationstrength";
public static final String CHANNEL_VOLTAGE = "voltage";
public static final String CHANNEL_VALUE = "value";
public static final String CHANNEL_WATERLEAKAGE = "waterleakage";
public static final String CHANNEL_WINDOW_OPEN = "windowopen";
public static final String CHANNEL_EXTERNAL_WINDOW_OPEN = "externalwindowopen";
// group + light channel ids
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_POSITION = "position";
public static final String CHANNEL_ALERT = "alert";
public static final String CHANNEL_ALL_ON = "all_on";
public static final String CHANNEL_ANY_ON = "any_on";
public static final String CHANNEL_LOCK = "lock";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_TEMPERATURE = "color_temperature";
public static final String CHANNEL_EFFECT = "effect";
public static final String CHANNEL_EFFECT_SPEED = "effectSpeed";
public static final String CHANNEL_SCENE = "scene";
public static final String CHANNEL_LOCK = "lock";
public static final String CHANNEL_ONTIME = "ontime";
public static final String CHANNEL_POSITION = "position";
public static final String CHANNEL_SCENE = "scene";
public static final String CHANNEL_SWITCH = "switch";
// channel uids
public static final ChannelTypeUID CHANNEL_EFFECT_TYPE_UID = new ChannelTypeUID(BINDING_ID, CHANNEL_EFFECT);
public static final ChannelTypeUID CHANNEL_EFFECT_SPEED_TYPE_UID = new ChannelTypeUID(BINDING_ID,

View File

@ -28,80 +28,55 @@ import org.eclipse.jdt.annotation.Nullable;
*/
@NonNullByDefault
public class SensorState {
/** Some presence sensors, the daylight sensor and all light sensors provide the "dark" boolean. */
public @Nullable Boolean dark;
/** The daylight sensor and all light sensors provides the "daylight" boolean. */
public @Nullable Boolean daylight;
/** Light sensors provide a light level value. */
public @Nullable Integer lightlevel;
/** Light sensors provide a lux value. */
public @Nullable Integer lux;
/** Temperature sensors provide a degrees value. */
public @Nullable Double temperature;
/** Humidity sensors provide a percent value. */
public @Nullable Double humidity;
/** OpenClose sensors provide a boolean value. */
public @Nullable Boolean open;
/** fire sensors provide a boolean value. */
public @Nullable Boolean fire;
/** water sensors provide a boolean value. */
public @Nullable Boolean water;
/** alarm sensors provide a boolean value. */
public @Nullable Boolean alarm;
/** IAS Zone sensors provide a boolean value. */
public @Nullable Boolean tampered;
/** vibration sensors provide a boolean value. */
public @Nullable Boolean vibration;
/** carbonmonoxide sensors provide a boolean value. */
public @Nullable Boolean carbonmonoxide;
/** Pressure sensors provide a hPa value. */
public @Nullable Integer pressure;
/** Presence sensors provide this boolean. */
public @Nullable Boolean presence;
/** Power sensors provide this value in Watts. */
public @Nullable Double power;
/** Batttery sensors provide this value */
public @Nullable Integer battery;
/** Consumption sensors provide this value in Watts/hour. */
public @Nullable Boolean lowbattery;
/** Consumption sensors provide this value in Watts/hour. */
public @Nullable Double consumption;
public @Nullable Double consumption2;
/** Power sensors provide this value in Volt. */
public @Nullable Double voltage;
/** Power sensors provide this value in Milliampere. */
public @Nullable Double current;
/** Light sensors and the daylight sensor provide a status integer that can have various semantics. */
public @Nullable Integer status;
/** Switches provide this value. */
public @Nullable Integer buttonevent;
/** Switches may provide this value. */
public @Nullable Integer gesture;
/** Thermostat may provide this value. */
public @Nullable Integer valve;
public @Nullable Boolean on;
/** air quality sensors provide this value */
public @Nullable String airquality;
public @Nullable Integer airqualityppb;
/** moisture sensors provide this value */
public @Nullable Integer moisture;
/** Thermostats may provide this value */
public @Nullable String windowopen;
/** deCONZ sends a last update string with every event. */
public @Nullable Boolean alarm;
public @Nullable Integer battery;
public @Nullable Integer buttonevent;
public @Nullable Boolean carbonmonoxide;
public @Nullable Double consumption;
public @Nullable Double consumption2;
public @Nullable Double current;
public @Nullable Boolean dark;
public @Nullable Boolean daylight;
public @Nullable Boolean fire;
public @Nullable Integer gesture;
public @Nullable Double humidity;
public @Nullable String lastupdated;
/** color controllers send xy values */
public @Nullable Integer lightlevel;
public @Nullable Boolean lowbattery;
public @Nullable Integer lux;
public @Nullable Integer moisture;
public @Nullable Boolean on;
public @Nullable Boolean open;
public Integer @Nullable [] orientation;
public @Nullable Double power;
public @Nullable Boolean presence;
public @Nullable Integer pressure;
public @Nullable Integer status;
public @Nullable Boolean tampered;
public @Nullable Double temperature;
public @Nullable Integer tiltangle;
public @Nullable Integer valve;
public @Nullable Boolean vibration;
public @Nullable Integer vibrationstrength;
public @Nullable Double voltage;
public @Nullable Boolean water;
public @Nullable String windowopen;
public double @Nullable [] xy;
@Override
public String toString() {
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 + ", lowbattery=" + lowbattery + ", consumption="
+ consumption + ", voltage=" + voltage + ", current=" + current + ", status=" + status
+ ", buttonevent=" + buttonevent + ", gesture=" + gesture + ", valve=" + valve + ", airquality='"
+ airquality + "'" + ", airqualityppb=" + airqualityppb + ", windowopen='" + windowopen + "'"
+ ", lastupdated='" + lastupdated + "'" + ", xy=" + Arrays.toString(xy) + "}";
return "SensorState{" + "airquality='" + airquality + "'" + ", airqualityppb=" + airqualityppb + ", alarm="
+ alarm + ", battery=" + battery + ", buttonevent=" + buttonevent + ", carbonmonoxide=" + carbonmonoxide
+ ", consumption=" + consumption + ", consumption2=" + consumption2 + ", current=" + current + ", dark="
+ dark + ", daylight=" + daylight + ", fire=" + fire + ", gesture=" + gesture + ", humidity=" + humidity
+ ", lastupdated='" + lastupdated + "'" + ", lightlevel=" + lightlevel + ", lowbattery=" + lowbattery
+ ", lux=" + lux + ", moisture=" + moisture + ", on=" + on + ", open=" + open + ", orientation="
+ Arrays.toString(orientation) + ", power=" + power + ", presence=" + presence + ", pressure="
+ pressure + ", status=" + status + ", tampered=" + tampered + ", temperature=" + temperature
+ ", tiltangle=" + tiltangle + ", valve=" + valve + ", vibration=" + vibration + ", vibrationstrength="
+ vibrationstrength + ", voltage=" + voltage + ", water=" + water + ", windowopen='" + windowopen + "'"
+ ", xy=" + Arrays.toString(xy) + "}";
}
}

View File

@ -33,6 +33,7 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -182,6 +183,8 @@ public abstract class SensorBaseThingHandler extends DeconzBaseThingHandler {
String lastUpdated = newState.lastupdated;
if (lastUpdated != null && !"none".equals(lastUpdated)) {
updateState(channelUID, Util.convertTimestampToDateTime(lastUpdated));
} else if ("none".equals(lastUpdated)) {
updateState(channelUID, UnDefType.UNDEF);
}
}
case CHANNEL_BATTERY_LOW -> updateSwitchChannel(channelUID, newState.lowbattery);

View File

@ -28,7 +28,6 @@ import org.openhab.binding.deconz.internal.dto.SensorUpdateConfig;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
@ -107,73 +106,55 @@ public class SensorThingHandler extends SensorBaseThingHandler {
protected void valueUpdated(ChannelUID channelUID, SensorState newState, boolean initializing) {
super.valueUpdated(channelUID, newState, initializing);
switch (channelUID.getId()) {
case CHANNEL_AIRQUALITY -> updateStringChannel(channelUID, newState.airquality);
case CHANNEL_AIRQUALITYPPB ->
updateQuantityTypeChannel(channelUID, newState.airqualityppb, PARTS_PER_BILLION);
case CHANNEL_ALARM -> updateSwitchChannel(channelUID, newState.alarm);
case CHANNEL_BATTERY_LEVEL -> updateDecimalTypeChannel(channelUID, newState.battery);
case CHANNEL_LIGHT -> {
Boolean dark = newState.dark;
if (dark != null) {
Boolean daylight = newState.daylight;
if (dark) { // if it's dark, it's dark ;)
updateState(channelUID, new StringType("Dark"));
} else if (daylight != null) { // if its not dark, it might be between darkness and daylight
if (daylight) {
updateState(channelUID, new StringType("Daylight"));
} else {
updateState(channelUID, new StringType("Sunset"));
}
} else { // if no daylight value is known, we assume !dark means daylight
updateState(channelUID, new StringType("Daylight"));
}
}
}
case CHANNEL_POWER -> updateQuantityTypeChannel(channelUID, newState.power, WATT);
case CHANNEL_CONSUMPTION -> updateQuantityTypeChannel(channelUID, newState.consumption, WATT_HOUR);
case CHANNEL_VOLTAGE -> updateQuantityTypeChannel(channelUID, newState.voltage, VOLT);
case CHANNEL_CURRENT -> updateQuantityTypeChannel(channelUID, newState.current, MILLI(AMPERE));
case CHANNEL_LIGHT_LUX -> updateQuantityTypeChannel(channelUID, newState.lux, LUX);
case CHANNEL_BUTTON -> updateDecimalTypeChannel(channelUID, newState.buttonevent);
case CHANNEL_BUTTONEVENT -> triggerChannel(channelUID, newState.buttonevent, initializing);
case CHANNEL_CARBONMONOXIDE -> updateSwitchChannel(channelUID, newState.carbonmonoxide);
case CHANNEL_COLOR -> {
final double @Nullable [] xy = newState.xy;
if (xy != null && xy.length == 2) {
updateState(channelUID, ColorUtil.xyToHsv(xy));
}
}
case CHANNEL_LIGHT_LEVEL -> updateDecimalTypeChannel(channelUID, newState.lightlevel);
case CHANNEL_CONSUMPTION -> updateQuantityTypeChannel(channelUID, newState.consumption, WATT_HOUR);
case CHANNEL_CURRENT -> updateQuantityTypeChannel(channelUID, newState.current, MILLI(AMPERE));
case CHANNEL_DARK -> updateSwitchChannel(channelUID, newState.dark);
case CHANNEL_DAYLIGHT -> updateSwitchChannel(channelUID, newState.daylight);
case CHANNEL_TEMPERATURE -> updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
case CHANNEL_FIRE -> updateSwitchChannel(channelUID, newState.fire);
case CHANNEL_GESTURE -> updateDecimalTypeChannel(channelUID, newState.gesture);
case CHANNEL_GESTUREEVENT -> triggerChannel(channelUID, newState.gesture, initializing);
case CHANNEL_HUMIDITY -> updateQuantityTypeChannel(channelUID, newState.humidity, PERCENT, 1.0 / 100);
case CHANNEL_PRESSURE -> updateQuantityTypeChannel(channelUID, newState.pressure, HECTO(PASCAL));
case CHANNEL_PRESENCE -> updateSwitchChannel(channelUID, newState.presence);
case CHANNEL_VALUE -> updateDecimalTypeChannel(channelUID, newState.status);
case CHANNEL_LIGHT -> updateStringChannel(channelUID, getLightState(newState));
case CHANNEL_LIGHT_LEVEL -> updateDecimalTypeChannel(channelUID, newState.lightlevel);
case CHANNEL_LIGHT_LUX -> updateQuantityTypeChannel(channelUID, newState.lux, LUX);
case CHANNEL_MOISTURE -> updateQuantityTypeChannel(channelUID, newState.moisture, PERCENT);
case CHANNEL_OPENCLOSE -> {
Boolean open = newState.open;
if (open != null) {
updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
}
}
case CHANNEL_WATERLEAKAGE -> updateSwitchChannel(channelUID, newState.water);
case CHANNEL_FIRE -> updateSwitchChannel(channelUID, newState.fire);
case CHANNEL_ALARM -> updateSwitchChannel(channelUID, newState.alarm);
case CHANNEL_ORIENTATION_X ->
updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[0] : null);
case CHANNEL_ORIENTATION_Y ->
updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[1] : null);
case CHANNEL_ORIENTATION_Z ->
updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[2] : null);
case CHANNEL_POWER -> updateQuantityTypeChannel(channelUID, newState.power, WATT);
case CHANNEL_PRESENCE -> updateSwitchChannel(channelUID, newState.presence);
case CHANNEL_PRESSURE -> updateQuantityTypeChannel(channelUID, newState.pressure, HECTO(PASCAL));
case CHANNEL_TAMPERED -> updateSwitchChannel(channelUID, newState.tampered);
case CHANNEL_TEMPERATURE -> updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
case CHANNEL_TILTANGLE -> updateQuantityTypeChannel(channelUID, newState.tiltangle, DEGREE_ANGLE);
case CHANNEL_VALUE -> updateDecimalTypeChannel(channelUID, newState.status);
case CHANNEL_VIBRATION -> updateSwitchChannel(channelUID, newState.vibration);
case CHANNEL_CARBONMONOXIDE -> updateSwitchChannel(channelUID, newState.carbonmonoxide);
case CHANNEL_AIRQUALITY -> updateStringChannel(channelUID, newState.airquality);
case CHANNEL_AIRQUALITYPPB ->
updateQuantityTypeChannel(channelUID, newState.airqualityppb, PARTS_PER_BILLION);
case CHANNEL_MOISTURE -> updateQuantityTypeChannel(channelUID, newState.moisture, PERCENT);
case CHANNEL_BUTTON -> updateDecimalTypeChannel(channelUID, newState.buttonevent);
case CHANNEL_BUTTONEVENT -> {
Integer buttonevent = newState.buttonevent;
if (buttonevent != null && !initializing) {
triggerChannel(channelUID, String.valueOf(buttonevent));
}
}
case CHANNEL_GESTURE -> updateDecimalTypeChannel(channelUID, newState.gesture);
case CHANNEL_GESTUREEVENT -> {
Integer gesture = newState.gesture;
if (gesture != null && !initializing) {
triggerChannel(channelUID, String.valueOf(gesture));
}
}
case CHANNEL_VIBRATION_STRENGTH -> updateDecimalTypeChannel(channelUID, newState.vibrationstrength);
case CHANNEL_VOLTAGE -> updateQuantityTypeChannel(channelUID, newState.voltage, VOLT);
case CHANNEL_WATERLEAKAGE -> updateSwitchChannel(channelUID, newState.water);
}
}
@ -220,6 +201,26 @@ public class SensorThingHandler extends SensorBaseThingHandler {
thingEdited = true;
}
// vibration sensors
if (sensorState.tiltangle != null && createChannel(thingBuilder, CHANNEL_TILTANGLE, ChannelKind.STATE)) {
thingEdited = true;
}
if (sensorState.vibrationstrength != null
&& createChannel(thingBuilder, CHANNEL_VIBRATION_STRENGTH, ChannelKind.STATE)) {
thingEdited = true;
}
if (sensorState.orientation != null) {
if (createChannel(thingBuilder, CHANNEL_ORIENTATION_X, ChannelKind.STATE)) {
thingEdited = true;
}
if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Y, ChannelKind.STATE)) {
thingEdited = true;
}
if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Z, ChannelKind.STATE)) {
thingEdited = true;
}
}
return thingEdited;
}
@ -227,4 +228,36 @@ public class SensorThingHandler extends SensorBaseThingHandler {
protected List<String> getConfigChannels() {
return CONFIG_CHANNELS;
}
/**
* Determine the light state from a state message
*
* @param newState the {@link SensorState} message
* @return <code>Dark</code>, <code>Daylight</code>, <code>Sunset</code>
*/
private @Nullable String getLightState(SensorState newState) {
Boolean dark = newState.dark;
if (dark == null) {
return null;
}
Boolean daylight = newState.daylight;
if (dark) { // if it's dark, it's dark ;)
return "Dark";
} else if (daylight != null) { // if its not dark, it might be between darkness and daylight
if (daylight) {
return "Daylight";
} else {
return "Sunset";
}
} else { // if no daylight value is known, we assume !dark means daylight
return "Daylight";
}
}
private void triggerChannel(ChannelUID channelUID, @Nullable Integer value, boolean initializing) {
if (value == null || initializing) {
return;
}
triggerChannel(channelUID, String.valueOf(value));
}
}

View File

@ -174,6 +174,9 @@ channel-type.deconz.ontime.label = On Time
channel-type.deconz.ontime.description = Time that the light stays on before switched off automatically (0=forever)
channel-type.deconz.open.label = Open/Close
channel-type.deconz.open.description = Open/Close detected
channel-type.deconz.orientation_x.label = Orientation X
channel-type.deconz.orientation_y.label = Orientation Y
channel-type.deconz.orientation_z.label = Orientation Z
channel-type.deconz.position.label = Position
channel-type.deconz.power.label = Power
channel-type.deconz.power.description = Current power usage
@ -184,12 +187,15 @@ channel-type.deconz.tampered.label = Tampered
channel-type.deconz.tampered.description = A zone is being tampered.
channel-type.deconz.temperature.label = Temperature
channel-type.deconz.temperature.description = Current temperature
channel-type.deconz.tiltangle.label = Tilt Angle
channel-type.deconz.value.label = Daylight Value
channel-type.deconz.value.description = Dawn is around 130, sunrise at 140, sunset at 190, and dusk at 210
channel-type.deconz.valve.label = Valve position
channel-type.deconz.valve.description = Current valve position
channel-type.deconz.vibration.label = Vibration
channel-type.deconz.vibration.description = Vibration was detected.
channel-type.deconz.vibrationstrength.label = Vibrationstrength
channel-type.deconz.vibrationstrength.description = Vibration strength, value is device-dependent.
channel-type.deconz.voltage.label = Voltage
channel-type.deconz.voltage.description = Current voltage
channel-type.deconz.waterleakage.label = Water Leakage

View File

@ -0,0 +1,329 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="deconz"
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">
<channel-type id="airquality">
<item-type>String</item-type>
<label>Air Quality</label>
<description>Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor,
...</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="airqualityppb">
<item-type>Number:Dimensionless</item-type>
<label>Air Quality (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"/>
</channel-type>
<channel-type id="alarm">
<item-type>Switch</item-type>
<label>Alarm</label>
<description>Alarm was triggered.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="button">
<item-type>Number</item-type>
<label>Button</label>
<description>The Button that was last pressed on the switch.</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="buttonevent">
<kind>Trigger</kind>
<label>Button Trigger</label>
<description>This channel is triggered on a button event. The trigger payload consists of the button event number.
</description>
<event></event>
</channel-type>
<channel-type id="carbonmonoxide">
<item-type>Switch</item-type>
<label>Carbon-monoxide</label>
<description>Carbon-monoxide was detected.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="consumption">
<item-type>Number:Energy</item-type>
<label>Consumption</label>
<description>Current consumption</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="current">
<item-type>Number:ElectricCurrent</item-type>
<label>Current</label>
<description>Current current</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="dark">
<item-type>Switch</item-type>
<label>Dark</label>
<description>Light level is below the darkness threshold.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="daylight">
<item-type>Switch</item-type>
<label>Daylight</label>
<description>Light level is above the daylight threshold.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="externalwindowopen">
<item-type>Contact</item-type>
<label>External Window Open</label>
</channel-type>
<channel-type id="fire">
<item-type>Switch</item-type>
<label>Fire</label>
<description>A fire was detected.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="gesture">
<item-type>Number</item-type>
<label>Gesture</label>
<description>A gesture that was performed with the switch.</description>
<state readOnly="true" pattern="%d">
<options>
<option value="0">None</option>
<option value="1">Shake</option>
<option value="2">Drop</option>
<option value="3">Flip 90</option>
<option value="4">Flip 180</option>
<option value="5">Push</option>
<option value="6">Double Tap</option>
<option value="7">Rotate Clockwise</option>
<option value="8">Rotate Counter Clockwise</option>
</options>
</state>
</channel-type>
<channel-type id="gestureevent">
<kind>Trigger</kind>
<label>Gesture Trigger</label>
<description>This channel is triggered on a gesture event. The trigger payload consists of the gesture event number.</description>
<event></event>
</channel-type>
<channel-type id="heatsetpoint">
<item-type>Number:Temperature</item-type>
<label>Target Temperature</label>
<description>Target temperature</description>
<category>Heating</category>
<state pattern="%.1f %unit%" step="0.5" max="28" min="6"/>
</channel-type>
<channel-type id="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>Current humidity</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="last_seen">
<item-type>DateTime</item-type>
<label>Last Seen</label>
<description>The date and time when the sensor was last seen.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="last_updated">
<item-type>DateTime</item-type>
<label>Last Updated</label>
<description>The date and time when the sensor was last updated.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="light">
<item-type>String</item-type>
<label>Lightlevel</label>
<state readOnly="true">
<options>
<option value="daylight">Daylight</option>
<option value="sunset">Sunset</option>
<option value="dark">Dark</option>
</options>
</state>
</channel-type>
<channel-type id="lightlux">
<item-type>Number:Illuminance</item-type>
<label>Illuminance</label>
<description>Current light illuminance</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="light_level" advanced="true">
<item-type>Number</item-type>
<label>Light Level</label>
<description>Current light level.</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="locked">
<item-type>Switch</item-type>
<label>Locked</label>
<description>Status of this thermostat's child lock.</description>
<category>Lock</category>
</channel-type>
<channel-type id="mode">
<item-type>String</item-type>
<label>Mode</label>
<description>Current mode</description>
<category>Heating</category>
<state>
<options>
<option value="AUTO">auto</option>
<option value="HEAT">heat</option>
<option value="OFF">off</option>
</options>
</state>
</channel-type>
<channel-type id="moisture">
<item-type>Number:Dimensionless</item-type>
<label>Moisture</label>
<description>Current moisture</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="offset">
<item-type>Number:Temperature</item-type>
<label>Offset</label>
<description>Temperature offset</description>
<state pattern="%.2f %unit%" step="0.01"/>
</channel-type>
<channel-type id="on">
<item-type>Switch</item-type>
<label>Heater State</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="open">
<item-type>Contact</item-type>
<label>Open/Close</label>
<description>Open/Close detected</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="orientation_x">
<item-type>Number</item-type>
<label>Orientation X</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="orientation_y">
<item-type>Number</item-type>
<label>Orientation Y</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="orientation_z">
<item-type>Number</item-type>
<label>Orientation Z</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Current power usage</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="pressure">
<item-type>Number:Pressure</item-type>
<label>Pressure</label>
<description>Current pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="tampered">
<item-type>Switch</item-type>
<label>Tampered</label>
<description>A zone is being tampered.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="tiltangle">
<item-type>Number:Angle</item-type>
<label>Tilt Angle</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="value">
<item-type>Number</item-type>
<label>Daylight Value</label>
<description>Dawn is around 130, sunrise at 140, sunset at 190, and dusk at 210</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="valve">
<item-type>Number:Dimensionless</item-type>
<label>Valve position</label>
<description>Current valve position</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="vibration">
<item-type>Switch</item-type>
<label>Vibration</label>
<description>Vibration was detected.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="vibrationstrength">
<item-type>Number</item-type>
<label>Vibrationstrength</label>
<description>Vibration strength, value is device-dependent.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Current voltage</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="waterleakage">
<item-type>Switch</item-type>
<label>Water Leakage</label>
<description>Water leakage detected</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="windowopen">
<item-type>Contact</item-type>
<label>Window Open</label>
</channel-type>
</thing:thing-descriptions>

View File

@ -4,104 +4,64 @@
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="presencesensor">
<!-- thing types -->
<thing-type id="airqualitysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Presence Sensor</label>
<label>Air Quality Sensor</label>
<channels>
<channel typeId="system.motion" id="presence"/>
<channel typeId="airquality" id="airquality"/>
<channel typeId="airqualityppb" id="airqualityppb"/>
<channel typeId="last_updated" id="last_updated"/>
<channel typeId="system.power" id="enabled"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="alarmsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Alarm Sensor</label>
<channels>
<channel typeId="alarm" id="alarm"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="carbonmonoxidesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Carbon-monoxide Sensor</label>
<channels>
<channel typeId="carbonmonoxide" id="carbonmonoxide"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="batterysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Battery Sensor</label>
<channels>
<channel typeId="system.battery-level" id="battery_level"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<channel-type id="last_updated">
<item-type>DateTime</item-type>
<label>Last Updated</label>
<description>The date and time when the sensor was last updated.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="last_seen">
<item-type>DateTime</item-type>
<label>Last Seen</label>
<description>The date and time when the sensor was last seen.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<thing-type id="powersensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Power Sensor</label>
<channels>
<channel typeId="power" id="power"/>
<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="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Current power usage</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Current voltage</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="current">
<item-type>Number:ElectricCurrent</item-type>
<label>Current</label>
<description>Current current</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<thing-type id="consumptionsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Consumption Sensor</label>
<channels>
<channel typeId="consumption" id="consumption"/>
<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="consumption">
<item-type>Number:Energy</item-type>
<label>Consumption</label>
<description>Current consumption</description>
<category>Energy</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<thing-type id="colorcontrol">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
@ -113,72 +73,64 @@
<channel typeId="button" id="button"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="switch">
<thing-type id="consumptionsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Switch/Button</label>
<label>Consumption Sensor</label>
<channels>
<channel typeId="buttonevent" id="buttonevent"/>
<channel typeId="button" id="button"/>
<channel typeId="consumption" id="consumption"/>
<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="buttonevent">
<kind>Trigger</kind>
<label>Button Trigger</label>
<description>This channel is triggered on a button event. The trigger payload consists of the button event number.
</description>
<event></event>
</channel-type>
<thing-type id="daylightsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Daylight Sensor</label>
<channels>
<channel typeId="value" id="value"/>
<channel typeId="light" id="light"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<channel-type id="button">
<item-type>Number</item-type>
<label>Button</label>
<description>The Button that was last pressed on the switch.</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<thing-type id="firesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Fire Sensor</label>
<channels>
<channel typeId="fire" id="fire"/>
<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="gestureevent">
<kind>Trigger</kind>
<label>Gesture Trigger</label>
<description>This channel is triggered on a gesture event. The trigger payload consists of the gesture event number.</description>
<event></event>
</channel-type>
<channel-type id="gesture">
<item-type>Number</item-type>
<label>Gesture</label>
<description>A gesture that was performed with the switch.</description>
<state readOnly="true" pattern="%d">
<options>
<option value="0">None</option>
<option value="1">Shake</option>
<option value="2">Drop</option>
<option value="3">Flip 90</option>
<option value="4">Flip 180</option>
<option value="5">Push</option>
<option value="6">Double Tap</option>
<option value="7">Rotate Clockwise</option>
<option value="8">Rotate Counter Clockwise</option>
</options>
</state>
</channel-type>
<thing-type id="humiditysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Humidity Sensor</label>
<channels>
<channel typeId="humidity" id="humidity"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="lightsensor">
<supported-bridge-type-refs>
@ -192,333 +144,10 @@
<channel typeId="daylight" id="daylight"/>
<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="lightlux">
<item-type>Number:Illuminance</item-type>
<label>Illuminance</label>
<description>Current light illuminance</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="light_level" advanced="true">
<item-type>Number</item-type>
<label>Light Level</label>
<description>Current light level.</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="dark">
<item-type>Switch</item-type>
<label>Dark</label>
<description>Light level is below the darkness threshold.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="daylight">
<item-type>Switch</item-type>
<label>Daylight</label>
<description>Light level is above the daylight threshold.</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="temperaturesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Temperature Sensor</label>
<channels>
<channel typeId="temperature" id="temperature"/>
<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="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<thing-type id="humiditysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Humidity Sensor</label>
<channels>
<channel typeId="humidity" id="humidity"/>
<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="humidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>Current humidity</description>
<category>Humidity</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<thing-type id="pressuresensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Pressure Sensor</label>
<channels>
<channel typeId="pressure" id="pressure"/>
<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="pressure">
<item-type>Number:Pressure</item-type>
<label>Pressure</label>
<description>Current pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<thing-type id="daylightsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Daylight Sensor</label>
<channels>
<channel typeId="value" id="value"/>
<channel typeId="light" id="light"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<channel-type id="value">
<item-type>Number</item-type>
<label>Daylight Value</label>
<description>Dawn is around 130, sunrise at 140, sunset at 190, and dusk at 210</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="light">
<item-type>String</item-type>
<label>Lightlevel</label>
<state readOnly="true">
<options>
<option value="daylight">Daylight</option>
<option value="sunset">Sunset</option>
<option value="dark">Dark</option>
</options>
</state>
</channel-type>
<thing-type id="openclosesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Open/Close Sensor</label>
<channels>
<channel typeId="open" id="open"/>
<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="open">
<item-type>Contact</item-type>
<label>Open/Close</label>
<description>Open/Close detected</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="waterleakagesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Water Leakage Sensor</label>
<channels>
<channel typeId="waterleakage" id="waterleakage"/>
<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="waterleakage">
<item-type>Switch</item-type>
<label>Water Leakage</label>
<description>Water leakage detected</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="firesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Fire Sensor</label>
<channels>
<channel typeId="fire" id="fire"/>
<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="fire">
<item-type>Switch</item-type>
<label>Fire</label>
<description>A fire was detected.</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="alarmsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Alarm Sensor</label>
<channels>
<channel typeId="alarm" id="alarm"/>
<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="alarm">
<item-type>Switch</item-type>
<label>Alarm</label>
<description>Alarm was triggered.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="tampered">
<item-type>Switch</item-type>
<label>Tampered</label>
<description>A zone is being tampered.</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="vibrationsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Vibration Sensor</label>
<channels>
<channel typeId="vibration" id="vibration"/>
<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="vibration">
<item-type>Switch</item-type>
<label>Vibration</label>
<description>Vibration was detected.</description>
<state readOnly="true"/>
</channel-type>
<thing-type id="batterysensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Battery Sensor</label>
<channels>
<channel typeId="system.battery-level" id="battery_level"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="carbonmonoxidesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Carbon-monoxide Sensor</label>
<channels>
<channel typeId="carbonmonoxide" id="carbonmonoxide"/>
<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="carbonmonoxide">
<item-type>Switch</item-type>
<label>Carbon-monoxide</label>
<description>Carbon-monoxide was detected.</description>
<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>
<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</label>
<description>Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor,
...</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="airqualityppb">
<item-type>Number:Dimensionless</item-type>
<label>Air Quality (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"/>
</channel-type>
<thing-type id="moisturesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
@ -528,18 +157,92 @@
<channel typeId="moisture" id="moisture"/>
<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="moisture">
<item-type>Number:Dimensionless</item-type>
<label>Moisture</label>
<description>Current moisture</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<thing-type id="powersensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Power Sensor</label>
<channels>
<channel typeId="power" id="power"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="openclosesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Open/Close Sensor</label>
<channels>
<channel typeId="open" id="open"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="presencesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Presence Sensor</label>
<channels>
<channel typeId="system.motion" id="presence"/>
<channel typeId="last_updated" id="last_updated"/>
<channel typeId="system.power" id="enabled"/>
</channels>
<properties>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="pressuresensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Pressure Sensor</label>
<channels>
<channel typeId="pressure" id="pressure"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="switch">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Switch/Button</label>
<channels>
<channel typeId="buttonevent" id="buttonevent"/>
<channel typeId="button" id="button"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="temperaturesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Temperature Sensor</label>
<channels>
<channel typeId="temperature" id="temperature"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="thermostat">
<supported-bridge-type-refs>
@ -558,56 +261,30 @@
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<channel-type id="locked">
<item-type>Switch</item-type>
<label>Locked</label>
<description>Status of this thermostat's child lock.</description>
<category>Lock</category>
</channel-type>
<channel-type id="windowopen">
<item-type>Contact</item-type>
<label>Window Open</label>
</channel-type>
<channel-type id="externalwindowopen">
<item-type>Contact</item-type>
<label>External Window Open</label>
</channel-type>
<channel-type id="heatsetpoint">
<item-type>Number:Temperature</item-type>
<label>Target Temperature</label>
<description>Target temperature</description>
<category>Heating</category>
<state pattern="%.1f %unit%" step="0.5" max="28" min="6"/>
</channel-type>
<channel-type id="mode">
<item-type>String</item-type>
<label>Mode</label>
<description>Current mode</description>
<category>Heating</category>
<state>
<options>
<option value="AUTO">auto</option>
<option value="HEAT">heat</option>
<option value="OFF">off</option>
</options>
</state>
</channel-type>
<channel-type id="offset">
<item-type>Number:Temperature</item-type>
<label>Offset</label>
<description>Temperature offset</description>
<state pattern="%.2f %unit%" step="0.01"/>
</channel-type>
<channel-type id="valve">
<item-type>Number:Dimensionless</item-type>
<label>Valve position</label>
<description>Current valve position</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="on">
<item-type>Switch</item-type>
<label>Heater State</label>
<state readOnly="true"/>
</channel-type>
<thing-type id="vibrationsensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Vibration Sensor</label>
<channels>
<channel typeId="vibration" id="vibration"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
<thing-type id="waterleakagesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="deconz"/>
</supported-bridge-type-refs>
<label>Water Leakage Sensor</label>
<channels>
<channel typeId="waterleakage" id="waterleakage"/>
<channel typeId="last_updated" id="last_updated"/>
</channels>
<representation-property>uid</representation-property>
<config-description-ref uri="thing-type:deconz:sensor"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -1,147 +0,0 @@
/**
* Copyright (c) 2010-2023 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.deconz;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.openhab.binding.deconz.internal.BindingConstants.*;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.openhab.binding.deconz.internal.handler.SensorThingHandler;
import org.openhab.binding.deconz.internal.types.LightType;
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
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.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* This class provides tests for deconz sensors
*
* @author Jan N. Klug - Initial contribution
* @author Lukas Agethen - Added Thermostat
* @author Philipp Schneider - Added air quality sensor
*/
@ExtendWith(MockitoExtension.class)
@NonNullByDefault
public class SensorsTest {
private @NonNullByDefault({}) Gson gson;
private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
@BeforeEach
public void initialize() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
gson = gsonBuilder.create();
}
@Test
public void carbonmonoxideSensorUpdateTest() throws IOException {
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("carbonmonoxide.json", SensorMessage.class, gson);
assertNotNull(sensorMessage);
ThingUID thingUID = new ThingUID("deconz", "sensor");
ChannelUID channelUID = new ChannelUID(thingUID, "carbonmonoxide");
Thing sensor = ThingBuilder.create(THING_TYPE_CARBONMONOXIDE_SENSOR, thingUID)
.withChannel(ChannelBuilder.create(channelUID, "Switch").build()).build();
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
sensorThingHandler.setCallback(thingHandlerCallback);
sensorThingHandler.messageReceived(sensorMessage);
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 QuantityType<>("129 ppb")));
}
@Test
public void fireSensorUpdateTest() throws IOException {
SensorMessage sensorMessage = DeconzTest.getObjectFromJson("fire.json", SensorMessage.class, gson);
assertNotNull(sensorMessage);
ThingUID thingUID = new ThingUID("deconz", "sensor");
ChannelUID channelBatteryLevelUID = new ChannelUID(thingUID, CHANNEL_BATTERY_LEVEL);
ChannelUID channelFireUID = new ChannelUID(thingUID, CHANNEL_FIRE);
ChannelUID channelTamperedUID = new ChannelUID(thingUID, CHANNEL_TAMPERED);
ChannelUID channelLastSeenUID = new ChannelUID(thingUID, CHANNEL_LAST_SEEN);
Thing sensor = ThingBuilder.create(THING_TYPE_FIRE_SENSOR, thingUID)
.withChannel(ChannelBuilder.create(channelBatteryLevelUID, "Number").build())
.withChannel(ChannelBuilder.create(channelFireUID, "Switch").build())
.withChannel(ChannelBuilder.create(channelTamperedUID, "Switch").build())
.withChannel(ChannelBuilder.create(channelLastSeenUID, "DateTime").build()).build();
SensorThingHandler sensorThingHandler = new SensorThingHandler(sensor, gson);
sensorThingHandler.setCallback(thingHandlerCallback);
sensorThingHandler.messageReceived(sensorMessage);
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelFireUID), eq(OnOffType.OFF));
Mockito.verify(thingHandlerCallback).stateUpdated(eq(channelBatteryLevelUID), eq(new DecimalType(98)));
}
}

View File

@ -0,0 +1,170 @@
/**
* Copyright (c) 2010-2023 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.deconz.internal.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.openhab.binding.deconz.internal.BindingConstants.BINDING_ID;
import static org.openhab.binding.deconz.internal.BindingConstants.BRIDGE_TYPE;
import static org.openhab.binding.deconz.internal.BindingConstants.CONFIG_ID;
import static org.openhab.binding.deconz.internal.BindingConstants.THING_TYPE_THERMOSTAT;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.openhab.binding.deconz.DeconzTest;
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
import org.openhab.binding.deconz.internal.types.LightType;
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.openhab.binding.deconz.internal.types.ThermostatMode;
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.State;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link BaseDeconzThingHandlerTest} is the base class for test classes that are used to test subclasses of
* {@link DeconzBaseThingHandler}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class BaseDeconzThingHandlerTest extends JavaTest {
private static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE, "bridge");
private static final ThingUID THING_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thing");
protected @NonNullByDefault({}) DeconzBaseMessage deconzMessage;
private @Mock @NonNullByDefault({}) Bridge bridge;
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
private @Mock @NonNullByDefault({}) DeconzBridgeHandler bridgeHandler;
private @Mock @NonNullByDefault({}) WebSocketConnection webSocketConnection;
private @Mock @NonNullByDefault({}) BridgeFullState bridgeFullState;
private @NonNullByDefault({}) Gson gson;
private @NonNullByDefault({}) Thing thing;
private @NonNullByDefault({}) DeconzBaseThingHandler thingHandler;
@BeforeEach
public void setupMocks() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
gson = gsonBuilder.create();
when(callback.getBridge(BRIDGE_UID)).thenReturn(bridge);
when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
.thenAnswer(i -> ChannelBuilder.create((ChannelUID) i.getArgument(0)).withType(i.getArgument(1)));
doAnswer(i -> {
thing = i.getArgument(0);
thingHandler.thingUpdated(thing);
return null;
}).when(callback).thingUpdated(any(Thing.class));
when(bridge.getStatusInfo()).thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
when(bridge.getHandler()).thenReturn(bridgeHandler);
when(bridgeHandler.getWebSocketConnection()).thenReturn(webSocketConnection);
when(bridgeHandler.getBridgeFullState())
.thenReturn(CompletableFuture.completedFuture(Optional.of(bridgeFullState)));
when(bridgeFullState.getMessage(ResourceType.SENSORS, "1")).thenAnswer(i -> deconzMessage);
}
protected void createThing(ThingTypeUID thingTypeUID, List<String> channels,
BiFunction<Thing, Gson, DeconzBaseThingHandler> handlerSupplier) {
ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, THING_UID);
thingBuilder.withBridge(BRIDGE_UID);
for (String channelId : channels) {
Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, channelId))
.withType(new ChannelTypeUID(BINDING_ID, channelId)).build();
thingBuilder.withChannel(channel);
}
thingBuilder.withConfiguration(new Configuration(Map.of(CONFIG_ID, "1")));
thing = thingBuilder.build();
thingHandler = handlerSupplier.apply(thing, gson);
thingHandler.setCallback(callback);
}
protected void assertThing(String fileName, Set<TestParam> expected) throws IOException {
deconzMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson);
thingHandler.initialize();
ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
verify(callback, atLeast(2).description("assertQuantityOfStatusUpdates")).statusUpdated(eq(thing),
captor.capture());
List<ThingStatusInfo> statusInfoList = captor.getAllValues();
assertThat("assertFirstThingStatus", statusInfoList.get(0).getStatus(), is(ThingStatus.UNKNOWN));
assertThat("assertLastThingStatus", statusInfoList.get(statusInfoList.size() - 1).getStatus(),
is(ThingStatus.ONLINE));
assertThat("assertChannelCount:" + getAllChannels(thing), thing.getChannels().size(), is(expected.size()));
for (TestParam testParam : expected) {
Channel channel = thing.getChannel(testParam.channelId());
assertThat("assertNonNullChannel:" + testParam.channelId, channel, is(notNullValue()));
State state = testParam.state;
if (channel != null && state != null) {
verify(callback, times(3).description(channel + " did not receive an update"))
.stateUpdated(eq(channel.getUID()), eq(state));
}
}
}
private String getAllChannels(Thing thing) {
return thing.getChannels().stream().map(Channel::getUID).map(ChannelUID::getId)
.collect(Collectors.joining(","));
}
protected record TestParam(String channelId, @Nullable State state) {
}
}

View File

@ -12,69 +12,26 @@
*/
package org.openhab.binding.deconz.internal.handler;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.openhab.binding.deconz.internal.BindingConstants.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.binding.deconz.DeconzTest;
import org.openhab.binding.deconz.internal.Util;
import org.openhab.binding.deconz.internal.dto.BridgeFullState;
import org.openhab.binding.deconz.internal.dto.SensorMessage;
import org.openhab.binding.deconz.internal.netutils.WebSocketConnection;
import org.openhab.binding.deconz.internal.types.LightType;
import org.openhab.binding.deconz.internal.types.LightTypeDeserializer;
import org.openhab.binding.deconz.internal.types.ResourceType;
import org.openhab.binding.deconz.internal.types.ThermostatMode;
import org.openhab.binding.deconz.internal.types.ThermostatModeGsonTypeAdapter;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.test.java.JavaTest;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.ThingStatusInfo;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link SensorThermostatThingHandlerTest} contains test classes for the {@link SensorThermostatThingHandler}
*
@ -83,72 +40,20 @@ import com.google.gson.GsonBuilder;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class SensorThermostatThingHandlerTest extends JavaTest {
private static final ThingUID BRIDGE_UID = new ThingUID(BRIDGE_TYPE, "bridge");
private static final ThingUID THING_UID = new ThingUID(THING_TYPE_THERMOSTAT, "thing");
private @Mock @NonNullByDefault({}) Bridge bridge;
private @Mock @NonNullByDefault({}) ThingHandlerCallback callback;
private @Mock @NonNullByDefault({}) DeconzBridgeHandler bridgeHandler;
private @Mock @NonNullByDefault({}) WebSocketConnection webSocketConnection;
private @Mock @NonNullByDefault({}) BridgeFullState bridgeFullState;
private @NonNullByDefault({}) Gson gson;
private @NonNullByDefault({}) Thing thing;
private @NonNullByDefault({}) SensorThermostatThingHandler thingHandler;
private @NonNullByDefault({}) SensorMessage sensorMessage;
@BeforeEach
public void setup() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
gsonBuilder.registerTypeAdapter(ThermostatMode.class, new ThermostatModeGsonTypeAdapter());
gson = gsonBuilder.create();
ThingBuilder thingBuilder = ThingBuilder.create(THING_TYPE_THERMOSTAT, THING_UID);
thingBuilder.withBridge(BRIDGE_UID);
for (String channelId : List.of(CHANNEL_TEMPERATURE, CHANNEL_HEATSETPOINT, CHANNEL_THERMOSTAT_MODE,
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_LAST_UPDATED)) {
Channel channel = ChannelBuilder.create(new ChannelUID(THING_UID, channelId))
.withType(new ChannelTypeUID(BINDING_ID, channelId)).build();
thingBuilder.withChannel(channel);
}
thingBuilder.withConfiguration(new Configuration(Map.of(CONFIG_ID, "1")));
thing = thingBuilder.build();
thingHandler = new SensorThermostatThingHandler(thing, gson);
thingHandler.setCallback(callback);
when(callback.getBridge(BRIDGE_UID)).thenReturn(bridge);
when(callback.createChannelBuilder(any(ChannelUID.class), any(ChannelTypeUID.class)))
.thenAnswer(i -> ChannelBuilder.create((ChannelUID) i.getArgument(0)).withType(i.getArgument(1)));
doAnswer(i -> {
thing = i.getArgument(0);
thingHandler.thingUpdated(thing);
return null;
}).when(callback).thingUpdated(any(Thing.class));
when(bridge.getStatusInfo()).thenReturn(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
when(bridge.getHandler()).thenReturn(bridgeHandler);
when(bridgeHandler.getWebSocketConnection()).thenReturn(webSocketConnection);
when(bridgeHandler.getBridgeFullState())
.thenReturn(CompletableFuture.completedFuture(Optional.of(bridgeFullState)));
when(bridgeFullState.getMessage(ResourceType.SENSORS, "1")).thenAnswer(i -> sensorMessage);
}
public class SensorThermostatThingHandlerTest extends BaseDeconzThingHandlerTest {
@Test
public void testDanfoss() throws IOException {
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("21.45 °C")),
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("21.00 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("HEAT")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2023-03-18T05:52:29.506")),
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("21.45 °C")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("HEAT")),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(41)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
@ -156,99 +61,80 @@ public class SensorThermostatThingHandlerTest extends JavaTest {
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2023-03-18T05:58Z")),
// dynamic channels
new TestParam(CHANNEL_EXTERNAL_WINDOW_OPEN, OpenClosedType.CLOSED),
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("1 %")),
new TestParam(CHANNEL_THERMOSTAT_LOCKED, OnOffType.OFF),
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.OFF),
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("1 %")),
new TestParam(CHANNEL_WINDOW_OPEN, OpenClosedType.CLOSED));
assertThermostat("json/thermostat/danfoss.json", expected);
assertThing("json/thermostat/danfoss.json", expected);
}
@Test
public void testNamron() throws IOException {
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_TEMPERATURE, CHANNEL_HEATSETPOINT, CHANNEL_THERMOSTAT_MODE,
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_LAST_UPDATED), SensorThermostatThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("20.39 °C")),
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("22.00 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("OFF")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2023-03-18T18:10:39.296")),
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("20.39 °C")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("OFF")),
// last seen
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2023-03-18T18:10Z")),
// dynamic channels
new TestParam(CHANNEL_THERMOSTAT_LOCKED, OnOffType.OFF),
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.OFF));
assertThermostat("json/thermostat/namron_ZB_E1.json", expected);
assertThing("json/thermostat/namron_ZB_E1.json", expected);
}
@Test
public void testEurotronicValid() throws IOException {
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("25.00 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(85)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
// last seen
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
// dynamic channels
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("99 %")),
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON));
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON),
new TestParam(CHANNEL_VALVE_POSITION, new QuantityType<>("99 %")));
assertThermostat("json/thermostat/eurotronic.json", expected);
assertThing("json/thermostat/eurotronic.json", expected);
}
@Test
public void testEurotronicInvalid() throws IOException {
createThing(THING_TYPE_THERMOSTAT, List.of(CHANNEL_HEATSETPOINT, CHANNEL_LAST_UPDATED, CHANNEL_TEMPERATURE,
CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE), SensorThermostatThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
new TestParam(CHANNEL_HEATSETPOINT, new QuantityType<>("25.00 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("16.50 °C")),
new TestParam(CHANNEL_TEMPERATURE_OFFSET, new QuantityType<>("0.0 °C")),
new TestParam(CHANNEL_THERMOSTAT_MODE, new StringType("AUTO")),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(85)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
// last seen
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2020-05-31T20:24:55.819")),
// dynamic channels
new TestParam(CHANNEL_VALVE_POSITION, UnDefType.UNDEF),
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON));
new TestParam(CHANNEL_THERMOSTAT_ON, OnOffType.ON),
new TestParam(CHANNEL_VALVE_POSITION, UnDefType.UNDEF));
assertThermostat("json/thermostat/eurotronic-invalid.json", expected);
}
private void assertThermostat(String fileName, Set<TestParam> expected) throws IOException {
sensorMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson);
thingHandler.initialize();
ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
verify(callback, times(6)).statusUpdated(eq(thing), captor.capture());
List<ThingStatusInfo> statusInfoList = captor.getAllValues();
assertThat(statusInfoList.get(0).getStatus(), is(ThingStatus.UNKNOWN));
assertThat(statusInfoList.get(5).getStatus(), is(ThingStatus.ONLINE));
assertThat(thing.getChannels().size(), is(expected.size()));
for (TestParam testParam : expected) {
Channel channel = thing.getChannel(testParam.channelId());
assertThat(channel + "expected but missing", channel, is(notNullValue()));
State state = testParam.state;
if (state != null) {
verify(callback, times(3).description(channel + " did not receive an update"))
.stateUpdated(eq(channel.getUID()), eq(state));
}
}
}
private record TestParam(String channelId, @Nullable State state) {
assertThing("json/thermostat/eurotronic-invalid.json", expected);
}
}

View File

@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2023 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.deconz.internal.handler;
import static org.openhab.binding.deconz.internal.BindingConstants.*;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.binding.deconz.internal.Util;
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.types.UnDefType;
/**
* The {@link SensorThingHandlerTest} contains test classes for the {@link SensorThingHandler}
*
* @author Jan N. Klug - Initial contribution
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@NonNullByDefault
public class SensorThingHandlerTest extends BaseDeconzThingHandlerTest {
@Test
public void testAirQuality() throws IOException {
createThing(THING_TYPE_AIRQUALITY_SENSOR,
List.of(CHANNEL_AIRQUALITY, CHANNEL_AIRQUALITYPPB, CHANNEL_LAST_UPDATED), SensorThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_AIRQUALITY, new StringType("good")),
new TestParam(CHANNEL_AIRQUALITYPPB, new QuantityType<>("129 ppb")),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2021-12-29T01:18:41.184")),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
// last seen
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2021-12-29T01:18Z")));
assertThing("json/sensors/airquality.json", expected);
}
@Test
public void testCarbonMonoxide() throws IOException {
createThing(THING_TYPE_CARBONMONOXIDE_SENSOR, List.of(CHANNEL_CARBONMONOXIDE, CHANNEL_LAST_UPDATED),
SensorThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_CARBONMONOXIDE, OnOffType.ON),
new TestParam(CHANNEL_LAST_UPDATED, UnDefType.UNDEF),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF));
assertThing("json/sensors/carbonmonoxide.json", expected);
}
@Test
public void testFire() throws IOException {
createThing(THING_TYPE_FIRE_SENSOR, List.of(CHANNEL_FIRE, CHANNEL_LAST_UPDATED), SensorThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_FIRE, OnOffType.OFF), new TestParam(CHANNEL_LAST_UPDATED, UnDefType.UNDEF),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(98)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF));
assertThing("json/sensors/fire.json", expected);
}
@Test
public void testSwitch() throws IOException {
createThing(THING_TYPE_SWITCH, List.of(CHANNEL_BUTTON, CHANNEL_BUTTONEVENT, CHANNEL_LAST_UPDATED),
SensorThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_BUTTON, new DecimalType(1002)), new TestParam(CHANNEL_BUTTONEVENT, null),
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2022-02-15T13:36:16.271")),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(7)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.ON));
assertThing("json/sensors/switch.json", expected);
}
@Test
public void testVibration() throws IOException {
createThing(THING_TYPE_VIBRATION_SENSOR, List.of(CHANNEL_LAST_UPDATED, CHANNEL_VIBRATION),
SensorThingHandler::new);
Set<TestParam> expected = Set.of(
// standard channels
new TestParam(CHANNEL_LAST_UPDATED, Util.convertTimestampToDateTime("2022-09-09T18:13:44.653")),
new TestParam(CHANNEL_VIBRATION, OnOffType.ON),
// battery
new TestParam(CHANNEL_BATTERY_LEVEL, new DecimalType(100)),
new TestParam(CHANNEL_BATTERY_LOW, OnOffType.OFF),
// last seen
new TestParam(CHANNEL_LAST_SEEN, Util.convertTimestampToDateTime("2022-09-09T18:13Z")),
// dynamic channels
new TestParam(CHANNEL_ORIENTATION_X, new DecimalType(3)),
new TestParam(CHANNEL_ORIENTATION_Y, new DecimalType(-1)),
new TestParam(CHANNEL_ORIENTATION_Z, new DecimalType(-87)),
new TestParam(CHANNEL_TEMPERATURE, new QuantityType<>("26.00 °C")),
new TestParam(CHANNEL_TILTANGLE, new QuantityType<>("176 °")),
new TestParam(CHANNEL_VIBRATION_STRENGTH, new DecimalType(110)));
assertThing("json/sensors/vibration.json", expected);
}
}

View File

@ -0,0 +1,24 @@
{
"config": {
"alert": "none",
"battery": 7,
"group": "8",
"on": true,
"reachable": true
},
"ep": 1,
"etag": "43c4c267ebf1239c94791be02d3927c8",
"lastannounced": null,
"lastseen": null,
"manufacturername": "IKEA of Sweden",
"mode": 3,
"modelid": "TRADFRI remote control",
"name": "TRÅDFR Laura",
"state": {
"buttonevent": 1002,
"lastupdated": "2022-02-15T13:36:16.271"
},
"swversion": "2.3.014",
"type": "ZHASwitch",
"uniqueid": "90:fd:9f:ff:fe:17:75:a3-01-1000"
}

View File

@ -0,0 +1,32 @@
{
"config": {
"battery": 100,
"on": true,
"pending": [],
"reachable": true,
"sensitivity": null,
"sensitivitymax": 21,
"temperature": 2600
},
"ep": 1,
"etag": "6c0452972eb8183b5604899c66a3e32f",
"lastannounced": null,
"lastseen": "2022-09-09T18:13Z",
"manufacturername": "LUMI",
"modelid": "lumi.vibration.aq1",
"name": "Vibration Sensor",
"state": {
"lastupdated": "2022-09-09T18:13:44.653",
"orientation": [
3,
-1,
-87
],
"tiltangle": 176,
"vibration": true,
"vibrationstrength": 110
},
"swversion": "20180130",
"type": "ZHAVibration",
"uniqueid": "00:15:8d:00:08:52:a6:2c-01-0101"
}