]> git.basschouten.com Git - openhab-addons.git/commitdiff
[wemo] Refactor Insight Switch parser (#12380)
authorJacob Laursen <jacob-github@vindvejr.dk>
Sun, 27 Feb 2022 20:13:08 +0000 (21:13 +0100)
committerGitHub <noreply@github.com>
Sun, 27 Feb 2022 20:13:08 +0000 (21:13 +0100)
* Extract Insight parser to separate class and provide unit tests

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/InsightParser.java [new file with mode: 0644]
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java
bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/InsightParserTest.java [new file with mode: 0644]

diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/InsightParser.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/InsightParser.java
new file mode 100644 (file)
index 0000000..c80f74d
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2010-2022 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.wemo.internal;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DateTimeType;
+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.unit.Units;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * Parser for WeMo Insight Switch values.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class InsightParser {
+
+    private static final int INSIGHT_POSITION_STATE = 0;
+    private static final int INSIGHT_POSITION_LASTCHANGEDAT = 1;
+    private static final int INSIGHT_POSITION_LASTONFOR = 2;
+    private static final int INSIGHT_POSITION_ONTODAY = 3;
+    private static final int INSIGHT_POSITION_ONTOTAL = 4;
+    private static final int INSIGHT_POSITION_TIMESPAN = 5;
+    private static final int INSIGHT_POSITION_AVERAGEPOWER = 6;
+    private static final int INSIGHT_POSITION_CURRENTPOWER = 7;
+    private static final int INSIGHT_POSITION_ENERGYTODAY = 8;
+    private static final int INSIGHT_POSITION_ENERGYTOTAL = 9;
+    private static final int INSIGHT_POSITION_STANDBYLIMIT = 10;
+
+    private final String value;
+
+    public InsightParser(String value) {
+        this.value = value;
+    }
+
+    /**
+     * Parse provided string of values.
+     *
+     * @return Map of channel id's with states
+     */
+    public Map<String, State> parse() {
+        HashMap<String, State> result = new HashMap<>();
+        String[] params = value.split("\\|");
+        for (int i = 0; i < params.length; i++) {
+            String value = params[i];
+            switch (i) {
+                case INSIGHT_POSITION_STATE:
+                    result.put(WemoBindingConstants.CHANNEL_STATE, getOnOff(value));
+                    break;
+                case INSIGHT_POSITION_LASTCHANGEDAT:
+                    result.put(WemoBindingConstants.CHANNEL_LASTCHANGEDAT, getDateTime(value));
+                    break;
+                case INSIGHT_POSITION_LASTONFOR:
+                    result.put(WemoBindingConstants.CHANNEL_LASTONFOR, getNumber(value));
+                    break;
+                case INSIGHT_POSITION_ONTODAY:
+                    result.put(WemoBindingConstants.CHANNEL_ONTODAY, getNumber(value));
+                    break;
+                case INSIGHT_POSITION_ONTOTAL:
+                    result.put(WemoBindingConstants.CHANNEL_ONTOTAL, getNumber(value));
+                    break;
+                case INSIGHT_POSITION_TIMESPAN:
+                    result.put(WemoBindingConstants.CHANNEL_TIMESPAN, getNumber(value));
+                    break;
+                case INSIGHT_POSITION_AVERAGEPOWER:
+                    result.put(WemoBindingConstants.CHANNEL_AVERAGEPOWER, getPowerFromWatt(value));
+                    break;
+                case INSIGHT_POSITION_CURRENTPOWER:
+                    result.put(WemoBindingConstants.CHANNEL_CURRENTPOWER, getPowerFromMilliWatt(value));
+                    break;
+                case INSIGHT_POSITION_ENERGYTODAY:
+                    result.put(WemoBindingConstants.CHANNEL_ENERGYTODAY, getEnergy(value));
+                    break;
+                case INSIGHT_POSITION_ENERGYTOTAL:
+                    result.put(WemoBindingConstants.CHANNEL_ENERGYTOTAL, getEnergy(value));
+                    break;
+                case INSIGHT_POSITION_STANDBYLIMIT:
+                    result.put(WemoBindingConstants.CHANNEL_STANDBYLIMIT, getPowerFromMilliWatt(value));
+                    break;
+            }
+        }
+        return result;
+    }
+
+    private State getOnOff(String value) {
+        return "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
+    }
+
+    private State getDateTime(String value) {
+        long lastChangedAt = 0;
+        try {
+            lastChangedAt = Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return UnDefType.UNDEF;
+        }
+
+        State lastChangedAtState = new DateTimeType(
+                ZonedDateTime.ofInstant(Instant.ofEpochSecond(lastChangedAt), ZoneId.systemDefault()));
+        if (lastChangedAt == 0) {
+            return UnDefType.UNDEF;
+        }
+        return lastChangedAtState;
+    }
+
+    private State getNumber(String value) {
+        try {
+            return DecimalType.valueOf(value);
+        } catch (NumberFormatException e) {
+            return UnDefType.UNDEF;
+        }
+    }
+
+    private State getPowerFromWatt(String value) {
+        return new QuantityType<>(DecimalType.valueOf(value), Units.WATT);
+    }
+
+    private State getPowerFromMilliWatt(String value) {
+        return new QuantityType<>(new BigDecimal(value).divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP),
+                Units.WATT);
+    }
+
+    private State getEnergy(String value) {
+        // recalculate mW-mins to Wh
+        return new QuantityType<>(new BigDecimal(value).divide(new BigDecimal(60_000), 0, RoundingMode.HALF_UP),
+                Units.WATT_HOUR);
+    }
+}
index 02ca4ef6ebc19f0ebef95840c1059eda209d7fd1..e028e7568d6f29749ca5c6d61086555f7425e9bb 100644 (file)
  */
 package org.openhab.binding.wemo.internal.handler;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.Instant;
-import java.time.ZonedDateTime;
 import java.util.Map;
-import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.wemo.internal.InsightParser;
 import org.openhab.binding.wemo.internal.WemoBindingConstants;
 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
 import org.openhab.core.io.transport.upnp.UpnpIOService;
-import org.openhab.core.library.types.DateTimeType;
-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.unit.Units;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.types.State;
@@ -71,94 +64,21 @@ public class WemoInsightHandler extends WemoHandler {
             String insightParams = stateMap.get(variable);
 
             if (insightParams != null) {
-                String[] splitInsightParams = insightParams.split("\\|");
-
-                if (splitInsightParams[0] != null) {
-                    OnOffType binaryState = "0".equals(splitInsightParams[0]) ? OnOffType.OFF : OnOffType.ON;
-                    logger.trace("New InsightParam binaryState '{}' for device '{}' received", binaryState,
-                            getThing().getUID());
-                    updateState(WemoBindingConstants.CHANNEL_STATE, binaryState);
-                }
-
-                long lastChangedAt = 0;
-                try {
-                    lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms
-                } catch (NumberFormatException e) {
-                    logger.warn("Unable to parse lastChangedAt value '{}' for device '{}'; expected long",
-                            splitInsightParams[1], getThing().getUID());
-                }
-                ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt),
-                        TimeZone.getDefault().toZoneId());
-
-                State lastChangedAtState = new DateTimeType(zoned);
-                if (lastChangedAt != 0) {
-                    logger.trace("New InsightParam lastChangedAt '{}' for device '{}' received", lastChangedAtState,
-                            getThing().getUID());
-                    updateState(WemoBindingConstants.CHANNEL_LASTCHANGEDAT, lastChangedAtState);
-                }
-
-                State lastOnFor = DecimalType.valueOf(splitInsightParams[2]);
-                logger.trace("New InsightParam lastOnFor '{}' for device '{}' received", lastOnFor,
-                        getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_LASTONFOR, lastOnFor);
-
-                State onToday = DecimalType.valueOf(splitInsightParams[3]);
-                logger.trace("New InsightParam onToday '{}' for device '{}' received", onToday, getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_ONTODAY, onToday);
-
-                State onTotal = DecimalType.valueOf(splitInsightParams[4]);
-                logger.trace("New InsightParam onTotal '{}' for device '{}' received", onTotal, getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_ONTOTAL, onTotal);
-
-                State timespan = DecimalType.valueOf(splitInsightParams[5]);
-                logger.trace("New InsightParam timespan '{}' for device '{}' received", timespan, getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_TIMESPAN, timespan);
-
-                State averagePower = new QuantityType<>(DecimalType.valueOf(splitInsightParams[6]), Units.WATT); // natively
-                                                                                                                 // given
-                                                                                                                 // in W
-                logger.trace("New InsightParam averagePower '{}' for device '{}' received", averagePower,
-                        getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_AVERAGEPOWER, averagePower);
-
-                BigDecimal currentMW = new BigDecimal(splitInsightParams[7]);
-                State currentPower = new QuantityType<>(currentMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP),
-                        Units.WATT); // recalculate
-                // mW to W
-                logger.trace("New InsightParam currentPower '{}' for device '{}' received", currentPower,
-                        getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_CURRENTPOWER, currentPower);
-
-                BigDecimal energyTodayMWMin = new BigDecimal(splitInsightParams[8]);
-                // recalculate mW-mins to Wh
-                State energyToday = new QuantityType<>(
-                        energyTodayMWMin.divide(new BigDecimal(60000), 0, RoundingMode.HALF_UP), Units.WATT_HOUR);
-                logger.trace("New InsightParam energyToday '{}' for device '{}' received", energyToday,
-                        getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_ENERGYTODAY, energyToday);
-
-                BigDecimal energyTotalMWMin = new BigDecimal(splitInsightParams[9]);
-                // recalculate mW-mins to Wh
-                State energyTotal = new QuantityType<>(
-                        energyTotalMWMin.divide(new BigDecimal(60000), 0, RoundingMode.HALF_UP), Units.WATT_HOUR);
-                logger.trace("New InsightParam energyTotal '{}' for device '{}' received", energyTotal,
-                        getThing().getUID());
-                updateState(WemoBindingConstants.CHANNEL_ENERGYTOTAL, energyTotal);
-
-                if (splitInsightParams.length > 10 && splitInsightParams[10] != null) {
-                    BigDecimal standByLimitMW = new BigDecimal(splitInsightParams[10]);
-                    State standByLimit = new QuantityType<>(
-                            standByLimitMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP), Units.WATT); // recalculate
-                    // mW to W
-                    logger.trace("New InsightParam standByLimit '{}' for device '{}' received", standByLimit,
+                InsightParser parser = new InsightParser(insightParams);
+                Map<String, State> results = parser.parse();
+                results.forEach((channel, state) -> {
+                    logger.trace("New InsightParam {} '{}' for device '{}' received", channel, state,
                             getThing().getUID());
-                    updateState(WemoBindingConstants.CHANNEL_STANDBYLIMIT, standByLimit);
-
-                    if (currentMW.divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue() > standByLimitMW
-                            .divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP).intValue()) {
-                        updateState(WemoBindingConstants.CHANNEL_ONSTANDBY, OnOffType.OFF);
-                    } else {
-                        updateState(WemoBindingConstants.CHANNEL_ONSTANDBY, OnOffType.ON);
+                    updateState(channel, state);
+                });
+
+                // Update helper channel onStandBy by checking if currentPower > standByLimit.
+                var standByLimit = (QuantityType<?>) results.get(WemoBindingConstants.CHANNEL_STANDBYLIMIT);
+                if (standByLimit != null) {
+                    var currentPower = (QuantityType<?>) results.get(WemoBindingConstants.CHANNEL_CURRENTPOWER);
+                    if (currentPower != null) {
+                        updateState(WemoBindingConstants.CHANNEL_ONSTANDBY,
+                                OnOffType.from(currentPower.intValue() <= standByLimit.intValue()));
                     }
                 }
             }
diff --git a/bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/InsightParserTest.java b/bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/InsightParserTest.java
new file mode 100644 (file)
index 0000000..fe5c0dc
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010-2022 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.wemo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.time.ZoneId;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.wemo.internal.InsightParser;
+import org.openhab.binding.wemo.internal.WemoBindingConstants;
+import org.openhab.core.library.types.DateTimeType;
+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.unit.Units;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * Unit tests for {@link InsightParser}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class InsightParserTest {
+
+    /**
+     * 'InsightParams' for subscription 'insight1'.
+     */
+    @Test
+    public void parseUpnpInsightParams() {
+        InsightParser parser = new InsightParser(
+                "1|1645800647|109676|80323|1196960|1209600|44|41400|30288361|483361410|8000");
+        Map<String, State> result = parser.parse();
+        assertEquals(OnOffType.ON, result.get(WemoBindingConstants.CHANNEL_STATE));
+        assertEquals(DateTimeType.valueOf("2022-02-25T15:50:47.000+0100").toZone(ZoneId.systemDefault()),
+                result.get(WemoBindingConstants.CHANNEL_LASTCHANGEDAT));
+        assertEquals(new DecimalType(109_676), result.get(WemoBindingConstants.CHANNEL_LASTONFOR));
+        assertEquals(new DecimalType(80_323), result.get(WemoBindingConstants.CHANNEL_ONTODAY));
+        assertEquals(new DecimalType(1_196_960), result.get(WemoBindingConstants.CHANNEL_ONTOTAL));
+        assertEquals(new DecimalType(1_209_600), result.get(WemoBindingConstants.CHANNEL_TIMESPAN));
+        assertEquals(new QuantityType<>(44, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGEPOWER));
+        assertEquals(new QuantityType<>(41, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENTPOWER));
+        assertEquals(new QuantityType<>(505, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTODAY));
+        assertEquals(new QuantityType<>(8056, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTOTAL));
+        assertEquals(new QuantityType<>(8, Units.WATT), result.get(WemoBindingConstants.CHANNEL_STANDBYLIMIT));
+    }
+
+    /**
+     * 'InsightParams' received from HTTP call. Format is a bit different: State can be non-binary,
+     * e.g. 8 for ON, and energy total is formatted with decimals.
+     */
+    @Test
+    public void parseHttpInsightParams() {
+        InsightParser parser = new InsightParser("8|1645967627|0|0|0|1209600|13|0|0|0.000000|8000");
+        Map<String, State> result = parser.parse();
+        assertEquals(OnOffType.ON, result.get(WemoBindingConstants.CHANNEL_STATE));
+        assertEquals(DateTimeType.valueOf("2022-02-27T14:13:47.000+0100").toZone(ZoneId.systemDefault()),
+                result.get(WemoBindingConstants.CHANNEL_LASTCHANGEDAT));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_LASTONFOR));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_ONTODAY));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_ONTOTAL));
+        assertEquals(new DecimalType(1_209_600), result.get(WemoBindingConstants.CHANNEL_TIMESPAN));
+        assertEquals(new QuantityType<>(13, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGEPOWER));
+        assertEquals(new QuantityType<>(0, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENTPOWER));
+        assertEquals(new QuantityType<>(0, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTODAY));
+        assertEquals(new QuantityType<>(0, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTOTAL));
+        assertEquals(new QuantityType<>(8, Units.WATT), result.get(WemoBindingConstants.CHANNEL_STANDBYLIMIT));
+    }
+
+    /**
+     * Some devices provide 'BinaryState' for subscription 'basicevent1'. This contains
+     * the same information as 'InsightParams' except last parameter (stand-by limit).
+     */
+    @Test
+    public void parseUpnpBinaryState() {
+        InsightParser parser = new InsightParser(
+                "1|1645800647|109676|80323|1196960|1209600|44|41400|30288361|483361410");
+        Map<String, State> result = parser.parse();
+        assertEquals(OnOffType.ON, result.get(WemoBindingConstants.CHANNEL_STATE));
+        assertEquals(DateTimeType.valueOf("2022-02-25T15:50:47.000+0100").toZone(ZoneId.systemDefault()),
+                result.get(WemoBindingConstants.CHANNEL_LASTCHANGEDAT));
+        assertEquals(new DecimalType(109_676), result.get(WemoBindingConstants.CHANNEL_LASTONFOR));
+        assertEquals(new DecimalType(80_323), result.get(WemoBindingConstants.CHANNEL_ONTODAY));
+        assertEquals(new DecimalType(1_196_960), result.get(WemoBindingConstants.CHANNEL_ONTOTAL));
+        assertEquals(new DecimalType(1_209_600), result.get(WemoBindingConstants.CHANNEL_TIMESPAN));
+        assertEquals(new QuantityType<>(44, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGEPOWER));
+        assertEquals(new QuantityType<>(41, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENTPOWER));
+        assertEquals(new QuantityType<>(505, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTODAY));
+        assertEquals(new QuantityType<>(8056, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGYTOTAL));
+        assertNull(result.get(WemoBindingConstants.CHANNEL_STANDBYLIMIT));
+    }
+
+    @Test
+    public void parseInvalidLastChangedAt() {
+        InsightParser parser = new InsightParser("1|A");
+        Map<String, State> result = parser.parse();
+        assertEquals(UnDefType.UNDEF, result.get(WemoBindingConstants.CHANNEL_LASTCHANGEDAT));
+    }
+
+    @Test
+    public void parseInvalidLastOnFor() {
+        InsightParser parser = new InsightParser("1|1645800647|A");
+        Map<String, State> result = parser.parse();
+        assertEquals(UnDefType.UNDEF, result.get(WemoBindingConstants.CHANNEL_LASTONFOR));
+    }
+}