[miele] Migrate start channel to full DateTime channel and add end channel (#13393)

* Migrate start/finish channels to full DateTime channels
* Unmark start and duration as advanced channels
* Add end channel

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-09-25 11:29:26 +02:00 committed by GitHub
parent 96b77ed541
commit af0ac6e474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 344 additions and 152 deletions

View File

@ -125,6 +125,7 @@ Channels available for each appliance type are listed below.
| phase | String | Read | Current phase of the program running on the appliance |
| rawPhase | Number | Read | Current phase of the program running on the appliance as raw number |
| start | DateTime | Read | Programmed start time of the program |
| end | DateTime | Read | End time of the program (programmed or running) |
| duration | DateTime | Read | Duration of the program running on the appliance |
| elapsed | DateTime | Read | Time elapsed in the program running on the appliance |
| finish | DateTime | Read | Time to finish the program running on the appliance |
@ -237,6 +238,7 @@ Channels available for each appliance type are listed below.
| phase | String | Read | Current phase of the program running on the appliance |
| rawPhase | Number | Read | Current phase of the program running on the appliance as raw number |
| start | DateTime | Read | Programmed start time of the program |
| end | DateTime | Read | End time of the program (programmed or running) |
| duration | DateTime | Read | Duration of the program running on the appliance |
| elapsed | DateTime | Read | Time elapsed in the program running on the appliance |
| finish | DateTime | Read | Time to finish the program running on the appliance |
@ -279,6 +281,7 @@ See oven.
| phase | String | Read | Current phase of the program running on the appliance |
| rawPhase | Number | Read | Current phase of the program running on the appliance as raw number |
| start | DateTime | Read | Programmed start time of the program |
| end | DateTime | Read | End time of the program (programmed or running) |
| duration | DateTime | Read | Duration of the program running on the appliance |
| elapsed | DateTime | Read | Time elapsed in the program running on the appliance |
| finish | DateTime | Read | Time to finish the program running on the appliance |
@ -343,6 +346,7 @@ See oven.
| phase | String | Read | Current phase of the program running on the appliance |
| rawPhase | Number | Read | Current phase of the program running on the appliance as raw number |
| start | DateTime | Read | Programmed start time of the program |
| end | DateTime | Read | End time of the program (programmed or running) |
| duration | DateTime | Read | Duration of the program running on the appliance |
| elapsed | DateTime | Read | Time elapsed in the program running on the appliance |
| finish | DateTime | Read | Time to finish the program running on the appliance |

View File

@ -42,6 +42,8 @@ public class MieleBindingConstants {
public static final String PROGRAM_ID_PROPERTY_NAME = "programId";
public static final String PHASE_PROPERTY_NAME = "phase";
public static final String RAW_PHASE_PROPERTY_NAME = "rawPhase";
public static final String START_TIME_PROPERTY_NAME = "startTime";
public static final String FINISH_TIME_PROPERTY_NAME = "finishTime";
// Shared Channel ID's
public static final String STATE_TEXT_CHANNEL_ID = "state";
@ -53,6 +55,9 @@ public class MieleBindingConstants {
public static final String SUPERCOOL_CHANNEL_ID = "supercool";
public static final String SUPERFREEZE_CHANNEL_ID = "superfreeze";
public static final String SWITCH_CHANNEL_ID = "switch";
public static final String START_CHANNEL_ID = "start";
public static final String END_CHANNEL_ID = "end";
public static final String FINISH_CHANNEL_ID = "finish";
public static final String POWER_CONSUMPTION_CHANNEL_ID = "powerConsumption";
public static final String WATER_CONSUMPTION_CHANNEL_ID = "waterConsumption";

View File

@ -39,6 +39,7 @@ import org.openhab.binding.miele.internal.handler.WashingMachineHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
@ -73,16 +74,18 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider;
private final TimeZoneProvider timeZoneProvider;
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Activate
public MieleHandlerFactory(@Reference final HttpClientFactory httpClientFactory,
final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider,
ComponentContext componentContext) {
final @Reference TimeZoneProvider timeZoneProvider, ComponentContext componentContext) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider;
this.timeZoneProvider = timeZoneProvider;
}
@Override
@ -113,31 +116,31 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
return handler;
} else if (MieleApplianceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
if (thing.getThingTypeUID().equals(THING_TYPE_HOOD)) {
return new HoodHandler(thing, i18nProvider, localeProvider);
return new HoodHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_FRIDGEFREEZER)) {
return new FridgeFreezerHandler(thing, i18nProvider, localeProvider);
return new FridgeFreezerHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_FRIDGE)) {
return new FridgeHandler(thing, i18nProvider, localeProvider);
return new FridgeHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_OVEN)) {
return new OvenHandler(thing, i18nProvider, localeProvider);
return new OvenHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_HOB)) {
return new HobHandler(thing, i18nProvider, localeProvider);
return new HobHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_WASHINGMACHINE)) {
return new WashingMachineHandler(thing, i18nProvider, localeProvider);
return new WashingMachineHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_DRYER)) {
return new TumbleDryerHandler(thing, i18nProvider, localeProvider);
return new TumbleDryerHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_DISHWASHER)) {
return new DishWasherHandler(thing, i18nProvider, localeProvider);
return new DishWasherHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
if (thing.getThingTypeUID().equals(THING_TYPE_COFFEEMACHINE)) {
return new CoffeeMachineHandler(thing, i18nProvider, localeProvider);
return new CoffeeMachineHandler(thing, i18nProvider, localeProvider, timeZoneProvider);
}
}

View File

@ -0,0 +1,104 @@
/**
* 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.miele.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;
/**
* {@link TimeStabilizer} keeps a cache of historic timestamp values in order to
* provide moving average calculations to smooth out short-term fluctuations.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class TimeStabilizer {
private final static int SLIDING_SECONDS = 300;
private final static int MAX_FLUCTUATION_SECONDS = 180;
private final Deque<Item> cache = new ConcurrentLinkedDeque<Item>();
private class Item {
public Instant start;
public Instant end;
public Instant instant;
public Item(Instant instant, Instant start, Instant end) {
this.start = start;
this.end = end;
this.instant = instant;
}
}
public TimeStabilizer() {
}
public void clear() {
cache.clear();
}
public Instant apply(Instant value) {
return this.apply(value, Instant.now());
}
public Instant apply(Instant value, Instant now) {
if (cache.isEmpty()) {
cache.add(new Item(value, now, now));
return value;
}
@Nullable
Item first = cache.getFirst();
@Nullable
Item last = cache.getLast();
last.end = now;
Instant windowStart = now.minusSeconds(SLIDING_SECONDS);
Instant start = first.start;
Instant base = first.instant;
long weightedDiffFromBase = 0;
final Iterator<Item> it = cache.iterator();
while (it.hasNext()) {
Item current = it.next();
if (current.end.isBefore(windowStart)) {
it.remove();
continue;
}
if (current.start.isBefore(windowStart) && current.end.isAfter(windowStart)) {
// Truncate last item before sliding window.
start = current.start = windowStart;
}
long secs = current.start.until(current.end, ChronoUnit.SECONDS);
weightedDiffFromBase += base.until(current.instant, ChronoUnit.SECONDS) * secs;
}
Instant average = base.plusSeconds(weightedDiffFromBase / start.until(now, ChronoUnit.SECONDS));
if (value.isBefore(average.minusSeconds(MAX_FLUCTUATION_SECONDS))
|| value.isAfter(average.plusSeconds(MAX_FLUCTUATION_SECONDS))) {
cache.clear();
average = value;
}
cache.add(new Item(value, now, now));
return average;
}
}

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -42,8 +43,9 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class);
public CoffeeMachineHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, CoffeeMachineChannelSelector.class,
public CoffeeMachineHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, CoffeeMachineChannelSelector.class,
MIELE_DEVICE_CLASS_COFFEE_SYSTEM);
}

View File

@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
@ -54,8 +55,10 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
private final Logger logger = LoggerFactory.getLogger(DishWasherHandler.class);
public DishWasherHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, DishwasherChannelSelector.class, MIELE_DEVICE_CLASS_DISHWASHER);
public DishWasherHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, DishwasherChannelSelector.class,
MIELE_DEVICE_CLASS_DISHWASHER);
}
@Override

View File

@ -73,20 +73,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
START_TIME("", START_CHANNEL_ID, DateTimeType.class, false, false),
END_TIME("", END_CHANNEL_ID, DateTimeType.class, false, false),
DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
@ -115,20 +103,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("", FINISH_CHANNEL_ID, DateTimeType.class, false, false),
DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -42,8 +43,9 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class);
public FridgeFreezerHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, FridgeFreezerChannelSelector.class,
public FridgeFreezerHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, FridgeFreezerChannelSelector.class,
MIELE_DEVICE_CLASS_FRIDGE_FREEZER);
}

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -43,8 +44,10 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class);
public FridgeHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, FridgeChannelSelector.class, MIELE_DEVICE_CLASS_FRIDGE);
public FridgeHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, FridgeChannelSelector.class,
MIELE_DEVICE_CLASS_FRIDGE);
}
@Override

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEV
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
@ -31,8 +32,9 @@ import org.openhab.core.types.Command;
@NonNullByDefault
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, HobChannelSelector.class, MIELE_DEVICE_CLASS_HOB);
public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, HobChannelSelector.class, MIELE_DEVICE_CLASS_HOB);
}
@Override

View File

@ -17,6 +17,7 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEV
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -41,8 +42,10 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
public HoodHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, HoodChannelSelector.class, MIELE_DEVICE_CLASS_HOOD);
public HoodHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, HoodChannelSelector.class,
MIELE_DEVICE_CLASS_HOOD);
}
@Override

View File

@ -14,6 +14,10 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.IllformedLocaleException;
import java.util.Locale;
@ -24,12 +28,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.TimeStabilizer;
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
@ -43,6 +50,7 @@ import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -79,19 +87,25 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
protected TranslationProvider i18nProvider;
protected LocaleProvider localeProvider;
protected MieleTranslationProvider translationProvider;
private TimeZoneProvider timeZoneProvider;
private TimeStabilizer startTimeStabilizer;
private TimeStabilizer finishTimeStabilizer;
private Class<E> selectorType;
protected String modelID;
protected Map<String, String> metaDataCache = new HashMap<>();
public MieleApplianceHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
Class<E> selectorType, String modelID) {
TimeZoneProvider timeZoneProvider, Class<E> selectorType, String modelID) {
super(thing);
this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider;
this.selectorType = selectorType;
this.modelID = modelID;
this.translationProvider = new MieleTranslationProvider(i18nProvider, localeProvider);
this.timeZoneProvider = timeZoneProvider;
this.startTimeStabilizer = new TimeStabilizer();
this.finishTimeStabilizer = new TimeStabilizer();
}
public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
@ -180,6 +194,8 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
applianceId = null;
}
startTimeStabilizer.clear();
finishTimeStabilizer.clear();
}
@Override
@ -242,6 +258,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
}
ThingUID thingUid = getThing().getUID();
if (EXTENDED_DEVICE_STATE_PROPERTY_NAME.equals(dp.Name)) {
if (!dp.Value.isEmpty()) {
byte[] extendedStateBytes = DeviceUtil.stringToBytes(dp.Value);
@ -252,6 +269,13 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
}
return;
} else if (START_TIME_PROPERTY_NAME.equals(dp.Name)) {
updateStateFromTime(new ChannelUID(thingUid, START_CHANNEL_ID), dp.Value, startTimeStabilizer);
return;
} else if (FINISH_TIME_PROPERTY_NAME.equals(dp.Name)) {
updateDurationState(new ChannelUID(thingUid, FINISH_CHANNEL_ID), dp.Value);
updateStateFromTime(new ChannelUID(thingUid, END_CHANNEL_ID), dp.Value, finishTimeStabilizer);
return;
}
ApplianceChannelSelector selector = null;
@ -265,7 +289,6 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (selector != null) {
String channelId = selector.getChannelID();
ThingUID thingUid = getThing().getUID();
State state = selector.getState(dpValue, dmd, this.translationProvider);
if (selector.isProperty()) {
String value = state.toString();
@ -289,6 +312,40 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
updateState(channelUid, state);
}
private void updateStateFromTime(ChannelUID channelUid, String value, TimeStabilizer stabilizer) {
try {
long minutesFromNow = Long.valueOf(value);
if (minutesFromNow > 0) {
Instant rawTime = Instant.now().truncatedTo(ChronoUnit.MINUTES).plusSeconds(minutesFromNow * 60);
ZonedDateTime correctedTime = stabilizer.apply(rawTime).atZone(timeZoneProvider.getTimeZone());
ZonedDateTime truncatedTime = correctedTime.truncatedTo(ChronoUnit.MINUTES);
logger.trace("Update state of {} from {} -> '{}' -> '{}' to '{}'", channelUid, minutesFromNow, rawTime,
correctedTime, truncatedTime);
updateState(channelUid, new DateTimeType(truncatedTime));
return;
}
} catch (NumberFormatException e) {
// Fall through.
}
updateState(channelUid, UnDefType.UNDEF);
stabilizer.clear();
}
private void updateDurationState(ChannelUID channelUid, String value) {
try {
long minutesFromNow = Long.valueOf(value);
if (minutesFromNow > 0) {
ZonedDateTime remaining = ZonedDateTime.ofInstant(Instant.ofEpochSecond(minutesFromNow * 60),
ZoneId.of("UTC"));
updateState(channelUid, new DateTimeType(remaining.withZoneSameLocal(timeZoneProvider.getTimeZone())));
return;
}
} catch (NumberFormatException e) {
// Fall through.
}
updateState(channelUid, UnDefType.UNDEF);
}
protected void updateSwitchOnOffFromState(DeviceProperty dp) {
if (!STATE_PROPERTY_NAME.equals(dp.Name)) {
return;

View File

@ -68,20 +68,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
START_TIME("", START_CHANNEL_ID, DateTimeType.class, false),
END_TIME("", END_CHANNEL_ID, DateTimeType.class, false),
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
@ -110,20 +98,7 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("", FINISH_CHANNEL_ID, DateTimeType.class, false),
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -43,8 +44,10 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
public OvenHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, OvenChannelSelector.class, MIELE_DEVICE_CLASS_OVEN);
public OvenHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, OvenChannelSelector.class,
MIELE_DEVICE_CLASS_OVEN);
}
@Override

View File

@ -73,20 +73,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
START_TIME("", START_CHANNEL_ID, DateTimeType.class, false),
END_TIME("", END_CHANNEL_ID, DateTimeType.class, false),
DURATION("duration", "duration", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
@ -115,20 +103,7 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("", FINISH_CHANNEL_ID, DateTimeType.class, false),
DRYING_STEP("dryingStep", "step", DecimalType.class, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -43,8 +44,10 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
public TumbleDryerHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, TumbleDryerChannelSelector.class, MIELE_DEVICE_CLASS_TUMBLE_DRYER);
public TumbleDryerHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, TumbleDryerChannelSelector.class,
MIELE_DEVICE_CLASS_TUMBLE_DRYER);
}
@Override

View File

@ -74,20 +74,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}
},
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
START_TIME("", START_CHANNEL_ID, DateTimeType.class, false, false),
END_TIME("", END_CHANNEL_ID, DateTimeType.class, false, false),
DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
@ -116,20 +104,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {
Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(s) * 60000);
} catch (Exception e) {
date.setTime(0);
}
return getState(dateFormatter.format(date));
}
},
FINISH_TIME("", FINISH_CHANNEL_ID, DateTimeType.class, false, false),
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false, false) {
@Override
public State getState(String s, @Nullable DeviceMetaData dmd, MieleTranslationProvider translationProvider) {

View File

@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
@ -54,8 +55,9 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
private final Logger logger = LoggerFactory.getLogger(WashingMachineHandler.class);
public WashingMachineHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
super(thing, i18nProvider, localeProvider, WashingMachineChannelSelector.class,
public WashingMachineHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider,
TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, localeProvider, timeZoneProvider, WashingMachineChannelSelector.class,
MIELE_DEVICE_CLASS_WASHING_MACHINE);
}

View File

@ -68,6 +68,8 @@ channel-type.miele.duration.state.pattern = %1$tH:%1$tM
channel-type.miele.elapsed.label = Elapsed Time
channel-type.miele.elapsed.description = Time elapsed in the program running on the appliance
channel-type.miele.elapsed.state.pattern = %1$tH:%1$tM
channel-type.miele.end.label = End Time
channel-type.miele.end.description = End time of the program (programmed or running)
channel-type.miele.finish.label = Finish Time
channel-type.miele.finish.description = Time to finish the program running on the appliance
channel-type.miele.finish.state.pattern = %1$tH:%1$tM
@ -97,7 +99,6 @@ channel-type.miele.spinningspeed.label = Spinning Speed
channel-type.miele.spinningspeed.description = Spinning speed in the program running on the appliance
channel-type.miele.start.label = Start Time
channel-type.miele.start.description = Programmed start time of the program
channel-type.miele.start.state.pattern = %1$tH:%1$tM
channel-type.miele.state.label = State
channel-type.miele.state.description = Current status of the appliance
channel-type.miele.step.label = Step
@ -132,7 +133,7 @@ offline.configuration-error.invalid-ip-multicast-interface = Invalid IP address
offline.configuration-error.invalid-language = Invalid language: {0}
offline.configuration-error.uid-not-set = Appliance ID is not set
# Discovery result
# discovery result
discovery.xgw3000.label = Miele XGW 3000
@ -170,7 +171,6 @@ miele.program.dishwasher.extra-quiet = Extra Quiet
miele.program.dishwasher.hygiene = Hygiene
miele.program.dishwasher.quickpowerwash = QuickPowerWash
miele.program.dishwasher.tall-items = Tall items
miele.program.tumbledryer.automatic-plus = Automatic Plus
miele.program.tumbledryer.cottons = Cottons
miele.program.tumbledryer.cottons-hygiene = Cottons hygiene
@ -196,7 +196,6 @@ miele.program.tumbledryer.basket-programme = Basket programme
miele.program.tumbledryer.smoothing = Smoothing
miele.program.tumbledryer.cottons-auto-load-control = Cottons, auto load control
miele.program.tumbledryer.minimum-iron-auto-load-control = Minimum iron, auto load control
miele.program.washingmachine.cottons = Cottons
miele.program.washingmachine.minimum-iron = Minimum iron
miele.program.washingmachine.delicates = Delicates
@ -233,7 +232,6 @@ miele.phase.dishwasher.rinses = Rinses
miele.phase.dishwasher.final-rinse = Final rinse
miele.phase.dishwasher.drying = Drying
miele.phase.dishwasher.finished = Finished
miele.phase.oven.heating = Heating
miele.phase.oven.temp-hold = Temp. hold
miele.phase.oven.door-open = Door Open
@ -243,7 +241,6 @@ miele.phase.oven.searing-phase = Searing phase
miele.phase.oven.defrost = Defrost
miele.phase.oven.cooling-down = Cooling down
miele.phase.oven.energy-save-phase = Energy save phase
miele.phase.tumbledryer.programme-running = Programme running
miele.phase.tumbledryer.drying = Drying
miele.phase.tumbledryer.drying-machine-iron = Drying Machine iron
@ -252,7 +249,6 @@ miele.phase.tumbledryer.drying-normal = Drying Normal
miele.phase.tumbledryer.drying-normal-plus = Drying Normal+
miele.phase.tumbledryer.cooling-down = Cooling down
miele.phase.tumbledryer.finished = Finished
miele.phase.washingmachine.pre-wash = Pre-wash
miele.phase.washingmachine.washing = Washing
miele.phase.washingmachine.rinses = Rinses

View File

@ -53,15 +53,23 @@
<state readOnly="true"></state>
</channel-type>
<channel-type id="start" advanced="true">
<channel-type id="start">
<item-type>DateTime</item-type>
<label>Start Time</label>
<description>Programmed start time of the program</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tH:%1$tM"></state>
<state readOnly="true"></state>
</channel-type>
<channel-type id="duration" advanced="true">
<channel-type id="end">
<item-type>DateTime</item-type>
<label>End Time</label>
<description>End time of the program (programmed or running)</description>
<category>Time</category>
<state readOnly="true"></state>
</channel-type>
<channel-type id="duration">
<item-type>DateTime</item-type>
<label>Duration</label>
<description>Duration of the program running on the appliance</description>

View File

@ -21,6 +21,7 @@
<channel id="phase" typeId="phase"/>
<channel id="rawPhase" typeId="rawPhase"/>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>

View File

@ -22,6 +22,7 @@
<channel id="phase" typeId="phase"/>
<channel id="rawPhase" typeId="rawPhase"/>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>

View File

@ -22,6 +22,7 @@
<channel id="phase" typeId="phase"/>
<channel id="rawPhase" typeId="rawPhase"/>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>

View File

@ -22,6 +22,7 @@
<channel id="phase" typeId="phase"/>
<channel id="rawPhase" typeId="rawPhase"/>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="duration" typeId="duration"/>
<channel id="elapsed" typeId="elapsed"/>
<channel id="finish" typeId="finish"/>

View File

@ -0,0 +1,88 @@
/**
* 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.miele.internal;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link TimeStabilizer}.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class TimeStabilizerTest {
private @NonNullByDefault({}) TimeStabilizer stabilizer;
@BeforeEach
public void initialize() {
stabilizer = new TimeStabilizer();
}
@Test
public void whenLongestPeriodIsFloorThenWeightedAverageIsLess() {
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:00:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:01:00"), getInstantOf("22:00:31")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:01:00")),
is(equalTo(getInstantOf("02:00:29"))));
}
@Test
public void whenLongestPeriodIsCeilThenWeightedAverageIsMore() {
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:00:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:01:00"), getInstantOf("22:00:29")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:01:00")),
is(equalTo(getInstantOf("02:00:31"))));
}
@Test
public void whenTooMuchFluctuationThenAverageIsDisregarded() {
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:00:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:03:00"), getInstantOf("22:03:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:04:00"), getInstantOf("22:03:00")),
is(equalTo(getInstantOf("02:04:00"))));
}
@Test
public void whenOutsideSlidingWindowThenValueIsDisregarded() {
assertThat(stabilizer.apply(getInstantOf("02:00:00"), getInstantOf("22:00:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:01:00"), getInstantOf("22:10:00")),
is(equalTo(getInstantOf("02:00:00"))));
assertThat(stabilizer.apply(getInstantOf("02:02:00"), getInstantOf("22:15:00")),
is(equalTo(getInstantOf("02:00:30"))));
assertThat(stabilizer.apply(getInstantOf("02:02:00"), getInstantOf("22:15:01")),
is(equalTo(getInstantOf("02:01:00"))));
}
private Instant getInstantOf(String time) {
Clock clock = Clock.fixed(Instant.parse("2022-09-13T" + time + "Z"), ZoneId.of("UTC"));
return Instant.now(clock);
}
}