[wemo] Prevent excessive currentPower channel updates (#12461)

* 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>
This commit is contained in:
Jacob Laursen 2022-04-02 12:46:26 +02:00 committed by GitHub
parent 115f5ab534
commit 1239dda691
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 602 additions and 182 deletions

View 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
}
}
```

View 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);
}

View 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_ON_STAND_BY = "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_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_LAST_CLEANED = "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_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_COOKMODE = "cookMode";
public static final String CHANNEL_LOWCOOKTIME = "lowCookTime";
public static final String CHANNEL_WARMCOOKTIME = "warmCooktime";
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;

View File

@ -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;
}
}

View File

@ -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;
}

View 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;
}

View 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);

View 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;
}
}

View 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");
}
}
@Override

View 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;
}
}

View File

@ -12,17 +12,23 @@
*/
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;
}
}

View 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);
}
}
}

View 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 '{}'",

View File

@ -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>

View 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

View File

@ -74,12 +74,20 @@
<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>

View 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>

View 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));
}
}

View File

@ -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);
}
}

View 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());
}