]> git.basschouten.com Git - openhab-addons.git/commitdiff
[miio] add BT Devices channel to chuangmi plug (#11715)
authorMarcel <marcel@verpaalen.com>
Sun, 12 Dec 2021 21:21:20 +0000 (22:21 +0100)
committerGitHub <noreply@github.com>
Sun, 12 Dec 2021 21:21:20 +0000 (22:21 +0100)
* [miio] add BT Devices channel to chuangmi plug

* Shows the  bluetooth  devices connected to the plug (plug as BT
gateway)
* Add refresh interval functionality to reduce load on device
* Change public to private for the private functions in conversions.
* Add test for new conversion
* Update miio.properties

Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
bundles/org.openhab.binding.miio/README.md
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/MiIoSendCommand.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/Conversions.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/basic/MiIoBasicChannel.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/handler/MiIoBasicHandler.java
bundles/org.openhab.binding.miio/src/main/java/org/openhab/binding/miio/internal/transport/MiIoAsyncCommunication.java
bundles/org.openhab.binding.miio/src/main/resources/OH-INF/i18n/basic.properties
bundles/org.openhab.binding.miio/src/main/resources/database/chuangmi.plug.212a01-miot.json
bundles/org.openhab.binding.miio/src/test/java/org/openhab/binding/miio/internal/ConversionsTest.java

index a0adc3d9ee225028c285e7907294f8c1cfa9f362..7e4cd02b385a3c4fa162c9001d4deaf40f1bc61a 100644 (file)
@@ -701,6 +701,7 @@ Note, not all the values need to be in the json file, e.g. a subset of the param
 | task-switch          | Switch               | Imilab Timer - Task Switch               |            |
 | countdown-info       | Switch               | Imilab Timer - Countdown Info            |            |
 | bt-gw                | String               | BT Gateway                               | Value mapping `["disable"="Disable","enable"="Enable"]` |
+| bt-gw-devices        | String               | Connected BT Gateway Devices             | Note, refreshes every 2nd refresh. Channel requires cloud connectivity to function. Sample widget to visualise the (json) output available from the widget market |
 
 ### Mi Smart Plug WiFi (<a name="chuangmi-plug-hmi205">chuangmi.plug.hmi205</a>) Channels
 
@@ -5720,6 +5721,7 @@ Number:Time countdown "Imilab Timer - Countdown" (G_plug) {channel="miio:basic:p
 Switch task_switch "Imilab Timer - Task Switch" (G_plug) {channel="miio:basic:plug:task-switch"}
 Switch countdown_info "Imilab Timer - Countdown Info" (G_plug) {channel="miio:basic:plug:countdown-info"}
 String bt_gw "BT Gateway" (G_plug) {channel="miio:basic:plug:bt-gw"}
+String bt_gw_devices "Connected BT Gateway Devices" (G_plug) {channel="miio:basic:plug:bt-gw-devices"}
 ```
 
 ### Mi Smart Plug WiFi (chuangmi.plug.hmi205) item file lines
index b26bd251d11feb79f9e74709d178ebe927e38250..d552c8fc1e5bf73033c5937a1e154b43097c93d7 100644 (file)
@@ -74,7 +74,7 @@ public class MiIoSendCommand {
     }
 
     public JsonElement getParams() {
-        return commandJson.has("params") ? commandJson.get("params").getAsJsonArray() : new JsonArray();
+        return commandJson.has("params") ? commandJson.get("params") : new JsonArray();
     }
 
     public JsonObject getResponse() {
index e20487d09a0b80e8cd5bbae8e25db29d579017c6..ab17fdfdfe88c0924146c5e1cd5df4cfbe757c03 100644 (file)
@@ -44,7 +44,7 @@ public class Conversions {
      * @param RGB + brightness value (note brightness in the first byte)
      * @return HSV
      */
-    public static JsonElement bRGBtoHSV(JsonElement bRGB) throws ClassCastException {
+    private static JsonElement bRGBtoHSV(JsonElement bRGB) throws ClassCastException {
         if (bRGB.isJsonPrimitive() && bRGB.getAsJsonPrimitive().isNumber()) {
             Color rgb = new Color(bRGB.getAsInt());
             HSBType hsb = HSBType.fromRGB(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
@@ -62,7 +62,7 @@ public class Conversions {
      * @param map with device variables containing the brightness info
      * @return HSV
      */
-    public static JsonElement addBrightToHSV(JsonElement rgbValue, @Nullable Map<String, Object> deviceVariables)
+    private static JsonElement addBrightToHSV(JsonElement rgbValue, @Nullable Map<String, Object> deviceVariables)
             throws ClassCastException, IllegalStateException {
         int bright = 100;
         if (deviceVariables != null) {
@@ -79,12 +79,12 @@ public class Conversions {
         return rgbValue;
     }
 
-    public static JsonElement secondsToHours(JsonElement seconds) throws ClassCastException {
+    private static JsonElement secondsToHours(JsonElement seconds) throws ClassCastException {
         double value = seconds.getAsDouble() / 3600;
         return new JsonPrimitive(value);
     }
 
-    public static JsonElement yeelightSceneConversion(JsonElement intValue)
+    private static JsonElement yeelightSceneConversion(JsonElement intValue)
             throws ClassCastException, IllegalStateException {
         switch (intValue.getAsInt()) {
             case 1:
@@ -104,17 +104,17 @@ public class Conversions {
         }
     }
 
-    public static JsonElement divideTen(JsonElement value10) throws ClassCastException, IllegalStateException {
+    private static JsonElement divideTen(JsonElement value10) throws ClassCastException, IllegalStateException {
         double value = value10.getAsDouble() / 10.0;
         return new JsonPrimitive(value);
     }
 
-    public static JsonElement divideHundred(JsonElement value10) throws ClassCastException, IllegalStateException {
+    private static JsonElement divideHundred(JsonElement value10) throws ClassCastException, IllegalStateException {
         double value = value10.getAsDouble() / 100.0;
         return new JsonPrimitive(value);
     }
 
-    public static JsonElement tankLevel(JsonElement value12) throws ClassCastException, IllegalStateException {
+    private static JsonElement tankLevel(JsonElement value12) throws ClassCastException, IllegalStateException {
         // 127 without water tank. 120 = 100% water
         if (value12.getAsInt() == 127) {
             return new JsonPrimitive(-1);
@@ -124,7 +124,30 @@ public class Conversions {
         }
     }
 
-    public static JsonElement getJsonElement(String element, JsonElement responseValue) {
+    /**
+     * Returns the deviceId element value from the Json response. If not found, returns the input
+     *
+     * @param responseValue
+     * @param deviceVariables containing the deviceId
+     * @return
+     */
+    private static JsonElement getDidElement(JsonElement responseValue, Map<String, Object> deviceVariables) {
+        String did = (String) deviceVariables.get("deviceId");
+        if (did != null) {
+            return getJsonElement(did, responseValue);
+        }
+        LOGGER.debug("deviceId not Found, no conversion");
+        return responseValue;
+    }
+
+    /**
+     * Returns the element from the Json response. If not found, returns the input
+     *
+     * @param element to be found
+     * @param responseValue
+     * @return
+     */
+    private static JsonElement getJsonElement(String element, JsonElement responseValue) {
         try {
             if (responseValue.isJsonPrimitive() || responseValue.isJsonObject()) {
                 JsonElement jsonElement = responseValue.isJsonObject() ? responseValue
@@ -143,8 +166,7 @@ public class Conversions {
         return responseValue;
     }
 
-    public static JsonElement execute(String transformation, JsonElement value,
-            @Nullable Map<String, Object> deviceVariables) {
+    public static JsonElement execute(String transformation, JsonElement value, Map<String, Object> deviceVariables) {
         try {
             if (transformation.toUpperCase().startsWith("GETJSONELEMENT")) {
                 if (transformation.length() > 15) {
@@ -168,6 +190,8 @@ public class Conversions {
                     return addBrightToHSV(value, deviceVariables);
                 case "BRGBTOHSV":
                     return bRGBtoHSV(value);
+                case "GETDIDELEMENT":
+                    return getDidElement(value, deviceVariables);
                 default:
                     LOGGER.debug("Transformation {} not found. Returning '{}'", transformation, value.toString());
                     return value;
index 33dc575743288bccb08520a5cadf3af6b81d046f..6190f075da6b16b9b4c9dc8dc0171915cbcc2768 100644 (file)
@@ -22,6 +22,7 @@ import java.util.List;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 
+import com.google.gson.JsonElement;
 import com.google.gson.annotations.Expose;
 import com.google.gson.annotations.SerializedName;
 
@@ -66,12 +67,21 @@ public class MiIoBasicChannel {
     @SerializedName("refresh")
     @Expose
     private @Nullable Boolean refresh;
+    @SerializedName("refreshInterval")
+    @Expose
+    private @Nullable Integer refreshInterval;
     @SerializedName("customRefreshCommand")
     @Expose
     private @Nullable String channelCustomRefreshCommand;
+    @SerializedName("customRefreshParameters")
+    @Expose
+    private @Nullable JsonElement customRefreshParameters;
     @SerializedName("transformation")
     @Expose
     private @Nullable String transformation;
+    @SerializedName("transformations")
+    @Expose
+    private @Nullable List<String> transformations;
     @SerializedName("ChannelGroup")
     @Expose
     private @Nullable String channelGroup;
@@ -202,6 +212,23 @@ public class MiIoBasicChannel {
         this.refresh = refresh;
     }
 
+    public Integer getRefreshInterval() {
+        Integer refreshInterval = this.refreshInterval;
+        if (refreshInterval != null) {
+            return refreshInterval;
+        }
+        return 1;
+    }
+
+    public void setRefresh(@Nullable final Integer interval) {
+        final Integer refreshInterval = interval;
+        if (refreshInterval != null && refreshInterval.intValue() != 1) {
+            this.refreshInterval = refreshInterval;
+        } else {
+            this.refreshInterval = null;
+        }
+    }
+
     public String getChannelCustomRefreshCommand() {
         final @Nullable String channelCustomRefreshCommand = this.channelCustomRefreshCommand;
         return channelCustomRefreshCommand != null ? channelCustomRefreshCommand : "";
@@ -211,6 +238,14 @@ public class MiIoBasicChannel {
         this.channelCustomRefreshCommand = channelCustomRefreshCommand;
     }
 
+    public @Nullable final JsonElement getCustomRefreshParameters() {
+        return customRefreshParameters;
+    }
+
+    public final void setCustomRefreshParameters(@Nullable JsonElement customRefreshParameters) {
+        this.customRefreshParameters = customRefreshParameters;
+    }
+
     public String getChannelGroup() {
         final @Nullable String channelGroup = this.channelGroup;
         return channelGroup != null ? channelGroup : "";
@@ -237,6 +272,28 @@ public class MiIoBasicChannel {
         this.transformation = transformation;
     }
 
+    public final List<String> getTransformations() {
+        List<String> transformations = this.transformations;
+        if (transformations == null) {
+            transformations = new ArrayList<>();
+        }
+        String transformation = this.transformation;
+        if (transformation != null) {
+            List<String> allTransformation = new ArrayList<>(List.of(transformation));
+            allTransformation.addAll(transformations);
+            return allTransformation;
+        }
+        return transformations;
+    }
+
+    public final void setTransformations(@Nullable List<String> transformations) {
+        if (transformations != null && !transformations.isEmpty()) {
+            this.transformations = transformations;
+        } else {
+            this.transformations = null;
+        }
+    }
+
     public @Nullable String getCategory() {
         return category;
     }
index d192889aad8a80e82528eb06f6ed3c7bf542d2e9..e51083b71ea6b68e9b2472a2e1e54b307ff67d58 100644 (file)
@@ -105,6 +105,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
     private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
     private ChannelTypeRegistry channelTypeRegistry;
     private BasicChannelTypeProvider basicChannelTypeProvider;
+    private Map<String, Integer> customRefreshInterval = new HashMap<>();
 
     public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
             CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
@@ -352,16 +353,39 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
         }
     }
 
+    private boolean customRefreshIntervalCheck(MiIoBasicChannel miChannel) {
+        if (miChannel.getRefreshInterval() > 1) {
+            int iteration = customRefreshInterval.getOrDefault(miChannel.getChannel(), 0);
+            if (iteration < 1) {
+                customRefreshInterval.put(miChannel.getChannel(), miChannel.getRefreshInterval() - 1);
+            } else {
+                logger.debug("Skip refresh of channel {} for {}. Next refresh in {} cycles.", miChannel.getChannel(),
+                        getThing().getUID(), iteration);
+                customRefreshInterval.put(miChannel.getChannel(), iteration - 1);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean linkedChannelCheck(MiIoBasicChannel miChannel) {
+        if (!isLinked(miChannel.getChannel())) {
+            logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
+                    getThing().getUID());
+            return false;
+        }
+        return true;
+    }
+
     private void refreshCustomProperties(MiIoBasicDevice midevice) {
         for (MiIoBasicChannel miChannel : refreshListCustomCommands.values()) {
-            if (!isLinked(miChannel.getChannel())) {
-                logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
-                        getThing().getUID());
+            if (customRefreshIntervalCheck(miChannel) || !linkedChannelCheck(miChannel)) {
                 continue;
             }
-            String cmd = miChannel.getChannelCustomRefreshCommand();
+            final JsonElement para = miChannel.getCustomRefreshParameters();
+            String cmd = miChannel.getChannelCustomRefreshCommand() + (para != null ? para.toString() : "");
             if (!cmd.startsWith("/")) {
-                cmds.put(sendCommand(miChannel.getChannelCustomRefreshCommand()), miChannel.getChannel());
+                cmds.put(sendCommand(cmd), miChannel.getChannel());
             } else {
                 if (cloudServer.isBlank()) {
                     logger.debug("Cloudserver empty. Skipping refresh for {} channel '{}'", getThing().getUID(),
@@ -378,9 +402,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
         int maxProperties = device.getDevice().getMaxProperties();
         JsonArray getPropString = new JsonArray();
         for (MiIoBasicChannel miChannel : refreshList) {
-            if (!isLinked(miChannel.getChannel())) {
-                logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
-                        getThing().getUID());
+            if (customRefreshIntervalCheck(miChannel) || !linkedChannelCheck(miChannel)) {
                 continue;
             }
             JsonElement property;
index 1bb9018b4a92cf9a88de14d5cdda396aff4212f6..caa85b37b8bfb609a43fbad35cb56b58236abe1d 100644 (file)
@@ -182,10 +182,7 @@ public class MiIoAsyncCommunication {
                             miIoSendCommand.getCloudServer());
                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
                 } else {
-                    String data = miIoSendCommand.getParams().isJsonArray()
-                            && miIoSendCommand.getParams().getAsJsonArray().size() > 0
-                                    ? miIoSendCommand.getParams().getAsJsonArray().get(0).toString()
-                                    : "";
+                    String data = miIoSendCommand.getParams().toString();
                     logger.debug("Custom cloud request send to url '{}' with data '{}'", miIoSendCommand.getMethod(),
                             data);
                     decryptedResponse = cloudConnector.sendCloudCommand(miIoSendCommand.getMethod(),
index 65659da0c429a460586411c8e44ae0a75d175e8d..ec7c8d67214a0fa0ac781a43d5b03daad874cba3 100644 (file)
@@ -414,6 +414,7 @@ ch.cgllc.airmonitor.s1.pm25 = PM2.5
 ch.cgllc.airmonitor.s1.temperature = Temperature
 ch.cgllc.airmonitor.s1.tvoc = tVOC
 ch.chuangmi.plug.212a01-miot.bt-gw = BT Gateway
+ch.chuangmi.plug.212a01-miot.bt-gw-devices = Connected BT Gateway Devices
 ch.chuangmi.plug.212a01-miot.countdown = Imilab Timer - Countdown
 ch.chuangmi.plug.212a01-miot.countdown-info = Imilab Timer - Countdown Info
 ch.chuangmi.plug.212a01-miot.electric-current = Power Consumption - Electric Current
index 9ee47e5e69f3ef72016ec0efb54eb035ebdce203..66f248fa283d8bd720d80d741369366b857f0c44 100644 (file)
                                                }
                                        }
                                ],
+                               "category": "bluetooth",
                                "readmeComment": "Value mapping `[\"disable\"\u003d\"Disable\",\"enable\"\u003d\"Enable\"]`"
+                       },
+                       {
+                               "property": "",
+                               "friendlyName": "Connected BT Gateway Devices",
+                               "channel": "bt-gw-devices",
+                               "type": "String",
+                               "stateDescription": {
+                                       "readOnly": true
+                               },
+                               "refresh": true,
+                               "refreshInterval": 2,
+                               "customRefreshCommand": "/device/get_bledevice_by_gateway",
+                               "customRefreshParameters": {
+                                       "dids": [
+                                               "$deviceId$"
+                                       ]
+                               },
+                               "transformation": "getDiDElement",
+                               "actions": [],
+                               "category": "bluetooth",
+                               "readmeComment": "Note, refreshes every 2nd refresh. Channel requires cloud connectivity to function. Sample widget to visualise the (json) output available from the widget market"
                        }
                ],
                "experimental": false
index 2ee46a065f5d4b1fa83b6b0a1c6e7aff80c32584..aa83988d63cf3eaf47e915c3dab9556b4ed5d912 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.miio.internal;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -35,6 +36,48 @@ import com.google.gson.JsonPrimitive;
 @NonNullByDefault
 public class ConversionsTest {
 
+    @Test
+    public void getDidElementTest() {
+        Map<String, Object> deviceVariables = new HashMap<>();
+        String transformation = "getDidElement";
+        JsonElement validInput = new JsonPrimitive(
+                "{\"361185596\":\"{\\\"C812105B04000400\\\":\\\"-92\\\",\\\"blt.3.17q3si5345k00\\\":\\\"-54\\\",\\\"blt.4.10heul64og400\\\":\\\"-73\\\"}\"}");
+
+        // test no did in deviceVariables
+        JsonElement value = validInput;
+        JsonElement transformedResponse = Conversions.execute(transformation, value, deviceVariables);
+        assertNotNull(transformedResponse);
+        assertEquals(value, transformedResponse);
+
+        // test valid input & response
+        deviceVariables.put("deviceId", "361185596");
+        value = validInput;
+        transformedResponse = Conversions.execute(transformation, value, deviceVariables);
+        assertNotNull(transformedResponse);
+        assertEquals(new JsonPrimitive(
+                "{\"C812105B04000400\":\"-92\",\"blt.3.17q3si5345k00\":\"-54\",\"blt.4.10heul64og400\":\"-73\"}"),
+                transformedResponse);
+
+        // test non json
+        value = new JsonPrimitive("some non json value");
+        transformedResponse = Conversions.execute(transformation, value, deviceVariables);
+        assertNotNull(transformedResponse);
+        assertEquals(value, transformedResponse);
+
+        // test different did in deviceVariables
+        deviceVariables.put("deviceId", "ABC185596");
+        value = validInput;
+        transformedResponse = Conversions.execute(transformation, value, deviceVariables);
+        assertNotNull(transformedResponse);
+        assertEquals(value, transformedResponse);
+
+        // test empty input
+        value = new JsonPrimitive("");
+        transformedResponse = Conversions.execute(transformation, value, deviceVariables);
+        assertNotNull(transformedResponse);
+        assertEquals(value, transformedResponse);
+    }
+
     @Test
     public void getJsonElementTest() {
 
@@ -55,11 +98,6 @@ public class ConversionsTest {
 
         transformation = "getJsonElement-test";
 
-        // test without deviceVariables
-        resp = Conversions.execute(transformation, value, null);
-        assertNotNull(resp);
-        assertEquals(new JsonPrimitive("testresponse"), resp);
-
         // test non json
         value = new JsonPrimitive("some non json value");
         resp = Conversions.execute(transformation, value, deviceVariables);