From 2da6b67f2cff4394d3177423cd1a160e43d71e53 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Fri, 19 Jun 2026 10:29:31 +0200 Subject: [PATCH] [shelly] Add support for Shelly BLU Motion ZB, BLU H&T Display ZB, BLU For/Window ZB, BLU Remote ZB (#20967) * [shelly] BLU ZB step 0: move scalar/array handling from JS to Java Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 4 + .../shelly/internal/ShellyDevices.java | 10 +- .../shelly/internal/ShellyHandlerFactory.java | 2 - .../internal/api1/Shelly1ApiJsonDTO.java | 1 + .../shelly/internal/api2/ShellyBluApi.java | 28 +++- .../internal/api2/ShellyBluJsonDTO.java | 39 ++++- .../internal/handler/ShellyComponents.java | 19 ++- .../provider/ShellyChannelDefinitions.java | 3 +- .../main/resources/scripts/oh-blu-scanner.js | 12 +- .../shelly/internal/api2/ShellyBluZbTest.java | 141 ++++++++++++++++++ .../discovery/ShellyThingCreatorTest.java | 4 + 11 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/api2/ShellyBluZbTest.java diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 339d21980f..1b3b517a07 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -151,10 +151,14 @@ See section [Discovery](#discovery) for details. | shellyblubutton | Shelly BLU Tough ZB | SBBT-102C | | shellybluwallswitch4 | Shelly BLU Wallswitch 4 | SBBT-EU5027 | | shellyblurcbutton4 | Shelly BLU RC Button 4 | SBBT-004CUS | +| shellyblurcbutton4 | Shelly BLU RC Button 4 ZB | SBBT-104CUS | | shellybluht | Shelly BLU H&T | SBHT-003C | | shellybluht | Shelly BLU H&T ZB | SBHT-203C | +| shellybluht | Shelly BLU H&T Display ZB | SBHT-103C | | shellybludw | Shelly BLU Door/Windows | SBDW-002C | +| shellybludw | Shelly BLU Door/Window ZB | SBDW-103C | | shellyblumotion | Shelly BLU Motion | SBMO-003Z | +| shellyblumotion | Shelly BLU Motion ZB | SBMO-103Z | | shellybludistance | Shelly BLU Distance | SBDI-003E | | shellybluremote | Shelly BLU Remote Control | SBRC-005B | diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyDevices.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyDevices.java index 16c14631c8..9f425a3ffd 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyDevices.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyDevices.java @@ -165,10 +165,14 @@ public class ShellyDevices { public static final String SHELLYDT_BLUTOUGHZB = "SBBT-102C"; public static final String SHELLYDT_BLUWALLSWITCH4 = "SBBT-004CEU"; public static final String SHELLYDT_BLURCBUTTON4 = "SBBT-004CUS"; + public static final String SHELLYDT_BLURCBUTTON4ZB = "SBBT-104CUS"; public static final String SHELLYDT_BLUHT = "SBHT-003C"; public static final String SHELLYDT_BLUHTZB = "SBHT-203C"; + public static final String SHELLYDT_BLUHTDISPLAYZB = "SBHT-103C"; public static final String SHELLYDT_BLUDW = "SBDW-002C"; + public static final String SHELLYDT_BLUDWZB = "SBDW-103C"; public static final String SHELLYDT_BLUMOTION = "SBMO-003Z"; + public static final String SHELLYDT_BLUMOTIONZB = "SBMO-103Z"; public static final String SHELLYDT_BLUDISTANCE = "SBDI-003E"; public static final String SHELLYDT_BLUREMOTE = "SBRC-005B"; @@ -492,10 +496,14 @@ public class ShellyDevices { Map.entry(SHELLYDT_BLUTOUGHZB, THING_TYPE_SHELLYBLUBUTTON1), Map.entry(SHELLYDT_BLUWALLSWITCH4, THING_TYPE_SHELLYBLUWALLSWITCH4), Map.entry(SHELLYDT_BLURCBUTTON4, THING_TYPE_SHELLYBLURCBUTTON4), + Map.entry(SHELLYDT_BLURCBUTTON4ZB, THING_TYPE_SHELLYBLURCBUTTON4), Map.entry(SHELLYDT_BLUHT, THING_TYPE_SHELLYBLUHT), // Map.entry(SHELLYDT_BLUHTZB, THING_TYPE_SHELLYBLUHT), // - Map.entry(SHELLYDT_BLUDW, THING_TYPE_SHELLYBLUDW), + Map.entry(SHELLYDT_BLUHTDISPLAYZB, THING_TYPE_SHELLYBLUHT), // + Map.entry(SHELLYDT_BLUDW, THING_TYPE_SHELLYBLUDW), // + Map.entry(SHELLYDT_BLUDWZB, THING_TYPE_SHELLYBLUDW), Map.entry(SHELLYDT_BLUMOTION, THING_TYPE_SHELLYBLUMOTION), + Map.entry(SHELLYDT_BLUMOTIONZB, THING_TYPE_SHELLYBLUMOTION), Map.entry(SHELLYDT_BLUDISTANCE, THING_TYPE_SHELLYBLUDISTANCE), Map.entry(SHELLYDT_BLUREMOTE, THING_TYPE_SHELLYBLUREMOTE), diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index da082ff467..cd07cc41f2 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -67,7 +67,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { private final Shelly1CoapServer coapServer; private final ShellyThingTable thingTable; private final WebSocketClient webSocketClient; - private final NetworkAddressService networkAddressService; private final ShellyBindingRuntimeConfig bindingConfig; /** @@ -83,7 +82,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { @Reference HttpClientFactory httpClientFactory, @Reference WebSocketFactory webSocketFactory, ComponentContext componentContext, Map configProperties) { super.activate(componentContext); - this.networkAddressService = networkAddressService; this.messages = translationProvider; this.thingTable = thingTable; WebSocketClient client = Shelly2RpcSocket.createWebSocketClient(webSocketFactory, "shelly2api"); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java index ae7c6a2a6c..77ac9771af 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java @@ -1004,6 +1004,7 @@ public class Shelly1ApiJsonDTO { public static class ShellySensorBat { public Double value; // estimated remaining battery capacity in % public Double voltage; // battery voltage + public @Nullable Boolean batteryLow; // device-reported low-battery flag (BLU ZB devices) } // Door/Window sensor diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 36ebdcfc07..7ed4d2f544 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -49,7 +49,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * {@link ShellyBluApi} implementsBLU interface + * {@link ShellyBluApi} handles the Shelly BLU Bluetooth Low Energy device protocol. + * + *

+ * BLU devices (buttons, motion sensors, door/window sensors, H&T sensors) are + * battery-powered and communicate via a Shelly Gen2/3/4 gateway. The gateway forwards + * BTHome-encoded advertisements as {@code NotifyBluGW} WebSocket events to the hub, which + * dispatches them to the individual per-device thing handlers via {@link ShellyThingTable}. + *

+ * + *

+ * Sensor data initialization follows a two-pass pattern: {@link #initializeSensorData} creates + * the sub-objects (bat, lux, tmp, …) whenever the corresponding BTHome fields are present, + * so the event-processing block can dereference them unconditionally. + *

* * @author Markus Michels - Initial contribution * @author Udo Hartmann - Add support for decoding multi button inputs @@ -216,6 +229,9 @@ public class ShellyBluApi extends Shelly2ApiRpc { if (e.blu.battery != null) { sensorData.bat.value = (double) e.blu.battery; } + if (e.blu.batteryLow != null) { + sensorData.bat.batteryLow = e.blu.batteryLow == 1; + } if (e.blu.rssi != null) { deviceStatus.wifiSta.rssi = e.blu.rssi; } @@ -227,6 +243,12 @@ public class ShellyBluApi extends Shelly2ApiRpc { sensorData.lux.isValid = true; sensorData.lux.value = (double) e.blu.illuminance; } + if (e.blu.lightLevel != null) { + sensorData.lux.isValid = true; + int ll = getInteger(e.blu.lightLevel); + sensorData.lux.illumination = ll == 0 ? "dark" + : ll == 1 ? "twilight" : ll == 2 ? "bright" : "unknown"; + } if (e.blu.temperatures != null) { if (e.blu.temperatures.length == 1) { sensorData.tmp.units = SHELLY_TEMP_CELSIUS; @@ -316,10 +338,10 @@ public class ShellyBluApi extends Shelly2ApiRpc { } private static void initializeSensorData(ShellyStatusSensor sensorData, Shelly2NotifyBluEventData data) { - if (data.battery != null && sensorData.bat == null) { + if ((data.battery != null || data.batteryLow != null) && sensorData.bat == null) { sensorData.bat = new ShellySensorBat(); } - if (data.illuminance != null && sensorData.lux == null) { + if ((data.illuminance != null || data.lightLevel != null) && sensorData.lux == null) { sensorData.lux = new ShellySensorLux(); } if (data.temperatures != null && sensorData.tmp == null) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluJsonDTO.java index 192642267f..73d5f5e654 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluJsonDTO.java @@ -12,10 +12,18 @@ */ package org.openhab.binding.shelly.internal.api2; +import java.lang.reflect.Type; + +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; /** - * {@link ShellyBluJsonDTO} includes contans and structures used for BluApi's JSON mapping and processing. + * {@link ShellyBluJsonDTO} includes constants and structures used for BluApi's JSON mapping and processing. * * @author Markus Michels - Initial contribution */ @@ -42,6 +50,28 @@ public class ShellyBluJsonDTO { // "data":{"encryption":false,"BTHome_version":2,"pid":38,"Battery":100,"Illuminance":0,"Window":1,"Rotation":0,"addr":"bc:02:6e:c3:a6:c7","rssi":-62}, // "ts":1682877414.25} + // Handles BTHome fields that a single-button device sends as a scalar but a multi-button device sends as an array. + // Without this adapter, a plain Gson instance would throw JsonSyntaxException on scalar payloads for T[] fields. + static class IntegerArrayAdapter implements JsonDeserializer { + @Override + public Integer[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) { + if (json.isJsonArray()) { + return ctx.deserialize(json, Integer[].class); + } + return new Integer[] { json.getAsInt() }; + } + } + + static class DoubleArrayAdapter implements JsonDeserializer { + @Override + public Double[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext ctx) { + if (json.isJsonArray()) { + return ctx.deserialize(json, Double[].class); + } + return new Double[] { json.getAsDouble() }; + } + } + public static class Shelly2NotifyBluEventData { public static class Shelly2NotifyBluEventDimmer { public Integer direction; @@ -57,6 +87,7 @@ public class ShellyBluJsonDTO { public Integer pid; @SerializedName("Battery") public Integer battery; + @JsonAdapter(IntegerArrayAdapter.class) @SerializedName("Button") public Integer[] buttons; @SerializedName("Illuminance") @@ -65,6 +96,7 @@ public class ShellyBluJsonDTO { public Integer windowState; @SerializedName("Motion") public Integer motionState; + @JsonAdapter(DoubleArrayAdapter.class) @SerializedName("Temperature") public Double[] temperatures; @SerializedName("Humidity") @@ -75,12 +107,17 @@ public class ShellyBluJsonDTO { public Double distance; @SerializedName("Channel") // BLU Remote public Integer channel; + @JsonAdapter(DoubleArrayAdapter.class) @SerializedName("Rotation") // BLU Remote public Double[] rotations; @SerializedName("Dimmer") // BLU Remote public Shelly2NotifyBluEventDimmer dimmer; @SerializedName("Firmware32") public Long firmware32; + @SerializedName("LightLevel") // BLU ZB: 0=dark, 1=twilight, 2=bright + public @Nullable Integer lightLevel; + @SerializedName("BatteryLow") // BLU H&T Display ZB: 1=battery below 15% + public @Nullable Integer batteryLow; public Integer rssi; @SerializedName("tx_power") diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index e78e2746ca..8b50d1a5f3 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -475,8 +475,10 @@ public class ShellyComponents { } if ((sdata.lux != null) && getBool(sdata.lux.isValid)) { // “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true}, - updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX, - toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX)); + if (sdata.lux.value != null) { + updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX, + toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX)); + } if (sdata.lux.illumination != null) { updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM, getStringType(sdata.lux.illumination)); @@ -551,14 +553,19 @@ public class ShellyComponents { getOnOff(charger)); } if (sdata.bat != null) { // no update for Sense - updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, - toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT)); + if (sdata.bat.value != null) { + updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, + toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT)); + } int lowBattery = thingHandler.getThingConfig().getLowBattery(); + Boolean batteryLowFlag = sdata.bat.batteryLow; + boolean isLow = batteryLowFlag != null ? batteryLowFlag.booleanValue() + : (sdata.bat.value != null && !charger && getDouble(sdata.bat.value) < lowBattery); boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW, - getOnOff(!charger && getDouble(sdata.bat.value) < lowBattery)); + getOnOff(isLow)); updated |= changed; - if (!charger && changed && getDouble(sdata.bat.value) < lowBattery) { + if (changed && isLow) { thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false); } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index 2c5783f080..742b0f0c87 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -602,7 +602,8 @@ public class ShellyChannelDefinitions { addChannel(thing, newChannels, sdata.tmp != null || sdata.thermostats != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP); addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM); - addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX); + addChannel(thing, newChannels, sdata.lux != null && sdata.lux.value != null, CHANNEL_GROUP_SENSOR, + CHANNEL_SENSOR_LUX); addChannel(thing, newChannels, sdata.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM); addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD); diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js b/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js index 54ac86574d..6867ce266e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js +++ b/bundles/org.openhab.binding.shelly/src/main/resources/scripts/oh-blu-scanner.js @@ -31,7 +31,6 @@ let uint32 = 6; let int32 = 7; let BTH_DIMMERSTEPS_INDEX = 0x3c; // Dimmer (Wheel) Steps object ID -let FORCE_ARRAY_VALUES = ["Temperature", "Button", "Rotation"]; // BTHome object definitions: id => {name, type, optional scale factor} // https://bthome.io/format/ @@ -53,7 +52,7 @@ BTH[0x11] = { n: "Opening", t: uint8 }; // BTH[0x12] = { n: "Co2", t: uint16 }; // CO2 concentration ppm BTH[0x13] = { n: "TVOC", t: uint16 }; // TVOC Air Quality ug/m3 BTH[0x14] = { n: "Moisture16", t: uint16, f: 0.01 }; // Moisture (scaled by 0.01) -BTH[0x15] = { n: "Battery", t: uint8 }; // Battery level normal/low status (boolean) +BTH[0x15] = { n: "BatteryLow", t: uint8 }; // Battery low flag: 0=normal, 1=low BTH[0x16] = { n: "BatteryCharging", t: uint8 }; // Battery charging status (boolean) BTH[0x17] = { n: "CarbonMonoxide", t: uint8 }; // Carbon Monoxide not detected/detected status (boolean) BTH[0x18] = { n: "Cold", t: uint8 }; // Cold normal/cold status (boolean) @@ -115,6 +114,7 @@ BTH[0x5d] = { n: "Current", t: int16, f: 0.001 }; // BTH[0x5e] = { n: "Direction", t: uint16, f: 0.01 }; // Direction (scaled by 0.01) BTH[0x5f] = { n: "Precipitation", t: uint16, f: 0.1 }; // Precipitation (scaled by 0.1) BTH[0x60] = { n: "Channel", t: uint8 }; // Channel +BTH[0x64] = { n: "LightLevel", t: uint8 }; // Light level 0=dark, 1=twilight, 2=bright BTH[0xF0] = { n: "DeviceId", t: uint16}; // Device type ID BTH[0xF1] = { n: "Firmware32", t: uint32}; // Firmware version in 1.2.3.4 format BTH[0xF2] = { n: "Firmware24", t: uint24}; // Firmware version in 1.2.3 format @@ -251,14 +251,6 @@ let BTHomeDecoder = { // Add events as arrays to the result if (dimmer.length > 0) result["Dimmer"] = dimmer; - // Special handling for values, which need to be converted to an array - for (let i = 0; i < FORCE_ARRAY_VALUES.length; i++) { - let key = FORCE_ARRAY_VALUES[i]; - if (typeof result[key] !== "undefined" && !Array.isArray(result[key])) { - result[key] = [result[key]]; - } - } - return result; } }; diff --git a/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/api2/ShellyBluZbTest.java b/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/api2/ShellyBluZbTest.java new file mode 100644 index 0000000000..56ee03cde6 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/api2/ShellyBluZbTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2010-2026 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.shelly.internal.api2; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.openhab.binding.shelly.internal.ShellyDevices.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.shelly.internal.api2.ShellyBluJsonDTO.Shelly2NotifyBluEventData; +import org.openhab.core.thing.ThingTypeUID; + +import com.google.gson.Gson; + +/** + * Tests for BLU ZB device support: model registration, BTHome field deserialization, + * and scalar/array adapter handling. + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class ShellyBluZbTest { + + private final Gson gson = new Gson(); + + @Test + void bluRcButton4ZbMapsToRcButton4ThingType() { + ThingTypeUID uid = THING_TYPE_BY_DEVICE_TYPE.get(SHELLYDT_BLURCBUTTON4ZB); + assertNotNull(uid); + assertThat(uid, is(THING_TYPE_SHELLYBLURCBUTTON4)); + } + + @Test + void bluMotionZbMapsToMotionThingType() { + ThingTypeUID uid = THING_TYPE_BY_DEVICE_TYPE.get(SHELLYDT_BLUMOTIONZB); + assertNotNull(uid); + assertThat(uid, is(THING_TYPE_SHELLYBLUMOTION)); + } + + @Test + void bluDwZbMapsToDwThingType() { + ThingTypeUID uid = THING_TYPE_BY_DEVICE_TYPE.get(SHELLYDT_BLUDWZB); + assertNotNull(uid); + assertThat(uid, is(THING_TYPE_SHELLYBLUDW)); + } + + @Test + void bluHtDisplayZbMapsToHtThingType() { + ThingTypeUID uid = THING_TYPE_BY_DEVICE_TYPE.get(SHELLYDT_BLUHTDISPLAYZB); + assertNotNull(uid); + assertThat(uid, is(THING_TYPE_SHELLYBLUHT)); + } + + @Test + void lightLevelBrightDeserializes() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"LightLevel\":2,\"Battery\":85}", + Shelly2NotifyBluEventData.class); + assertNotNull(data); + assertThat(data.lightLevel, is(2)); + assertThat(data.battery, is(85)); + } + + @Test + void lightLevelDarkDeserializes() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"LightLevel\":0}", Shelly2NotifyBluEventData.class); + assertNotNull(data); + assertThat(data.lightLevel, is(0)); + } + + @Test + void batteryLowSetDeserializes() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Battery\":10,\"BatteryLow\":1}", + Shelly2NotifyBluEventData.class); + assertNotNull(data); + assertThat(data.battery, is(10)); + assertThat(data.batteryLow, is(1)); + } + + @Test + void batteryLowClearedDeserializes() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Battery\":80,\"BatteryLow\":0}", + Shelly2NotifyBluEventData.class); + assertNotNull(data); + assertThat(data.batteryLow, is(0)); + } + + @Test + void scalarButtonDeserializesToSingleElementArray() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Button\":1}", Shelly2NotifyBluEventData.class); + assertNotNull(data); + Integer[] buttons = data.buttons; + assertNotNull(buttons); + assertThat(buttons.length, is(1)); + assertThat(buttons[0], is(1)); + } + + @Test + void arrayButtonDeserializesToMultiElementArray() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Button\":[1,0,1,0]}", Shelly2NotifyBluEventData.class); + assertNotNull(data); + Integer[] buttons = data.buttons; + assertNotNull(buttons); + assertThat(buttons.length, is(4)); + assertThat(buttons[0], is(1)); + assertThat(buttons[2], is(1)); + } + + @Test + void scalarTemperatureDeserializesToSingleElementArray() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Temperature\":21.5}", Shelly2NotifyBluEventData.class); + assertNotNull(data); + Double[] temperatures = data.temperatures; + assertNotNull(temperatures); + assertThat(temperatures.length, is(1)); + assertThat(temperatures[0], is(21.5)); + } + + @Test + void arrayTemperatureDeserializesToMultiElementArray() { + Shelly2NotifyBluEventData data = gson.fromJson("{\"Temperature\":[21.5,22.0]}", + Shelly2NotifyBluEventData.class); + assertNotNull(data); + Double[] temperatures = data.temperatures; + assertNotNull(temperatures); + assertThat(temperatures.length, is(2)); + assertThat(temperatures[0], is(21.5)); + assertThat(temperatures[1], is(22.0)); + } +} diff --git a/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreatorTest.java b/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreatorTest.java index d48877776b..8d6e467a61 100644 --- a/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreatorTest.java +++ b/bundles/org.openhab.binding.shelly/src/test/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreatorTest.java @@ -264,10 +264,14 @@ public class ShellyThingCreatorTest { Arguments.of(SHELLYDT_BLUTOUGHZB, "", THING_TYPE_SHELLYBLUBUTTON1), // Arguments.of(SHELLYDT_BLUWALLSWITCH4, "", THING_TYPE_SHELLYBLUWALLSWITCH4), // Arguments.of(SHELLYDT_BLURCBUTTON4, "", THING_TYPE_SHELLYBLURCBUTTON4), // + Arguments.of(SHELLYDT_BLURCBUTTON4ZB, "", THING_TYPE_SHELLYBLURCBUTTON4), // Arguments.of(SHELLYDT_BLUHT, "", THING_TYPE_SHELLYBLUHT), // Arguments.of(SHELLYDT_BLUHTZB, "", THING_TYPE_SHELLYBLUHT), // + Arguments.of(SHELLYDT_BLUHTDISPLAYZB, "", THING_TYPE_SHELLYBLUHT), // Arguments.of(SHELLYDT_BLUDW, "", THING_TYPE_SHELLYBLUDW), // + Arguments.of(SHELLYDT_BLUDWZB, "", THING_TYPE_SHELLYBLUDW), // Arguments.of(SHELLYDT_BLUMOTION, "", THING_TYPE_SHELLYBLUMOTION), // + Arguments.of(SHELLYDT_BLUMOTIONZB, "", THING_TYPE_SHELLYBLUMOTION), // Arguments.of(SHELLYDT_BLUDISTANCE, "", THING_TYPE_SHELLYBLUDISTANCE), // Arguments.of(SHELLYDT_BLUREMOTE, "", THING_TYPE_SHELLYBLUREMOTE), //