| 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 |
| 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 |
| 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 |
| 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 |
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";
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";
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;
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
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);
}
}
--- /dev/null
+/**
+ * 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;
+ }
+}
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;
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);
}
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;
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
}
},
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) {
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) {
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;
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);
}
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;
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
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;
@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
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;
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
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;
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;
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;
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)
}
applianceId = null;
}
+ startTimeStabilizer.clear();
+ finishTimeStabilizer.clear();
}
@Override
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);
}
}
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;
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();
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;
}
},
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) {
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) {
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;
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
}
},
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) {
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) {
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;
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
}
},
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) {
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) {
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;
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);
}
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
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
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
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
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
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
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
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
<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="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" advanced="true">
+ <channel-type id="duration">
<item-type>DateTime</item-type>
<label>Duration</label>
<description>Duration of the program running on the appliance</description>
<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"/>
<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"/>
<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"/>
<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"/>
--- /dev/null
+/**
+ * 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);
+ }
+}