]> git.basschouten.com Git - openhab-addons.git/commitdiff
[wemo] Prevent excessive currentPower channel updates (#12461)
authorJacob Laursen <jacob-github@vindvejr.dk>
Sat, 2 Apr 2022 10:46:26 +0000 (12:46 +0200)
committerGitHub <noreply@github.com>
Sat, 2 Apr 2022 10:46:26 +0000 (12:46 +0200)
* Introduce algorithm for preventing excessive currentPower updates
* Increase calculation accuracy
* Rename currentPowerAccurate to currentPowerRaw
* Remove duplicated line
* Use interface when declaring double ended queue
* Reformat README to one sentence per line
* Rename constants for consistency and readability

Fixes #12460

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
20 files changed:
bundles/org.openhab.binding.wemo/README.md
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/InsightParser.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/WemoBindingConstants.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/WemoPowerBank.java [new file with mode: 0644]
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/config/WemoInsightConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCoffeeHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoCrockpotHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoDimmerHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoHolmesHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoInsightHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoMotionHandler.java
bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/handler/WemoSwitchHandler.java
bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/config/insight.xml [new file with mode: 0644]
bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/i18n/wemo.properties
bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/thing/channels.xml
bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/thing/insight.xml
bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/InsightParserTest.java
bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/WemoPowerBankTest.java [new file with mode: 0644]
itests/org.openhab.binding.wemo.tests/src/main/java/org/openhab/binding/wemo/internal/handler/test/WemoInsightHandlerTest.java

index 0bb98e837c2957f09f248b29bb0d8b64f4220904..8ebc22ce16a0229b021616d94fab1cfc77cea21d 100644 (file)
@@ -10,7 +10,8 @@ The Binding also supports the Crock-Pot Smart Slow Cooker, Mr. Coffee Smart Coff
 
 ## Discovery
 
-The WeMo devices are discovered through UPnP discovery service in the network. Devices will show up in the inbox and can be easily added as Things.
+The WeMo devices are discovered through UPnP discovery service in the network.
+Devices will show up in the inbox and can be easily added as Things.
 
 ## Binding Configuration
 
@@ -18,23 +19,48 @@ The binding does not need any configuration.
 
 ## Thing Configuration
 
-For manual Thing configuration, one needs to know the UUID of a certain WeMo device.
-In the thing file, this looks e.g. like
+For manual Thing configuration, one needs to know the UDN of a certain WeMo device.
+It can most easily be obtained by performing an auto-discovery before configuring the thing manually.
 
-```
-wemo:socket:Switch1 [udn="Socket-1_0-221242K11xxxxx"]
-```
+Most devices share the `udn` configuration parameter:
 
-For a WeMo Link bridge and paired LED Lights, please use the following Thing definition
+| Configuration Parameter | Description                        |
+|-------------------------|------------------------------------|
+| udn                     | The UDN identifies the WeMo device |
 
-```
-Bridge wemo:bridge:Bridge-1_0-231445B01006A0 [udn="Bridge-1_0-231445B010xxxx"] {
-MZ100 94103EA2B278xxxx [ deviceID="94103EA2B278xxxx" ]
-MZ100 94103EA2B278xxxx [ deviceID="94103EA2B278xxxx" ]
-}
-```
+### WeMo LED Light
+
+For LED Lights paired to a WeMo Link bridge, please use the following configuration parameter:
+
+| Configuration Parameter | Description                                     |
+|-------------------------|-------------------------------------------------|
+| deviceID                | The device ID identifies one certain WeMo light |
+
+### WeMo Insight Switch
 
+The WeMo Insight Switch has some additional parameters for controlling the behavior for channel `currentPower`.
+This channel reports the current power consumption in Watt.
+The internal theoretical accuracy is 5 mW, i.e. three decimals.
+These raw values are reported with high frequency, often multiple updates can occur within a single second.
+For example, the sequence of 40.440 W, 40.500 W and 40.485 W would result in the channel being updated with values rounded to nearest integer, respectively 40 W, 41 W and 40 W.
 
+When persisting items linked to this channel, this can result in a significant amount of data being stored.
+To mitigate this issue, a sliding window with a moving average calculation has been introduced.
+This window is defined with a one minute default period.
+This is combined with a delta trigger value, which is defaulted to 1 W.
+This means that the channel is only updated when one of the following conditions are met:
+
+1. The rounded value received is equal to the rounded average for the past minute, i.e. this value has stabilized. This introduces a delay for very small changes in consumption, but on the other hand it prevents excessive logging and persistence caused by temporary small changes and rounding.
+2. The rounded value received is more than 1 W from the previous value. So when changes are happening fast, the channel will also be updated fast.
+
+| Configuration Parameter    | Description                                                                           |
+|----------------------------|---------------------------------------------------------------------------------------|
+| udn                        | The UDN identifies the WeMo Insight Switch                                            |
+| currentPowerSlidingSeconds | Sliding window in seconds for which moving average power is calculated (0 = disabled) |
+| currentPowerDeltaTrigger   | Delta triggering immediate channel update (in Watt)                                   |
+
+The moving average calculation can be disabled by setting either `currentPowerSlidingSeconds` or `currentPowerDeltaTrigger` to 0.
+This will cause the channel to be updated the same way as in openHAB versions prior to 3.3.
 
 ## Channels
 
@@ -52,6 +78,7 @@ Devices support some of the following channels:
 | timespan            | Number        | Time in seconds over which onTotal applies. Typically 2 weeks except first used.                                           | Insight                                              |
 | averagePower        | Number:Power  | Average power consumption in Watts.                                                                                        | Insight                                              |
 | currentPower        | Number:Power  | Current power consumption of an Insight device. 0 if switched off.                                                         | Insight                                              |
+| currentPowerRaw     | Number:Power  | Current power consumption of an Insight device with full precision (5 mW accuracy, three decimals). 0 if switched off.     | Insight                                              |
 | energyToday         | Number:Energy | Energy in Wh used today.                                                                                                   | Insight                                              |
 | energyTotal         | Number:Energy | Energy in Wh used in total.                                                                                                | Insight                                              |
 | standbyLimit        | Number:Power  | Minimum energy draw in W to register device as switched on (default 8W, configurable via WeMo App).                        | Insight                                              |
@@ -94,7 +121,6 @@ Devices support some of the following channels:
 | autoOffTime         | DateTime      | Time when the heater switches off                                                                                          | Heater                                               |
 | heatingRemaining    | Number        | Shows the remaining heating time                                                                                           | Heater                                               |
 
-
 ## Full Example
 
 demo.things:
@@ -102,6 +128,7 @@ demo.things:
 ```
 wemo:socket:Switch1     "DemoSwitch"   @ "Office"   [udn="Socket-1_0-221242K11xxxxx"]
 wemo:motion:Sensor1     "MotionSensor" @ "Entrance" [udn="Sensor-1_0-221337L11xxxxx"]
+wemo:insight:Insight1   "Insight"      @ "Attic"    [udn="Insight-1_0-xxxxxxxxxxxxxx", currentPowerSlidingSeconds=120, currentPowerDeltaTrigger=2]
 
 Bridge wemo:bridge:Bridge-1_0-231445B010xxxx [udn="Bridge-1_0-231445B010xxxx"] {
 MZ100 94103EA2B278xxxx  "DemoLight1"   @ "Living"   [ deviceID="94103EA2B278xxxx" ]
@@ -185,7 +212,6 @@ Number currentTemp          { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:cu
 Number targetTemp           { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:targetTemp" }
 DateTime autoOffTime        { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:autoOffTime" }
 String heaterRemaining      { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:heaterRemaining" }
-
 ```
 
 demo.sitemap:
@@ -266,8 +292,6 @@ sitemap demo label="Main Menu"
        Setpoint item=targetTemp
        Text item=autoOffTime
        Number item=heaterRemaining
-
-
     }
 }
 ```
index c80f74d7c2bed275714b05da087f4649a37231be..ca89abfacb8b1fd3c622bf08632081d088a1b28a 100644 (file)
@@ -70,34 +70,34 @@ public class InsightParser {
                     result.put(WemoBindingConstants.CHANNEL_STATE, getOnOff(value));
                     break;
                 case INSIGHT_POSITION_LASTCHANGEDAT:
-                    result.put(WemoBindingConstants.CHANNEL_LASTCHANGEDAT, getDateTime(value));
+                    result.put(WemoBindingConstants.CHANNEL_LAST_CHANGED_AT, getDateTime(value));
                     break;
                 case INSIGHT_POSITION_LASTONFOR:
-                    result.put(WemoBindingConstants.CHANNEL_LASTONFOR, getNumber(value));
+                    result.put(WemoBindingConstants.CHANNEL_LAST_ON_FOR, getNumber(value));
                     break;
                 case INSIGHT_POSITION_ONTODAY:
-                    result.put(WemoBindingConstants.CHANNEL_ONTODAY, getNumber(value));
+                    result.put(WemoBindingConstants.CHANNEL_ON_TODAY, getNumber(value));
                     break;
                 case INSIGHT_POSITION_ONTOTAL:
-                    result.put(WemoBindingConstants.CHANNEL_ONTOTAL, getNumber(value));
+                    result.put(WemoBindingConstants.CHANNEL_ON_TOTAL, 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));
+                    result.put(WemoBindingConstants.CHANNEL_AVERAGE_POWER, getPowerFromWatt(value));
                     break;
                 case INSIGHT_POSITION_CURRENTPOWER:
-                    result.put(WemoBindingConstants.CHANNEL_CURRENTPOWER, getPowerFromMilliWatt(value));
+                    result.put(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW, getPowerFromMilliWatt(value));
                     break;
                 case INSIGHT_POSITION_ENERGYTODAY:
-                    result.put(WemoBindingConstants.CHANNEL_ENERGYTODAY, getEnergy(value));
+                    result.put(WemoBindingConstants.CHANNEL_ENERGY_TODAY, getEnergy(value));
                     break;
                 case INSIGHT_POSITION_ENERGYTOTAL:
-                    result.put(WemoBindingConstants.CHANNEL_ENERGYTOTAL, getEnergy(value));
+                    result.put(WemoBindingConstants.CHANNEL_ENERGY_TOTAL, getEnergy(value));
                     break;
                 case INSIGHT_POSITION_STANDBYLIMIT:
-                    result.put(WemoBindingConstants.CHANNEL_STANDBYLIMIT, getPowerFromMilliWatt(value));
+                    result.put(WemoBindingConstants.CHANNEL_STAND_BY_LIMIT, getPowerFromMilliWatt(value));
                     break;
             }
         }
@@ -137,7 +137,7 @@ public class InsightParser {
     }
 
     private State getPowerFromMilliWatt(String value) {
-        return new QuantityType<>(new BigDecimal(value).divide(new BigDecimal(1000), 0, RoundingMode.HALF_UP),
+        return new QuantityType<>(new BigDecimal(value).divide(new BigDecimal(1000), 3, RoundingMode.HALF_UP),
                 Units.WATT);
     }
 
index 48f3000680a52404ec76585cc40e6341345b9c9a..64706014cde425b8166edbbd1e2acda2e22b3121 100644 (file)
@@ -49,68 +49,69 @@ public class WemoBindingConstants {
 
     // List of all Channel ids
     public static final String CHANNEL_STATE = "state";
-    public static final String CHANNEL_MOTIONDETECTION = "motionDetection";
-    public static final String CHANNEL_LASTMOTIONDETECTED = "lastMotionDetected";
-    public static final String CHANNEL_LASTCHANGEDAT = "lastChangedAt";
-    public static final String CHANNEL_LASTONFOR = "lastOnFor";
-    public static final String CHANNEL_ONTODAY = "onToday";
-    public static final String CHANNEL_ONTOTAL = "onTotal";
+    public static final String CHANNEL_MOTION_DETECTION = "motionDetection";
+    public static final String CHANNEL_LAST_MOTION_DETECTED = "lastMotionDetected";
+    public static final String CHANNEL_LAST_CHANGED_AT = "lastChangedAt";
+    public static final String CHANNEL_LAST_ON_FOR = "lastOnFor";
+    public static final String CHANNEL_ON_TODAY = "onToday";
+    public static final String CHANNEL_ON_TOTAL = "onTotal";
     public static final String CHANNEL_TIMESPAN = "timespan";
-    public static final String CHANNEL_AVERAGEPOWER = "averagePower";
-    public static final String CHANNEL_CURRENTPOWER = "currentPower";
-    public static final String CHANNEL_ENERGYTODAY = "energyToday";
-    public static final String CHANNEL_ENERGYTOTAL = "energyTotal";
-    public static final String CHANNEL_STANDBYLIMIT = "standByLimit";
+    public static final String CHANNEL_AVERAGE_POWER = "averagePower";
+    public static final String CHANNEL_CURRENT_POWER = "currentPower";
+    public static final String CHANNEL_CURRENT_POWER_RAW = "currentPowerRaw";
+    public static final String CHANNEL_ENERGY_TODAY = "energyToday";
+    public static final String CHANNEL_ENERGY_TOTAL = "energyTotal";
+    public static final String CHANNEL_STAND_BY_LIMIT = "standByLimit";
     public static final String CHANNEL_BRIGHTNESS = "brightness";
     public static final String CHANNEL_RELAY = "relay";
     public static final String CHANNEL_SENSOR = "sensor";
-    public static final String CHANNEL_ONSTANDBY = "onStandBy";
-
-    public static final String CHANNEL_COFFEEMODE = "coffeeMode";
-    public static final String CHANNEL_MODETIME = "modeTime";
-    public static final String CHANNEL_TIMEREMAINING = "timeRemaining";
-    public static final String CHANNEL_WATERLEVELREACHED = "waterLevelReached";
-    public static final String CHANNEL_CLEANADVISE = "cleanAdvise";
-    public static final String CHANNEL_FILTERADVISE = "filterAdvise";
+    public static final String CHANNEL_ON_STAND_BY = "onStandBy";
+
+    public static final String CHANNEL_COFFEE_MODE = "coffeeMode";
+    public static final String CHANNEL_MODE_TIME = "modeTime";
+    public static final String CHANNEL_TIME_REMAINING = "timeRemaining";
+    public static final String CHANNEL_WATER_LEVEL_REACHED = "waterLevelReached";
+    public static final String CHANNEL_CLEAN_ADVISE = "cleanAdvise";
+    public static final String CHANNEL_FILTER_ADVISE = "filterAdvise";
     public static final String CHANNEL_BREWED = "brewed";
-    public static final String CHANNEL_LASTCLEANED = "lastCleaned";
-
-    public static final String CHANNEL_FADERENABLED = "faderEnabled";
-    public static final String CHANNEL_TIMERSTART = "timerStart";
-    public static final String CHANNEL_FADERCOUNTDOWNTIME = "faderCountDownTime";
-    public static final String CHANNEL_NIGHTMODE = "nightMode";
-    public static final String CHANNEL_STARTTIME = "startTime";
-    public static final String CHANNEL_ENDTIME = "endTime";
-    public static final String CHANNEL_NIGHTMODEBRIGHTNESS = "nightModeBrightness";
-
-    public static final String CHANNEL_COOKMODE = "cookMode";
-    public static final String CHANNEL_LOWCOOKTIME = "lowCookTime";
-    public static final String CHANNEL_WARMCOOKTIME = "warmCooktime";
+    public static final String CHANNEL_LAST_CLEANED = "lastCleaned";
+
+    public static final String CHANNEL_FADER_ENABLED = "faderEnabled";
+    public static final String CHANNEL_TIMER_START = "timerStart";
+    public static final String CHANNEL_FADER_COUNT_DOWN_TIME = "faderCountDownTime";
+    public static final String CHANNEL_NIGHT_MODE = "nightMode";
+    public static final String CHANNEL_START_TIME = "startTime";
+    public static final String CHANNEL_END_TIME = "endTime";
+    public static final String CHANNEL_NIGHT_MODE_BRIGHTNESS = "nightModeBrightness";
+
+    public static final String CHANNEL_COOK_MODE = "cookMode";
+    public static final String CHANNEL_LOW_COOK_TIME = "lowCookTime";
+    public static final String CHANNEL_WARM_COOK_TIME = "warmCooktime";
     public static final String CHANNEL_HIGHCOOKTIME = "highCooktime";
-    public static final String CHANNEL_COOKEDTIME = "cookedtime";
+    public static final String CHANNEL_COOKED_TIME = "cookedtime";
 
-    public static final String CHANNEL_PURIFIERMODE = "purifierMode";
-    public static final String CHANNEL_AIRQUALITY = "airQuality";
+    public static final String CHANNEL_PURIFIER_MODE = "purifierMode";
+    public static final String CHANNEL_AIR_QUALITY = "airQuality";
     public static final String CHANNEL_IONIZER = "ionizer";
-    public static final String CHANNEL_FILTERLIFE = "filterLife";
-    public static final String CHANNEL_EXPIREDFILTERTIME = "expiredFilterTime";
-    public static final String CHANNEL_FILTERPRESENT = "filterPresent";
+    public static final String CHANNEL_FILTER_LIFE = "filterLife";
+    public static final String CHANNEL_EXPIRED_FILTER_TIME = "expiredFilterTime";
+    public static final String CHANNEL_FILTER_PRESENT = "filterPresent";
 
-    public static final String CHANNEL_HUMIDIFIERMODE = "humidifierMode";
-    public static final String CHANNEL_CURRENTHUMIDITY = "currentHumidity";
-    public static final String CHANNEL_DESIREDHUMIDITY = "desiredHumidity";
-    public static final String CHANNEL_WATERLEVEL = "waterLEvel";
+    public static final String CHANNEL_HUMIDIFIER_MODE = "humidifierMode";
+    public static final String CHANNEL_CURRENT_HUMIDITY = "currentHumidity";
+    public static final String CHANNEL_DESIRED_HUMIDITY = "desiredHumidity";
+    public static final String CHANNEL_WATER_LEVEL = "waterLEvel";
 
-    public static final String CHANNEL_HEATERMODE = "heaterMode";
-    public static final String CHANNEL_CURRENTTEMP = "currentTemperature";
-    public static final String CHANNEL_TARGETTEMP = "targetTemperature";
-    public static final String CHANNEL_AUTOOFFTIME = "autoOffTime";
-    public static final String CHANNEL_HEATINGREMAINING = "heatingRemaining";
+    public static final String CHANNEL_HEATER_MODE = "heaterMode";
+    public static final String CHANNEL_CURRENT_TEMPERATURE = "currentTemperature";
+    public static final String CHANNEL_TARGET_TEMPERATURE = "targetTemperature";
+    public static final String CHANNEL_AUTO_OFF_TIME = "autoOffTime";
+    public static final String CHANNEL_HEATING_REMAINING = "heatingRemaining";
 
     // List of thing configuration properties
     public static final String UDN = "udn";
     public static final String DEVICE_ID = "deviceID";
-    public static final String POLLINGINTERVALL = "pollingInterval";
+    public static final String POLLING_INTERVAL = "pollingInterval";
     public static final int DEFAULT_REFRESH_INTERVAL_SECONDS = 60;
     public static final int SUBSCRIPTION_DURATION_SECONDS = 600;
     public static final int LINK_DISCOVERY_SERVICE_INITIAL_DELAY = 5;
diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/WemoPowerBank.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/WemoPowerBank.java
new file mode 100644 (file)
index 0000000..e4d2796
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * 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.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.QuantityType;
+
+/**
+ * Class for caching and processing historic values for current power.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class WemoPowerBank {
+
+    private final Deque<CacheItem> slidingCache = new ConcurrentLinkedDeque<CacheItem>();
+
+    @Nullable
+    private QuantityType<?> previousCurrentPower = null;
+    private int slidingSeconds;
+
+    private class CacheItem {
+        public Instant start;
+        public @Nullable Instant end;
+        public double power;
+
+        public CacheItem(double power, Instant start) {
+            this.start = start;
+            this.power = power;
+        }
+    }
+
+    public WemoPowerBank() {
+        this.slidingSeconds = 60;
+    }
+
+    public WemoPowerBank(int slidingSeconds) {
+        this.slidingSeconds = slidingSeconds;
+    }
+
+    public void clear() {
+        slidingCache.clear();
+        previousCurrentPower = null;
+    }
+
+    public void apply(double value) {
+        this.apply(value, Instant.now());
+    }
+
+    public void apply(double value, Instant now) {
+        if (slidingCache.isEmpty()) {
+            slidingCache.add(new CacheItem(value, now));
+            return;
+        }
+        @Nullable
+        CacheItem last = slidingCache.getLast();
+        last.end = now;
+        Instant windowStart = now.minusSeconds(slidingSeconds);
+        final Iterator<CacheItem> it = slidingCache.iterator();
+        while (it.hasNext()) {
+            CacheItem current = it.next();
+            Instant end = current.end;
+            end = end != null ? end.minusNanos(1) : now;
+            if (end.isBefore(windowStart)) {
+                it.remove();
+                continue;
+            }
+            if (current.start.isBefore(windowStart) && end.isAfter(windowStart)) {
+                // Truncate last item before sliding window.
+                current.start = windowStart;
+                break;
+            }
+        }
+        slidingCache.add(new CacheItem(value, now));
+    }
+
+    public void setPreviousCurrentPower(QuantityType<?> previousCurrentPower) {
+        this.previousCurrentPower = previousCurrentPower;
+    }
+
+    public @Nullable QuantityType<?> getPreviousCurrentPower() {
+        return previousCurrentPower;
+    }
+
+    public double getCalculatedAverage(double currentValue) {
+        double historyWattMillis = 0;
+        long historyMillis = 0;
+        for (CacheItem item : slidingCache) {
+            Instant end = item.end;
+            if (end != null) {
+                long millis = item.start.until(end, ChronoUnit.MILLIS);
+                historyWattMillis += item.power * millis;
+                historyMillis += millis;
+            }
+        }
+        double average;
+        if (historyMillis > 0) {
+            average = historyWattMillis / historyMillis;
+        } else {
+            average = currentValue;
+        }
+
+        return average;
+    }
+}
diff --git a/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/config/WemoInsightConfiguration.java b/bundles/org.openhab.binding.wemo/src/main/java/org/openhab/binding/wemo/internal/config/WemoInsightConfiguration.java
new file mode 100644 (file)
index 0000000..3cd949a
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * 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.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Configuration for a WeMo Insight Switch
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class WemoInsightConfiguration {
+
+    public static final String CURRENT_POWER_SLIDING_SECONDS = "currentPowerSlidingSeconds";
+    public static final String CURRENT_POWER_DELTA_TRIGGER = "currentPowerDeltaTrigger";
+
+    @Nullable
+    public String udn;
+    public int currentPowerSlidingSeconds = 60;
+    public int currentPowerDeltaTrigger = 1;
+}
index 7714ac2c7b0ab4e2d64ffc1304b4e2275b22e496..bd16e5bf4e1f132c401c98411f70fbf8441acd92 100644 (file)
@@ -161,7 +161,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
                         wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
                         updateState(CHANNEL_STATE, OnOffType.ON);
                         State newMode = new StringType("Brewing");
-                        updateState(CHANNEL_COFFEEMODE, newMode);
+                        updateState(CHANNEL_COFFEE_MODE, newMode);
                         updateStatus(ThingStatus.ONLINE);
                     } catch (Exception e) {
                         logger.warn("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
@@ -244,69 +244,69 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
                                 case "0":
                                     updateState(CHANNEL_STATE, OnOffType.ON);
                                     newMode = new StringType("Refill");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "1":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("PlaceCarafe");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "2":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("RefillWater");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "3":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("Ready");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "4":
                                     updateState(CHANNEL_STATE, OnOffType.ON);
                                     newMode = new StringType("Brewing");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "5":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("Brewed");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "6":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("CleaningBrewing");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "7":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("CleaningSoaking");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                                 case "8":
                                     updateState(CHANNEL_STATE, OnOffType.OFF);
                                     newMode = new StringType("BrewFailCarafeRemoved");
-                                    updateState(CHANNEL_COFFEEMODE, newMode);
+                                    updateState(CHANNEL_COFFEE_MODE, newMode);
                                     break;
                             }
                             break;
                         case "ModeTime":
                             newAttributeValue = new DecimalType(attributeValue);
-                            updateState(CHANNEL_MODETIME, newAttributeValue);
+                            updateState(CHANNEL_MODE_TIME, newAttributeValue);
                             break;
                         case "TimeRemaining":
                             newAttributeValue = new DecimalType(attributeValue);
-                            updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
+                            updateState(CHANNEL_TIME_REMAINING, newAttributeValue);
                             break;
                         case "WaterLevelReached":
                             newAttributeValue = new DecimalType(attributeValue);
-                            updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
+                            updateState(CHANNEL_WATER_LEVEL_REACHED, newAttributeValue);
                             break;
                         case "CleanAdvise":
                             newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
-                            updateState(CHANNEL_CLEANADVISE, newAttributeValue);
+                            updateState(CHANNEL_CLEAN_ADVISE, newAttributeValue);
                             break;
                         case "FilterAdvise":
                             newAttributeValue = "0".equals(attributeValue) ? OnOffType.OFF : OnOffType.ON;
-                            updateState(CHANNEL_FILTERADVISE, newAttributeValue);
+                            updateState(CHANNEL_FILTER_ADVISE, newAttributeValue);
                             break;
                         case "Brewed":
                             newAttributeValue = getDateTimeState(attributeValue);
@@ -317,7 +317,7 @@ public class WemoCoffeeHandler extends WemoBaseThingHandler {
                         case "LastCleaned":
                             newAttributeValue = getDateTimeState(attributeValue);
                             if (newAttributeValue != null) {
-                                updateState(CHANNEL_LASTCLEANED, newAttributeValue);
+                                updateState(CHANNEL_LAST_CLEANED, newAttributeValue);
                             }
                             break;
                     }
index 435d544180d00cae01f4c30ac8ea5362662cc744..a2e76beea1882fbe01b3da9ce623e0a166a19b7a 100644 (file)
@@ -128,7 +128,7 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
 
         if (command instanceof RefreshType) {
             updateWemoState();
-        } else if (CHANNEL_COOKMODE.equals(channelUID.getId())) {
+        } else if (CHANNEL_COOK_MODE.equals(channelUID.getId())) {
             String commandString = command.toString();
             switch (commandString) {
                 case "OFF":
@@ -202,12 +202,12 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
                 case "50":
                     newMode = new StringType("WARM");
                     State warmTime = DecimalType.valueOf(time);
-                    updateState(CHANNEL_WARMCOOKTIME, warmTime);
+                    updateState(CHANNEL_WARM_COOK_TIME, warmTime);
                     break;
                 case "51":
                     newMode = new StringType("LOW");
                     State lowTime = DecimalType.valueOf(time);
-                    updateState(CHANNEL_LOWCOOKTIME, lowTime);
+                    updateState(CHANNEL_LOW_COOK_TIME, lowTime);
                     break;
                 case "52":
                     newMode = new StringType("HIGH");
@@ -215,8 +215,8 @@ public class WemoCrockpotHandler extends WemoBaseThingHandler {
                     updateState(CHANNEL_HIGHCOOKTIME, highTime);
                     break;
             }
-            updateState(CHANNEL_COOKMODE, newMode);
-            updateState(CHANNEL_COOKEDTIME, newCoockedTime);
+            updateState(CHANNEL_COOK_MODE, newMode);
+            updateState(CHANNEL_COOKED_TIME, newCoockedTime);
             updateStatus(ThingStatus.ONLINE);
         } catch (IOException e) {
             logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
index 4d43e87d97b39c9a7adc3d0fbcf065f71d2357f9..846fecb9f7bbc4c68d6aa9dc5e22438971f5bf0a 100644 (file)
@@ -152,7 +152,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         if (command.equals(OnOffType.OFF)) {
                             State brightnessState = new PercentType("0");
                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
-                            updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
+                            updateState(CHANNEL_TIMER_START, OnOffType.OFF);
                         } else {
                             State brightnessState = new PercentType(currentBrightness);
                             updateState(CHANNEL_BRIGHTNESS, brightnessState);
@@ -211,7 +211,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         setBinaryState(action, argument, value);
                     }
                     break;
-                case CHANNEL_FADERCOUNTDOWNTIME:
+                case CHANNEL_FADER_COUNT_DOWN_TIME:
                     argument = "Fader";
                     if (command instanceof DecimalType) {
                         int commandValue = Integer.valueOf(String.valueOf(command));
@@ -223,7 +223,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         setBinaryState(action, argument, value);
                     }
                     break;
-                case CHANNEL_FADERENABLED:
+                case CHANNEL_FADER_ENABLED:
                     argument = "Fader";
                     if (command.equals(OnOffType.ON)) {
                         value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
@@ -234,7 +234,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                     }
                     setBinaryState(action, argument, value);
                     break;
-                case CHANNEL_TIMERSTART:
+                case CHANNEL_TIMER_START:
                     argument = "Fader";
                     long ts = System.currentTimeMillis() / 1000;
                     timeStamp = String.valueOf(ts);
@@ -265,7 +265,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                     }
                     setBinaryState(action, argument, value);
                     break;
-                case CHANNEL_NIGHTMODE:
+                case CHANNEL_NIGHT_MODE:
                     action = "ConfigureNightMode";
                     argument = "NightModeConfiguration";
                     String nightModeBrightness = String.valueOf(currentNightModeBrightness);
@@ -278,7 +278,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                     }
                     setBinaryState(action, argument, value);
                     break;
-                case CHANNEL_NIGHTMODEBRIGHTNESS:
+                case CHANNEL_NIGHT_MODE_BRIGHTNESS:
                     action = "ConfigureNightMode";
                     argument = "NightModeConfiguration";
                     if (command instanceof PercentType) {
@@ -334,7 +334,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
                         updateState(CHANNEL_BRIGHTNESS, state);
                         if (state.equals(OnOffType.OFF)) {
-                            updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
+                            updateState(CHANNEL_TIMER_START, OnOffType.OFF);
                         }
                     }
                     break;
@@ -358,13 +358,13 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         State faderMinutes = new DecimalType(faderSeconds / 60);
                         logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes,
                                 getThing().getUID());
-                        updateState(CHANNEL_FADERCOUNTDOWNTIME, faderMinutes);
+                        updateState(CHANNEL_FADER_COUNT_DOWN_TIME, faderMinutes);
                     }
                     if (splitFader[1] != null) {
                         State isTimerRunning = splitFader[1].equals("-1") ? OnOffType.OFF : OnOffType.ON;
                         logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning,
                                 getThing().getUID());
-                        updateState(CHANNEL_TIMERSTART, isTimerRunning);
+                        updateState(CHANNEL_TIMER_START, isTimerRunning);
                         if (isTimerRunning.equals(OnOffType.ON)) {
                             updateState(CHANNEL_STATE, OnOffType.ON);
                         }
@@ -373,27 +373,27 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                         State isFaderEnabled = splitFader[1].equals("0") ? OnOffType.OFF : OnOffType.ON;
                         logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled,
                                 getThing().getUID());
-                        updateState(CHANNEL_FADERENABLED, isFaderEnabled);
+                        updateState(CHANNEL_FADER_ENABLED, isFaderEnabled);
                     }
                     break;
                 case "nightMode":
                     State nightModeState = "0".equals(value) ? OnOffType.OFF : OnOffType.ON;
                     currentNightModeState = value;
                     logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
-                    updateState(CHANNEL_NIGHTMODE, nightModeState);
+                    updateState(CHANNEL_NIGHT_MODE, nightModeState);
                     break;
                 case "startTime":
                     State startTimeState = getDateTimeState(value);
                     logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
                     if (startTimeState != null) {
-                        updateState(CHANNEL_STARTTIME, startTimeState);
+                        updateState(CHANNEL_START_TIME, startTimeState);
                     }
                     break;
                 case "endTime":
                     State endTimeState = getDateTimeState(value);
                     logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
                     if (endTimeState != null) {
-                        updateState(CHANNEL_ENDTIME, endTimeState);
+                        updateState(CHANNEL_END_TIME, endTimeState);
                     }
                     break;
                 case "nightModeBrightness":
@@ -402,7 +402,7 @@ public class WemoDimmerHandler extends WemoBaseThingHandler {
                     State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
                     logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
                             getThing().getUID());
-                    updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
+                    updateState(CHANNEL_NIGHT_MODE_BRIGHTNESS, nightModeBrightnessState);
                     break;
             }
         }
index 4e16cce218771f416badc77d72c28c4478185153..ae84e0806e79539740474999a4eca0ea46967ad8 100644 (file)
@@ -21,7 +21,6 @@ import java.util.concurrent.TimeUnit;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
-import org.openhab.core.config.core.Configuration;
 import org.openhab.core.io.transport.upnp.UpnpIOService;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.thing.ChannelUID;
@@ -62,21 +61,13 @@ public abstract class WemoHandler extends WemoBaseThingHandler {
     @Override
     public void initialize() {
         super.initialize();
-        Configuration configuration = getConfig();
 
-        if (configuration.get(UDN) != null) {
-            logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get(UDN));
-            addSubscription(BASICEVENT);
-            if (THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) {
-                addSubscription(INSIGHTEVENT);
-            }
-            pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
-                    TimeUnit.SECONDS);
-            updateStatus(ThingStatus.UNKNOWN);
-        } else {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                    "@text/config-status.error.missing-udn");
+        addSubscription(BASICEVENT);
+        if (THING_TYPE_INSIGHT.equals(thing.getThingTypeUID())) {
+            addSubscription(INSIGHTEVENT);
         }
+        pollingJob = scheduler.scheduleWithFixedDelay(this::poll, 0, DEFAULT_REFRESH_INTERVAL_SECONDS,
+                TimeUnit.SECONDS);
     }
 
     @Override
index 041707008b233bc7dd779bbd3603dfab9dc48779..5802c6c55eea0a94cb12ee138448a2ec7cff2a13 100644 (file)
@@ -143,7 +143,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
 
         if (command instanceof RefreshType) {
             updateWemoState();
-        } else if (CHANNEL_PURIFIERMODE.equals(channelUID.getId())) {
+        } else if (CHANNEL_PURIFIER_MODE.equals(channelUID.getId())) {
             attribute = "Mode";
             String commandString = command.toString();
             switch (commandString) {
@@ -170,7 +170,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
             } else if (OnOffType.OFF.equals(command)) {
                 value = "0";
             }
-        } else if (CHANNEL_HUMIDIFIERMODE.equals(channelUID.getId())) {
+        } else if (CHANNEL_HUMIDIFIER_MODE.equals(channelUID.getId())) {
             attribute = "FanMode";
             String commandString = command.toString();
             switch (commandString) {
@@ -193,7 +193,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                     value = "5";
                     break;
             }
-        } else if (CHANNEL_DESIREDHUMIDITY.equals(channelUID.getId())) {
+        } else if (CHANNEL_DESIRED_HUMIDITY.equals(channelUID.getId())) {
             attribute = "DesiredHumidity";
             String commandString = command.toString();
             switch (commandString) {
@@ -213,7 +213,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                     value = "4";
                     break;
             }
-        } else if (CHANNEL_HEATERMODE.equals(channelUID.getId())) {
+        } else if (CHANNEL_HEATER_MODE.equals(channelUID.getId())) {
             attribute = "Mode";
             String commandString = command.toString();
             switch (commandString) {
@@ -233,7 +233,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                     value = "4";
                     break;
             }
-        } else if (CHANNEL_TARGETTEMP.equals(channelUID.getId())) {
+        } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
             attribute = "SetTemperature";
             value = command.toString();
         }
@@ -341,7 +341,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                     newMode = new StringType("AUTO");
                                     break;
                             }
-                            updateState(CHANNEL_PURIFIERMODE, newMode);
+                            updateState(CHANNEL_PURIFIER_MODE, newMode);
                         } else {
                             switch (attributeValue) {
                                 case "0":
@@ -360,7 +360,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                     newMode = new StringType("ECO");
                                     break;
                             }
-                            updateState(CHANNEL_HEATERMODE, newMode);
+                            updateState(CHANNEL_HEATER_MODE, newMode);
                         }
                         break;
                     case "Ionizer":
@@ -386,7 +386,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                 newMode = new StringType("GOOD");
                                 break;
                         }
-                        updateState(CHANNEL_AIRQUALITY, newMode);
+                        updateState(CHANNEL_AIR_QUALITY, newMode);
                         break;
                     case "FilterLife":
                         int filterLife = Integer.valueOf(attributeValue);
@@ -395,7 +395,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                         } else {
                             filterLife = Math.round((filterLife / 60480) * 100);
                         }
-                        updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife)));
+                        updateState(CHANNEL_FILTER_LIFE, new PercentType(String.valueOf(filterLife)));
                         break;
                     case "ExpiredFilterTime":
                         switch (attributeValue) {
@@ -406,7 +406,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                 newMode = OnOffType.ON;
                                 break;
                         }
-                        updateState(CHANNEL_EXPIREDFILTERTIME, newMode);
+                        updateState(CHANNEL_EXPIRED_FILTER_TIME, newMode);
                         break;
                     case "FilterPresent":
                         switch (attributeValue) {
@@ -417,7 +417,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                 newMode = OnOffType.ON;
                                 break;
                         }
-                        updateState(CHANNEL_FILTERPRESENT, newMode);
+                        updateState(CHANNEL_FILTER_PRESENT, newMode);
                         break;
                     case "FANMode":
                         switch (attributeValue) {
@@ -437,7 +437,7 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                 newMode = new StringType("AUTO");
                                 break;
                         }
-                        updateState(CHANNEL_PURIFIERMODE, newMode);
+                        updateState(CHANNEL_PURIFIER_MODE, newMode);
                         break;
                     case "DesiredHumidity":
                         switch (attributeValue) {
@@ -457,27 +457,27 @@ public class WemoHolmesHandler extends WemoBaseThingHandler {
                                 newMode = new PercentType("100");
                                 break;
                         }
-                        updateState(CHANNEL_DESIREDHUMIDITY, newMode);
+                        updateState(CHANNEL_DESIRED_HUMIDITY, newMode);
                         break;
                     case "CurrentHumidity":
                         newMode = new StringType(attributeValue);
-                        updateState(CHANNEL_CURRENTHUMIDITY, newMode);
+                        updateState(CHANNEL_CURRENT_HUMIDITY, newMode);
                         break;
                     case "Temperature":
                         newMode = new StringType(attributeValue);
-                        updateState(CHANNEL_CURRENTTEMP, newMode);
+                        updateState(CHANNEL_CURRENT_TEMPERATURE, newMode);
                         break;
                     case "SetTemperature":
                         newMode = new StringType(attributeValue);
-                        updateState(CHANNEL_TARGETTEMP, newMode);
+                        updateState(CHANNEL_TARGET_TEMPERATURE, newMode);
                         break;
                     case "AutoOffTime":
                         newMode = new StringType(attributeValue);
-                        updateState(CHANNEL_AUTOOFFTIME, newMode);
+                        updateState(CHANNEL_AUTO_OFF_TIME, newMode);
                         break;
                     case "TimeRemaining":
                         newMode = new StringType(attributeValue);
-                        updateState(CHANNEL_HEATINGREMAINING, newMode);
+                        updateState(CHANNEL_HEATING_REMAINING, newMode);
                         break;
                 }
             }
index e028e7568d6f29749ca5c6d61086555f7425e9bb..19ad6e2538bef57921e3fd274720536e9f44e6e1 100644 (file)
  */
 package org.openhab.binding.wemo.internal.handler;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Map;
+import java.util.Map.Entry;
 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.WemoPowerBank;
+import org.openhab.binding.wemo.internal.config.WemoInsightConfiguration;
 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
 import org.openhab.core.io.transport.upnp.UpnpIOService;
 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;
@@ -41,10 +47,33 @@ public class WemoInsightHandler extends WemoHandler {
     private final Logger logger = LoggerFactory.getLogger(WemoInsightHandler.class);
     private final Map<String, String> stateMap = new ConcurrentHashMap<String, String>();
 
+    private WemoPowerBank wemoPowerBank = new WemoPowerBank();
+    private int currentPowerSlidingSeconds;
+    private int currentPowerDeltaTrigger;
+
     public WemoInsightHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
         super(thing, upnpIOService, wemoHttpCaller);
     }
 
+    @Override
+    public void initialize() {
+        logger.debug("Initializing WemoInsightHandler for thing '{}'", thing.getUID());
+
+        WemoInsightConfiguration configuration = getConfigAs(WemoInsightConfiguration.class);
+        currentPowerSlidingSeconds = configuration.currentPowerSlidingSeconds;
+        currentPowerDeltaTrigger = configuration.currentPowerDeltaTrigger;
+        wemoPowerBank = new WemoPowerBank(currentPowerSlidingSeconds);
+
+        updateStatus(ThingStatus.UNKNOWN);
+        super.initialize();
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        wemoPowerBank.clear();
+    }
+
     @Override
     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
@@ -66,22 +95,79 @@ public class WemoInsightHandler extends WemoHandler {
             if (insightParams != null) {
                 InsightParser parser = new InsightParser(insightParams);
                 Map<String, State> results = parser.parse();
-                results.forEach((channel, state) -> {
+                for (Entry<String, State> entry : results.entrySet()) {
+                    String channel = entry.getKey();
+                    State state = entry.getValue();
+
                     logger.trace("New InsightParam {} '{}' for device '{}' received", channel, state,
                             getThing().getUID());
                     updateState(channel, state);
-                });
+                    if (channel.equals(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW)
+                            && state instanceof QuantityType) {
+                        QuantityType<?> power = state.as(QuantityType.class);
+                        if (power != null) {
+                            updateCurrentPower(power);
+                        }
+                    }
+                }
 
                 // Update helper channel onStandBy by checking if currentPower > standByLimit.
-                var standByLimit = (QuantityType<?>) results.get(WemoBindingConstants.CHANNEL_STANDBYLIMIT);
+                var standByLimit = (QuantityType<?>) results.get(WemoBindingConstants.CHANNEL_STAND_BY_LIMIT);
                 if (standByLimit != null) {
-                    var currentPower = (QuantityType<?>) results.get(WemoBindingConstants.CHANNEL_CURRENTPOWER);
+                    QuantityType<?> currentPower = wemoPowerBank.getPreviousCurrentPower();
                     if (currentPower != null) {
-                        updateState(WemoBindingConstants.CHANNEL_ONSTANDBY,
+                        updateState(WemoBindingConstants.CHANNEL_ON_STAND_BY,
                                 OnOffType.from(currentPower.intValue() <= standByLimit.intValue()));
                     }
                 }
             }
         }
     }
+
+    private boolean updateCurrentPower(QuantityType<?> power) {
+        double value = power.doubleValue();
+        var roundedValueState = new QuantityType<>(new BigDecimal(value).setScale(0, RoundingMode.HALF_UP),
+                power.getUnit());
+        if (currentPowerSlidingSeconds == 0 || currentPowerDeltaTrigger == 0) {
+            updateState(WemoBindingConstants.CHANNEL_CURRENT_POWER, roundedValueState);
+            return true;
+        }
+
+        wemoPowerBank.apply(value);
+        double averageValue = wemoPowerBank.getCalculatedAverage(value);
+
+        var roundedAverageValueState = new QuantityType<>(
+                new BigDecimal(averageValue).setScale(0, RoundingMode.HALF_UP), power.getUnit());
+
+        if (roundedValueState.equals(wemoPowerBank.getPreviousCurrentPower())) {
+            // No change, skip.
+            return false;
+        }
+
+        double roundedValue = roundedValueState.doubleValue();
+        QuantityType<?> previousCurrentPower = wemoPowerBank.getPreviousCurrentPower();
+
+        if (previousCurrentPower == null) {
+            // Always update initially.
+            return updateCurrentPowerBalanced(roundedValue);
+        }
+        double previousRoundedValue = previousCurrentPower.doubleValue();
+        if (roundedValue < previousRoundedValue - currentPowerDeltaTrigger
+                || roundedValue > previousRoundedValue + currentPowerDeltaTrigger) {
+            // Update immediately when delta is > 1 W.
+            return updateCurrentPowerBalanced(roundedValue);
+        }
+        if (roundedValueState.equals(roundedAverageValueState)) {
+            // Update when rounded value has stabilized.
+            return updateCurrentPowerBalanced(roundedValue);
+        }
+        return false;
+    }
+
+    private boolean updateCurrentPowerBalanced(double power) {
+        var state = new QuantityType<>(power, Units.WATT);
+        updateState(WemoBindingConstants.CHANNEL_CURRENT_POWER, state);
+        wemoPowerBank.setPreviousCurrentPower(state);
+        return true;
+    }
 }
index 032bab278cc9ea97e0121ac2a053fb8bb5464f34..f51bd09af5dc76ed2f1d5876c6792433e6758bfc 100644 (file)
@@ -44,6 +44,13 @@ public class WemoMotionHandler extends WemoHandler {
         super(thing, upnpIOService, wemoHttpCaller);
     }
 
+    @Override
+    public void initialize() {
+        logger.debug("Initializing WemoMotionHandler for thing '{}'", thing.getUID());
+        updateStatus(ThingStatus.UNKNOWN);
+        super.initialize();
+    }
+
     @Override
     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
@@ -66,10 +73,10 @@ public class WemoMotionHandler extends WemoHandler {
                 if (oldValue == null || !oldValue.equals(binaryState)) {
                     State state = "0".equals(binaryState) ? OnOffType.OFF : OnOffType.ON;
                     logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
-                    updateState(WemoBindingConstants.CHANNEL_MOTIONDETECTION, state);
+                    updateState(WemoBindingConstants.CHANNEL_MOTION_DETECTION, state);
                     if (OnOffType.ON.equals(state)) {
                         State lastMotionDetected = new DateTimeType();
-                        updateState(WemoBindingConstants.CHANNEL_LASTMOTIONDETECTED, lastMotionDetected);
+                        updateState(WemoBindingConstants.CHANNEL_LAST_MOTION_DETECTED, lastMotionDetected);
                     }
                 }
             }
index 79df99db7e83f15b433f5600ce70007d0ee8aa94..99c6668efbe68af87a177b7b77b19509bbae3244 100644 (file)
@@ -43,6 +43,13 @@ public class WemoSwitchHandler extends WemoHandler {
         super(thing, upnpIOService, wemoHttpCaller);
     }
 
+    @Override
+    public void initialize() {
+        logger.debug("Initializing WemoSwitchHandler for thing '{}'", thing.getUID());
+        updateStatus(ThingStatus.UNKNOWN);
+        super.initialize();
+    }
+
     @Override
     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
         logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
diff --git a/bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/config/insight.xml b/bundles/org.openhab.binding.wemo/src/main/resources/OH-INF/config/insight.xml
new file mode 100644 (file)
index 0000000..1a6efd1
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
+               https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="thing-type:wemo:insight">
+               <parameter name="udn" type="text" required="true">
+                       <label>Unique Device Name</label>
+                       <description>The UDN identifies the WeMo Insight Switch</description>
+               </parameter>
+               <parameter name="currentPowerSlidingSeconds" type="integer" min="0" unit="s">
+                       <label>Current Power sliding window</label>
+                       <description>Sliding window in seconds for which moving average power is calculated (0 = disabled)</description>
+                       <unitLabel>seconds</unitLabel>
+                       <default>60</default>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="currentPowerDeltaTrigger" type="integer" min="0" unit="W">
+                       <label>Current Power delta trigger</label>
+                       <description>Delta triggering immediate channel update (in Watt)</description>
+                       <unitLabel>Watt</unitLabel>
+                       <default>1</default>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
index e69a1e5554f9ca68df7ad2a1f00cc5ac0c52383e..0ee306b6ed25434d66141d4606a0bdbdd968dd2e 100644 (file)
@@ -44,6 +44,12 @@ thing-type.config.wemo.bridge.udn.label = Unique Device Name
 thing-type.config.wemo.bridge.udn.description = The UDN identifies the WeMo Link Device
 thing-type.config.wemo.device.udn.label = Unique Device Name
 thing-type.config.wemo.device.udn.description = The UDN identifies the WeMo Device
+thing-type.config.wemo.insight.currentPowerDeltaTrigger.label = Current Power delta trigger
+thing-type.config.wemo.insight.currentPowerDeltaTrigger.description = Delta triggering immediate channel update (in Watt)
+thing-type.config.wemo.insight.currentPowerSlidingSeconds.label = Current Power sliding window
+thing-type.config.wemo.insight.currentPowerSlidingSeconds.description = Sliding window in seconds for which moving average power is calculated (0 = disabled)
+thing-type.config.wemo.insight.udn.label = Unique Device Name
+thing-type.config.wemo.insight.udn.description = The UDN identifies the WeMo Insight Switch
 
 # channel types
 
@@ -81,8 +87,10 @@ channel-type.wemo.cookedTime.label = CookedTime
 channel-type.wemo.cookedTime.description = Shows the elapsed cooking time
 channel-type.wemo.currentHumidity.label = Current Humidity
 channel-type.wemo.currentHumidity.description = Shows the current humidity of a WeMo enabled Holmes Humidifier
-channel-type.wemo.currentPower.label = Power
+channel-type.wemo.currentPower.label = Current Power
 channel-type.wemo.currentPower.description = The current power consumption
+channel-type.wemo.currentPowerRaw.label = Current Power Raw
+channel-type.wemo.currentPowerRaw.description = The current power consumption with full precision
 channel-type.wemo.currentTemperature.label = Current Temperature
 channel-type.wemo.currentTemperature.description = Shows the current temperature measured by a WeMo enabled Heater
 channel-type.wemo.desiredHumidity.label = Target Humidity
index 62aeed076a372fb2cd02fa777a757bc2f6d8178e..ffb2bab9d95f0be35192af2160811f83b97c47ca 100644 (file)
 
        <channel-type id="currentPower">
                <item-type>Number:Power</item-type>
-               <label>Power</label>
+               <label>Current Power</label>
                <description>The current power consumption</description>
                <category>Energy</category>
                <state pattern="%.0f %unit%"/>
        </channel-type>
 
+       <channel-type id="currentPowerRaw" advanced="true">
+               <item-type>Number:Power</item-type>
+               <label>Current Power Raw</label>
+               <description>The current power consumption with full precision</description>
+               <category>Energy</category>
+               <state pattern="%.3f %unit%"/>
+       </channel-type>
+
        <channel-type id="energyToday" advanced="true">
                <item-type>Number:Energy</item-type>
                <label>Energy Today</label>
index 6485462d724b096d16691fd059155e6096bb8e5c..6d3fa3e7489b11c61710555f5ab07cc4cee60418 100644 (file)
@@ -17,6 +17,7 @@
                        <channel id="timespan" typeId="timespan"/>
                        <channel id="averagePower" typeId="averagePower"/>
                        <channel id="currentPower" typeId="currentPower"/>
+                       <channel id="currentPowerRaw" typeId="currentPowerRaw"/>
                        <channel id="energyToday" typeId="energyToday"/>
                        <channel id="energyTotal" typeId="energyTotal"/>
                        <channel id="standByLimit" typeId="standByLimit"/>
@@ -25,7 +26,7 @@
 
                <representation-property>udn</representation-property>
 
-               <config-description-ref uri="thing-type:wemo:device"/>
+               <config-description-ref uri="thing-type:wemo:insight"/>
        </thing-type>
 
 </thing:thing-descriptions>
index fe5c0dc6e8b7ae3666060c4027fad41a6ae57832..1937a43bcf308b3c612dbb8d5e3dc8eeb42eb938 100644 (file)
@@ -48,16 +48,16 @@ public class InsightParserTest {
         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));
+                result.get(WemoBindingConstants.CHANNEL_LAST_CHANGED_AT));
+        assertEquals(new DecimalType(109_676), result.get(WemoBindingConstants.CHANNEL_LAST_ON_FOR));
+        assertEquals(new DecimalType(80_323), result.get(WemoBindingConstants.CHANNEL_ON_TODAY));
+        assertEquals(new DecimalType(1_196_960), result.get(WemoBindingConstants.CHANNEL_ON_TOTAL));
         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));
+        assertEquals(new QuantityType<>(44, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGE_POWER));
+        assertEquals(new QuantityType<>(41.4, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW));
+        assertEquals(new QuantityType<>(505, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TODAY));
+        assertEquals(new QuantityType<>(8056, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TOTAL));
+        assertEquals(new QuantityType<>(8, Units.WATT), result.get(WemoBindingConstants.CHANNEL_STAND_BY_LIMIT));
     }
 
     /**
@@ -70,16 +70,16 @@ public class InsightParserTest {
         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));
+                result.get(WemoBindingConstants.CHANNEL_LAST_CHANGED_AT));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_LAST_ON_FOR));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_ON_TODAY));
+        assertEquals(new DecimalType(0), result.get(WemoBindingConstants.CHANNEL_ON_TOTAL));
         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));
+        assertEquals(new QuantityType<>(13, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGE_POWER));
+        assertEquals(new QuantityType<>(0, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW));
+        assertEquals(new QuantityType<>(0, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TODAY));
+        assertEquals(new QuantityType<>(0, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TOTAL));
+        assertEquals(new QuantityType<>(8, Units.WATT), result.get(WemoBindingConstants.CHANNEL_STAND_BY_LIMIT));
     }
 
     /**
@@ -93,29 +93,29 @@ public class InsightParserTest {
         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));
+                result.get(WemoBindingConstants.CHANNEL_LAST_CHANGED_AT));
+        assertEquals(new DecimalType(109_676), result.get(WemoBindingConstants.CHANNEL_LAST_ON_FOR));
+        assertEquals(new DecimalType(80_323), result.get(WemoBindingConstants.CHANNEL_ON_TODAY));
+        assertEquals(new DecimalType(1_196_960), result.get(WemoBindingConstants.CHANNEL_ON_TOTAL));
         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));
+        assertEquals(new QuantityType<>(44, Units.WATT), result.get(WemoBindingConstants.CHANNEL_AVERAGE_POWER));
+        assertEquals(new QuantityType<>(41.4, Units.WATT), result.get(WemoBindingConstants.CHANNEL_CURRENT_POWER_RAW));
+        assertEquals(new QuantityType<>(505, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TODAY));
+        assertEquals(new QuantityType<>(8056, Units.WATT_HOUR), result.get(WemoBindingConstants.CHANNEL_ENERGY_TOTAL));
+        assertNull(result.get(WemoBindingConstants.CHANNEL_STAND_BY_LIMIT));
     }
 
     @Test
     public void parseInvalidLastChangedAt() {
         InsightParser parser = new InsightParser("1|A");
         Map<String, State> result = parser.parse();
-        assertEquals(UnDefType.UNDEF, result.get(WemoBindingConstants.CHANNEL_LASTCHANGEDAT));
+        assertEquals(UnDefType.UNDEF, result.get(WemoBindingConstants.CHANNEL_LAST_CHANGED_AT));
     }
 
     @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));
+        assertEquals(UnDefType.UNDEF, result.get(WemoBindingConstants.CHANNEL_LAST_ON_FOR));
     }
 }
diff --git a/bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/WemoPowerBankTest.java b/bundles/org.openhab.binding.wemo/src/test/java/org/openhab/binding/wemo/WemoPowerBankTest.java
new file mode 100644 (file)
index 0000000..53ca008
--- /dev/null
@@ -0,0 +1,103 @@
+/**
+ * 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 java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.wemo.internal.WemoPowerBank;
+
+/**
+ * Unit tests for {@link WemoPowerBank}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class WemoPowerBankTest {
+
+    @Test
+    public void getCalculatedAverageOneMinuteEvenLoad() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(22, getInstantOf("2022-03-08T22:00:00Z"));
+        bank.apply(23, getInstantOf("2022-03-08T22:00:30Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:01:00Z"));
+
+        assertEquals(22.5, bank.getCalculatedAverage(0));
+    }
+
+    @Test
+    public void getCalculatedAverageOlderValuesAreIgnored() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(99, getInstantOf("2022-03-08T21:59:59Z"));
+        bank.apply(22, getInstantOf("2022-03-08T22:00:00Z"));
+        bank.apply(23, getInstantOf("2022-03-08T22:00:30Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:01:00Z"));
+
+        assertEquals(22.5, bank.getCalculatedAverage(0));
+    }
+
+    @Test
+    public void getCalculatedAveragePreviousValueBeforeWindowIsConsidered() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(22, getInstantOf("2022-03-08T21:59:59Z"));
+        bank.apply(23, getInstantOf("2022-03-08T22:00:30Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:01:00Z"));
+
+        assertEquals(22.5, bank.getCalculatedAverage(0));
+    }
+
+    @Test
+    public void getCalculatedAverageOneMinuteUnevenLoad() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(20, getInstantOf("2022-03-08T22:00:00Z"));
+        bank.apply(26, getInstantOf("2022-03-08T22:00:20Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:01:00Z"));
+
+        assertEquals(24, bank.getCalculatedAverage(0));
+    }
+
+    @Test
+    public void getCalculatedAverageSingleValue() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(20, getInstantOf("2022-03-08T22:00:00Z"));
+
+        assertEquals(50, bank.getCalculatedAverage(50));
+    }
+
+    @Test
+    public void getCalculatedAverageDuplicateInstants() {
+        var bank = new WemoPowerBank();
+
+        bank.apply(22, getInstantOf("2022-03-08T22:00:00Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:00:30Z"));
+        bank.apply(23, getInstantOf("2022-03-08T22:00:30Z"));
+        bank.apply(99, getInstantOf("2022-03-08T22:01:00Z"));
+
+        assertEquals(22.5, bank.getCalculatedAverage(0));
+    }
+
+    private Instant getInstantOf(String time) {
+        Clock clock = Clock.fixed(Instant.parse(time), ZoneId.of("UTC"));
+        return Instant.now(clock);
+    }
+}
index cf04469b96ab66e71091e4bed9e1d90a1d6c56e1..a54265398fa2a70f3acc790a3f1b515945438f0f 100644 (file)
@@ -89,7 +89,7 @@ public class WemoInsightHandlerTest {
     public void assertThatChannelLASTONFORIsUpdatedOnReceivedValue() {
         insightParams.lastOnFor = TIME_PARAM;
         State expectedStateType = new DecimalType(TIME_PARAM);
-        String expectedChannel = CHANNEL_LASTONFOR;
+        String expectedChannel = CHANNEL_LAST_ON_FOR;
 
         testOnValueReceived(expectedChannel, expectedStateType, insightParams.toString());
     }
@@ -98,7 +98,7 @@ public class WemoInsightHandlerTest {
     public void assertThatChannelONTODAYIsUpdatedOnReceivedValue() {
         insightParams.onToday = TIME_PARAM;
         State expectedStateType = new DecimalType(TIME_PARAM);
-        String expectedChannel = CHANNEL_ONTODAY;
+        String expectedChannel = CHANNEL_ON_TODAY;
 
         testOnValueReceived(expectedChannel, expectedStateType, insightParams.toString());
     }
@@ -107,7 +107,7 @@ public class WemoInsightHandlerTest {
     public void assertThatChannelONTOTALIsUpdatedOnReceivedValue() {
         insightParams.onTotal = TIME_PARAM;
         State expectedStateType = new DecimalType(TIME_PARAM);
-        String expectedChannel = CHANNEL_ONTOTAL;
+        String expectedChannel = CHANNEL_ON_TOTAL;
 
         testOnValueReceived(expectedChannel, expectedStateType, insightParams.toString());
     }
@@ -125,7 +125,7 @@ public class WemoInsightHandlerTest {
     public void assertThatChannelAVERAGEPOWERIsUpdatedOnReceivedValue() {
         insightParams.avgPower = POWER_PARAM;
         State expectedStateType = new QuantityType<>(POWER_PARAM, Units.WATT);
-        String expectedChannel = CHANNEL_AVERAGEPOWER;
+        String expectedChannel = CHANNEL_AVERAGE_POWER;
 
         testOnValueReceived(expectedChannel, expectedStateType, insightParams.toString());
     }