From 1786bb0eecd1602fdb035e5beb82b8276c49247e Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 21 Mar 2023 22:46:53 +0100 Subject: [PATCH] [deconz] Cleanup code and improve tests, edit channels to vibration sensor (#14641) * [deconz] Cleanup code and improve tests Signed-off-by: Jan N. Klug --- bundles/org.openhab.binding.deconz/README.md | 79 +- .../deconz/internal/BindingConstants.java | 78 +- .../deconz/internal/dto/SensorState.java | 111 ++- .../handler/SensorBaseThingHandler.java | 3 + .../internal/handler/SensorThingHandler.java | 133 ++-- .../resources/OH-INF/i18n/deconz.properties | 6 + .../OH-INF/thing/sensor-channel-types.xml | 329 ++++++++ .../OH-INF/thing/sensor-thing-types.xml | 703 +++++------------- .../openhab/binding/deconz/SensorsTest.java | 147 ---- .../handler/BaseDeconzThingHandlerTest.java | 170 +++++ .../SensorThermostatThingHandlerTest.java | 182 +---- .../handler/SensorThingHandlerTest.java | 133 ++++ .../deconz/{ => json/sensors}/airquality.json | 0 .../{ => json/sensors}/carbonmonoxide.json | 0 .../deconz/{ => json/sensors}/fire.json | 0 .../binding/deconz/json/sensors/switch.json | 24 + .../deconz/json/sensors/vibration.json | 32 + 17 files changed, 1130 insertions(+), 1000 deletions(-) create mode 100644 bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml delete mode 100644 bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/SensorsTest.java create mode 100644 bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/BaseDeconzThingHandlerTest.java create mode 100644 bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThingHandlerTest.java rename bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/{ => json/sensors}/airquality.json (100%) rename bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/{ => json/sensors}/carbonmonoxide.json (100%) rename bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/{ => json/sensors}/fire.json (100%) create mode 100644 bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/switch.json create mode 100644 bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/vibration.json diff --git a/bundles/org.openhab.binding.deconz/README.md b/bundles/org.openhab.binding.deconz/README.md index d580134e4..01de1a5d3 100644 --- a/bundles/org.openhab.binding.deconz/README.md +++ b/bundles/org.openhab.binding.deconz/README.md @@ -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. diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/BindingConstants.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/BindingConstants.java index c29e7063a..b345641b6 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/BindingConstants.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/BindingConstants.java @@ -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, diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/SensorState.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/SensorState.java index ab9554927..f1fd3dc50 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/SensorState.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/SensorState.java @@ -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) + "}"; } } diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorBaseThingHandler.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorBaseThingHandler.java index 004d769d0..1f5f0e428 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorBaseThingHandler.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorBaseThingHandler.java @@ -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); diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorThingHandler.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorThingHandler.java index 0d2322dcc..20e84a804 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorThingHandler.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/SensorThingHandler.java @@ -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 getConfigChannels() { return CONFIG_CHANNELS; } + + /** + * Determine the light state from a state message + * + * @param newState the {@link SensorState} message + * @return Dark, Daylight, Sunset + */ + 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)); + } } diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz.properties b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz.properties index 0dd13ca1f..1cf05de8b 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz.properties +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/i18n/deconz.properties @@ -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 diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml new file mode 100644 index 000000000..974ac70a7 --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-channel-types.xml @@ -0,0 +1,329 @@ + + + + + String + + Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor, + ... + + + + + Number:Dimensionless + + Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is + specified in ppb (parts per billion). + + + + + Switch + + Alarm was triggered. + + + + + Number + + The Button that was last pressed on the switch. + + + + + Trigger + + This channel is triggered on a button event. The trigger payload consists of the button event number. + + + + + + Switch + + Carbon-monoxide was detected. + + + + + Number:Energy + + Current consumption + Energy + + + + + Number:ElectricCurrent + + Current current + Energy + + + + + Switch + + Light level is below the darkness threshold. + + + + + Switch + + Light level is above the daylight threshold. + + + + + Contact + + + + + Switch + + A fire was detected. + + + + + Number + + A gesture that was performed with the switch. + + + + + + + + + + + + + + + + + Trigger + + This channel is triggered on a gesture event. The trigger payload consists of the gesture event number. + + + + + Number:Temperature + + Target temperature + Heating + + + + + Number:Dimensionless + + Current humidity + Humidity + + + + + DateTime + + The date and time when the sensor was last seen. + Time + + + + + DateTime + + The date and time when the sensor was last updated. + Time + + + + + String + + + + + + + + + + + + Number:Illuminance + + Current light illuminance + + + + + Number + + Current light level. + + + + + Switch + + Status of this thermostat's child lock. + Lock + + + + String + + Current mode + Heating + + + + + + + + + + + Number:Dimensionless + + Current moisture + + + + + Number:Temperature + + Temperature offset + + + + + Switch + + + + + + Contact + + Open/Close detected + + + + + Number + + + + + + Number + + + + + + Number + + + + + + Number:Power + + Current power usage + Energy + + + + + Number:Pressure + + Current pressure + Pressure + + + + + Switch + + A zone is being tampered. + + + + + Number:Temperature + + Current temperature + Temperature + + + + + Number:Angle + + + + + + Number + + Dawn is around 130, sunrise at 140, sunset at 190, and dusk at 210 + + + + + Number:Dimensionless + + Current valve position + + + + + Switch + + Vibration was detected. + + + + + Number + + Vibration strength, value is device-dependent. + + + + + Number:ElectricPotential + + Current voltage + Energy + + + + + Switch + + Water leakage detected + + + + + Contact + + + + diff --git a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-thing-types.xml b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-thing-types.xml index 05c9454cb..90703c0f3 100644 --- a/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-thing-types.xml +++ b/bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/sensor-thing-types.xml @@ -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"> - + + + - + - + + - + uid + + + + + + + + + + + + uid + + + + + + + + + + + + + uid + + + + + + + + + + + + 1 - uid - - - DateTime - - The date and time when the sensor was last updated. - Time - - - - - DateTime - - The date and time when the sensor was last seen. - Time - - - - - - - - - - - - - - uid - - - - - - Number:Power - - Current power usage - Energy - - - - - Number:ElectricPotential - - Current voltage - Energy - - - - - Number:ElectricCurrent - - Current current - Energy - - - - - - - - - - - - - - uid - - - - - - Number:Energy - - Current consumption - Energy - - - @@ -113,72 +73,64 @@ - 1 - uid - - + - + - - + - uid - - - Trigger - - This channel is triggered on a button event. The trigger payload consists of the button event number. - - - + + + + + + + + + + uid + + - - Number - - The Button that was last pressed on the switch. - - + + + + + + + + + + uid + + - - Trigger - - This channel is triggered on a gesture event. The trigger payload consists of the gesture event number. - - - - - Number - - A gesture that was performed with the switch. - - - - - - - - - - - - - - + + + + + + + + + + uid + + @@ -192,333 +144,10 @@ - uid - - - Number:Illuminance - - Current light illuminance - - - - - Number - - Current light level. - - - - - Switch - - Light level is below the darkness threshold. - - - - - Switch - - Light level is above the daylight threshold. - - - - - - - - - - - - - - uid - - - - - - Number:Temperature - - Current temperature - Temperature - - - - - - - - - - - - - - uid - - - - - - Number:Dimensionless - - Current humidity - Humidity - - - - - - - - - - - - - - uid - - - - - - Number:Pressure - - Current pressure - Pressure - - - - - - - - - - - - - - uid - - - - - - Number - - Dawn is around 130, sunrise at 140, sunset at 190, and dusk at 210 - - - - - String - - - - - - - - - - - - - - - - - - - - - uid - - - - - - Contact - - Open/Close detected - - - - - - - - - - - - - - uid - - - - - - Switch - - Water leakage detected - - - - - - - - - - - - - - uid - - - - - - Switch - - A fire was detected. - - - - - - - - - - - - - - uid - - - - - - Switch - - Alarm was triggered. - - - - - Switch - - A zone is being tampered. - - - - - - - - - - - - - - uid - - - - - - Switch - - Vibration was detected. - - - - - - - - - - - - - - - 1 - - - uid - - - - - - - - - - - - - - - uid - - - - - - Switch - - Carbon-monoxide was detected. - - - - - - - - - - - - - - - uid - - - - - - String - - Current air quality level based on volatile organic compounds (VOCs) measurement. Example: good or poor, - ... - - - - - Number:Dimensionless - - Current air quality based on measurements of volatile organic compounds (VOCs). The measured value is - specified in ppb (parts per billion). - - - @@ -528,18 +157,92 @@ - uid - - - Number:Dimensionless - - Current moisture - - + + + + + + + + + + uid + + + + + + + + + + + + + uid + + + + + + + + + + + + + + + 1 + + uid + + + + + + + + + + + + + uid + + + + + + + + + + + + + + uid + + + + + + + + + + + + + uid + + @@ -558,56 +261,30 @@ - - Switch - - Status of this thermostat's child lock. - Lock - - - Contact - - - - Contact - - - - Number:Temperature - - Target temperature - Heating - - - - String - - Current mode - Heating - - - - - - - - - - Number:Temperature - - Temperature offset - - - - Number:Dimensionless - - Current valve position - - - - Switch - - - + + + + + + + + + + uid + + + + + + + + + + + + + uid + + diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/SensorsTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/SensorsTest.java deleted file mode 100644 index dee22cb7e..000000000 --- a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/SensorsTest.java +++ /dev/null @@ -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))); - } -} diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/BaseDeconzThingHandlerTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/BaseDeconzThingHandlerTest.java new file mode 100644 index 000000000..0d9d1a06c --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/BaseDeconzThingHandlerTest.java @@ -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 channels, + BiFunction 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 expected) throws IOException { + deconzMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson); + + thingHandler.initialize(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ThingStatusInfo.class); + verify(callback, atLeast(2).description("assertQuantityOfStatusUpdates")).statusUpdated(eq(thing), + captor.capture()); + + List 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) { + } +} diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThermostatThingHandlerTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThermostatThingHandlerTest.java index 9c41c9b4b..b4075c909 100644 --- a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThermostatThingHandlerTest.java +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThermostatThingHandlerTest.java @@ -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 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 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 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 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 expected) throws IOException { - sensorMessage = DeconzTest.getObjectFromJson(fileName, SensorMessage.class, gson); - - thingHandler.initialize(); - - ArgumentCaptor captor = ArgumentCaptor.forClass(ThingStatusInfo.class); - verify(callback, times(6)).statusUpdated(eq(thing), captor.capture()); - - List 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); } } diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThingHandlerTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThingHandlerTest.java new file mode 100644 index 000000000..897833e3f --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/internal/handler/SensorThingHandlerTest.java @@ -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 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 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 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 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 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); + } +} diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/airquality.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/airquality.json similarity index 100% rename from bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/airquality.json rename to bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/airquality.json diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/carbonmonoxide.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/carbonmonoxide.json similarity index 100% rename from bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/carbonmonoxide.json rename to bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/carbonmonoxide.json diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/fire.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/fire.json similarity index 100% rename from bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/fire.json rename to bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/fire.json diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/switch.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/switch.json new file mode 100644 index 000000000..dda03a88a --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/switch.json @@ -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" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/vibration.json b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/vibration.json new file mode 100644 index 000000000..c6441fc73 --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/test/resources/org/openhab/binding/deconz/json/sensors/vibration.json @@ -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" +} \ No newline at end of file