| plate_power_step_raw | Number | The raw power level of the heating plate. | Yes |
| door_state | Switch | Indicates if the door of the device is open. | Yes |
| door_alarm | Switch | Indicates if the door alarm of the device is active. | Yes |
+| water_consumption_current | Number | The amount of water used by the current running program up to the present moment. | Yes |
+| energy_consumption_current | Number | The amount of energy used by the current running program up to the present moment. | Yes |
| battery_level | Number | The battery level of the robotic vacuum cleaner. | Yes |
### Coffee System
- error_state
- info_state
- door_state
+- water_consumption_current
+- energy_consumption_current
### Tumble Dryer
- light_switch
- light_can_be_controlled
- door_state
+- energy_consumption_current
### Freezer
- light_switch
- light_can_be_controlled
- door_state
+- water_consumption_current
+- energy_consumption_current
### Washing Machine
- light_switch
- light_can_be_controlled
- door_state
+- water_consumption_current
+- energy_consumption_current
### Wine Storage
* @author Björn Lange - Added locale config parameter, added i18n key collection
* @author Benjamin Bolte - Add pre-heat finished and plate step channels, door state and door alarm channels, info
* state channel and map signal flags from API
- * @author Björn Lange - Add elapsed time channel, dish warmer thing, removed e-mail validation
+ * @author Björn Lange - Add elapsed time channel, dish warmer thing, removed e-mail validation, add eco feedback
*/
@NonNullByDefault
public final class MieleCloudBindingConstants {
public static final String PLATE_6_POWER_STEP_RAW = "plate_6_power_step_raw";
public static final String DOOR_STATE = "door_state";
public static final String DOOR_ALARM = "door_alarm";
+ public static final String WATER_CONSUMPTION_CURRENT = "water_consumption_current";
+ public static final String ENERGY_CONSUMPTION_CURRENT = "energy_consumption_current";
public static final String BATTERY_LEVEL = "battery_level";
}
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DishwasherDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(ERROR_STATE), device.getErrorState());
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(DOOR_STATE), device.getDoorState());
+ updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
+ updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}
@Override
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DryerDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState());
+ updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}
@Override
* @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class WashingDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState());
+ updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
+ updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
}
@Override
package org.openhab.binding.mielecloud.internal.handler.channel;
import java.math.BigDecimal;
+import java.text.NumberFormat;
+import java.util.Locale;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Utility class handling type conversions from Java types to channel types.
*/
@NonNullByDefault
public final class ChannelTypeUtil {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ChannelTypeUtil.class);
+
private ChannelTypeUtil() {
throw new IllegalStateException("ChannelTypeUtil cannot be instantiated.");
}
// The Miele 3rd Party API always provides temperatures in °C (even if the device uses another unit).
return value.map(v -> (State) new QuantityType<>(v, SIUnits.CELSIUS)).orElse(UnDefType.UNDEF);
}
+
+ /**
+ * Converts an {@link Optional} of {@link Quantity} to {@link State}.
+ */
+ public static State quantityToState(Optional<Quantity> value) {
+ return value.flatMap(ChannelTypeUtil::formatQuantity).flatMap(ChannelTypeUtil::parseQuantityType)
+ .orElse(UnDefType.UNDEF);
+ }
+
+ /**
+ * Formats the quantity as "value unit" with the given locale.
+ *
+ * @param locale The locale to format with.
+ * @return An {@link Optional} containing the formatted quantity value or an empty {@link Optional} if formatting
+ * for the given locale failed.
+ */
+ private static Optional<String> formatQuantity(Quantity quantity) {
+ double value = quantity.getValue();
+ try {
+ var formatted = NumberFormat.getInstance(Locale.ENGLISH).format(value);
+
+ var unit = quantity.getUnit();
+ if (unit.isPresent()) {
+ formatted = formatted + " " + unit.get();
+ }
+
+ return Optional.of(formatted);
+ } catch (ArithmeticException e) {
+ LOGGER.warn("Failed to format {}", value, e);
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Parses a previously formatted {@link Quantity} into a {@link State}.
+ *
+ * @param value The quantity value formatted as "value unit".
+ * @return An {@link Optional} containing the parsed {@link State} or an empty {@link Optional} if the quantity
+ * including unit could not be parsed.
+ */
+ private static Optional<State> parseQuantityType(String value) {
+ try {
+ return Optional.of((State) new QuantityType<>(value));
+ } catch (IllegalArgumentException e) {
+ LOGGER.warn("Failed to convert {} to quantity: {}", value, e.getMessage(), e);
+ return Optional.empty();
+ }
+ }
}
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channel and map
* signal flags from API
- * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner thing
+ * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner thing, eco feedback
*/
@NonNullByDefault
public final class DeviceChannelState {
return ChannelTypeUtil.intToState(device.getSpinningSpeedRaw());
}
+ public State getCurrentWaterConsumption() {
+ return ChannelTypeUtil.quantityToState(device.getCurrentWaterConsumption());
+ }
+
+ public State getCurrentEnergyConsumption() {
+ return ChannelTypeUtil.quantityToState(device.getCurrentEnergyConsumption());
+ }
+
public State getBatteryLevel() {
return ChannelTypeUtil.intToState(device.getBatteryLevel());
}
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DryingStep;
+import org.openhab.binding.mielecloud.internal.webservice.api.json.EcoFeedback;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light;
import org.openhab.binding.mielecloud.internal.webservice.api.json.PlateStep;
* @author Björn Lange - Introduced null handling
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm, info state channel and map signal
* flags from API
- * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things
+ * @author Björn Lange - Add elapsed time channel, dish warmer and robotic vacuum cleaner things, eco feedback
*/
@NonNullByDefault
public class DeviceState {
return Optional.of(doorState.get() && failure.get());
}
+ /**
+ * Gets the amount of water consumed since the currently running program started.
+ *
+ * @return The amount of water consumed since the currently running program started.
+ */
+ public Optional<Quantity> getCurrentWaterConsumption() {
+ if (deviceIsInOffState()) {
+ return Optional.empty();
+ }
+
+ return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
+ .flatMap(EcoFeedback::getCurrentWaterConsumption).flatMap(consumption -> consumption.getValue()
+ .map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
+ }
+
+ /**
+ * Gets the amount of energy consumed since the currently running program started.
+ *
+ * @return The amount of energy consumed since the currently running program started.
+ */
+ public Optional<Quantity> getCurrentEnergyConsumption() {
+ if (deviceIsInOffState()) {
+ return Optional.empty();
+ }
+
+ return device.flatMap(Device::getState).flatMap(State::getEcoFeedback)
+ .flatMap(EcoFeedback::getCurrentEnergyConsumption).flatMap(consumption -> consumption.getValue()
+ .map(value -> new Quantity(value, consumption.getUnit().orElse(null))));
+ }
+
/**
* Gets the battery level.
*
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.mielecloud.internal.webservice.api;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A physical quantity as obtained from the Miele REST API.
+ *
+ * @author Björn Lange - Initial contribution
+ */
+@NonNullByDefault
+public class Quantity {
+ double value;
+ Optional<String> unit;
+
+ public Quantity(double value, @Nullable String unit) {
+ this.value = value;
+ this.unit = Optional.ofNullable(unit);
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public Optional<String> getUnit() {
+ return unit;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, unit);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Quantity other = (Quantity) obj;
+ return Double.doubleToLongBits(value) == Double.doubleToLongBits(other.value)
+ && Objects.equals(unit, other.unit);
+ }
+
+ @Override
+ public String toString() {
+ return "Quantity [value=" + value + ", unit=" + unit + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.mielecloud.internal.webservice.api.json;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Immutable POJO representing the amount of water and energy used by the current running program up to the present
+ * moment. Queried from the Miele REST API.
+ *
+ * @author Björn Lange - Initial contribution
+ */
+@NonNullByDefault
+public class EcoFeedback {
+ @Nullable
+ private WaterConsumption currentWaterConsumption;
+ @Nullable
+ private EnergyConsumption currentEnergyConsumption;
+ @Nullable
+ private Double waterForecast;
+ @Nullable
+ private Double energyForecast;
+
+ public Optional<WaterConsumption> getCurrentWaterConsumption() {
+ return Optional.ofNullable(currentWaterConsumption);
+ }
+
+ public Optional<EnergyConsumption> getCurrentEnergyConsumption() {
+ return Optional.ofNullable(currentEnergyConsumption);
+ }
+
+ /**
+ * Gets the relative water usage for the selected program from 0 to 1.
+ */
+ public Optional<Double> getWaterForecast() {
+ return Optional.ofNullable(waterForecast);
+ }
+
+ /**
+ * Gets the relative energy usage for the selected program from 0 to 1.
+ */
+ public Optional<Double> getEnergyForecast() {
+ return Optional.ofNullable(energyForecast);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(currentWaterConsumption, currentEnergyConsumption, waterForecast, energyForecast);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ EcoFeedback other = (EcoFeedback) obj;
+ return Objects.equals(currentWaterConsumption, other.currentWaterConsumption)
+ && Objects.equals(currentEnergyConsumption, other.currentEnergyConsumption)
+ && Objects.equals(waterForecast, other.waterForecast)
+ && Objects.equals(energyForecast, other.energyForecast);
+ }
+
+ @Override
+ public String toString() {
+ return "EcoFeedback [currentWaterConsumption=" + currentWaterConsumption + ", currentEnergyConsumption="
+ + currentEnergyConsumption + ", waterForecast=" + waterForecast + ", energyForecast=" + energyForecast
+ + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.mielecloud.internal.webservice.api.json;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Immutable POJO representing an amount of consumed energy. Queried from the Miele REST API.
+ *
+ * @author Björn Lange - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyConsumption {
+ @Nullable
+ private String unit;
+ @Nullable
+ private Double value;
+
+ /**
+ * Gets the measurement unit which represents energy.
+ */
+ public Optional<String> getUnit() {
+ return Optional.ofNullable(unit);
+ }
+
+ /**
+ * Gets the amount of energy.
+ */
+ public Optional<Double> getValue() {
+ return Optional.ofNullable(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(unit, value);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ EnergyConsumption other = (EnergyConsumption) obj;
+ return Objects.equals(unit, other.unit) && Objects.equals(value, other.value);
+ }
+
+ @Override
+ public String toString() {
+ return "EnergyConsumption [unit=" + unit + ", value=" + value + "]";
+ }
+}
*
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time channel, add eco feedback
*/
@NonNullByDefault
public class State {
@Nullable
private final List<PlateStep> plateStep = null;
@Nullable
+ private EcoFeedback ecoFeedback;
+ @Nullable
private Integer batteryLevel;
public Optional<Status> getStatus() {
return Collections.unmodifiableList(plateStep);
}
+ public Optional<EcoFeedback> getEcoFeedback() {
+ return Optional.ofNullable(ecoFeedback);
+ }
+
public Optional<Integer> getBatteryLevel() {
return Optional.ofNullable(batteryLevel);
}
public int hashCode() {
return Objects.hash(dryingStep, elapsedTime, light, programPhase, ProgramID, programId, programType,
remainingTime, remoteEnable, signalDoor, signalFailure, signalInfo, startTime, status,
- targetTemperature, temperature, ventilationStep, plateStep, batteryLevel);
+ targetTemperature, temperature, ventilationStep, plateStep, ecoFeedback, batteryLevel);
}
@Override
&& Objects.equals(targetTemperature, other.targetTemperature)
&& Objects.equals(temperature, other.temperature)
&& Objects.equals(ventilationStep, other.ventilationStep) && Objects.equals(plateStep, other.plateStep)
- && Objects.equals(batteryLevel, other.batteryLevel);
+ && Objects.equals(ecoFeedback, other.ecoFeedback) && Objects.equals(batteryLevel, other.batteryLevel);
}
@Override
+ ", targetTemperature=" + targetTemperature + ", temperature=" + temperature + ", signalInfo="
+ signalInfo + ", signalFailure=" + signalFailure + ", signalDoor=" + signalDoor + ", remoteEnable="
+ remoteEnable + ", light=" + light + ", elapsedTime=" + elapsedTime + ", dryingStep=" + dryingStep
- + ", ventilationStep=" + ventilationStep + ", plateStep=" + plateStep + ", batteryLevel=" + batteryLevel
- + "]";
+ + ", ventilationStep=" + ventilationStep + ", plateStep=" + plateStep + ", ecoFeedback=" + ecoFeedback
+ + ", batteryLevel=" + batteryLevel + "]";
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.mielecloud.internal.webservice.api.json;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Immutable POJO representing an amount of consumed water. Queried from the Miele REST API.
+ *
+ * @author Björn Lange - Initial contribution
+ */
+@NonNullByDefault
+public class WaterConsumption {
+ @Nullable
+ private String unit;
+ @Nullable
+ private Double value;
+
+ /**
+ * Gets the measurement unit which represents a volume.
+ */
+ public Optional<String> getUnit() {
+ return Optional.ofNullable(unit);
+ }
+
+ /**
+ * Gets the amount of water.
+ */
+ public Optional<Double> getValue() {
+ return Optional.ofNullable(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(unit, value);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ WaterConsumption other = (WaterConsumption) obj;
+ return Objects.equals(unit, other.unit) && Objects.equals(value, other.value);
+ }
+
+ @Override
+ public String toString() {
+ return "WaterConsumption [unit=" + unit + ", value=" + value + "]";
+ }
+}
channel-type.mielecloud.door_alarm.label=Door Alarm
channel-type.mielecloud.door_alarm.description=Indicates if the door alarm of the device is active.
+channel-type.mielecloud.water_consumption_current.label=Current Water Consumption
+channel-type.mielecloud.water_consumption_current.description=The amount of water used by the current running program up to the present moment.
+
+channel-type.mielecloud.energy_consumption_current.label=Current Energy Consumption
+channel-type.mielecloud.energy_consumption_current.description=The amount of energy used by the current running program up to the present moment.
+
channel-type.mielecloud.battery_level.label=Battery Level
channel-type.mielecloud.battery_level.description=The battery level of the robotic vacuum cleaner.
<state readOnly="true"/>
</channel-type>
+ <channel-type id="water_consumption_current">
+ <item-type>Number:Volume</item-type>
+ <label>@text/channel-type.mielecloud.water_consumption_current.label</label>
+ <description>@text/channel-type.mielecloud.water_consumption_current.description</description>
+ <category>Water</category>
+ <tags>
+ <tag>Measurement</tag>
+ <tag>Water</tag>
+ </tags>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="energy_consumption_current">
+ <item-type>Number:Energy</item-type>
+ <label>@text/channel-type.mielecloud.energy_consumption_current.label</label>
+ <description>@text/channel-type.mielecloud.energy_consumption_current.description</description>
+ <category>Energy</category>
+ <tags>
+ <tag>Measurement</tag>
+ <tag>Energy</tag>
+ </tags>
+ <state pattern="%.1f %unit%" readOnly="true"/>
+ </channel-type>
+
<channel-type id="battery_level">
<item-type>Number</item-type>
<label>@text/channel-type.mielecloud.battery_level.label</label>
<channel id="error_state" typeId="error_state"/>
<channel id="info_state" typeId="info_state"/>
<channel id="door_state" typeId="door_state"/>
+ <channel id="water_consumption_current" typeId="water_consumption_current"/>
+ <channel id="energy_consumption_current" typeId="energy_consumption_current"/>
</channels>
<properties>
+ <property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property>
</properties>
<channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_state"/>
+ <channel id="energy_consumption_current" typeId="energy_consumption_current"/>
</channels>
<properties>
+ <property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property>
</properties>
<channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_state"/>
+ <channel id="water_consumption_current" typeId="water_consumption_current"/>
+ <channel id="energy_consumption_current" typeId="energy_consumption_current"/>
</channels>
<properties>
+ <property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property>
</properties>
<channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_state"/>
+ <channel id="water_consumption_current" typeId="water_consumption_current"/>
+ <channel id="energy_consumption_current" typeId="energy_consumption_current"/>
</channels>
<properties>
+ <property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property>
</properties>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="mielecloud:dishwasher">
+ <instruction-set targetVersion="1">
+ <add-channel id="water_consumption_current">
+ <type>mielecloud:water_consumption_current</type>
+ </add-channel>
+ <add-channel id="energy_consumption_current">
+ <type>mielecloud:energy_consumption_current</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+
+ <thing-type uid="mielecloud:dryer">
+ <instruction-set targetVersion="1">
+ <add-channel id="energy_consumption_current">
+ <type>mielecloud:energy_consumption_current</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+
+ <thing-type uid="mielecloud:washer_dryer">
+ <instruction-set targetVersion="1">
+ <add-channel id="water_consumption_current">
+ <type>mielecloud:water_consumption_current</type>
+ </add-channel>
+ <add-channel id="energy_consumption_current">
+ <type>mielecloud:energy_consumption_current</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+
+ <thing-type uid="mielecloud:washing_machine">
+ <instruction-set targetVersion="1">
+ <add-channel id="water_consumption_current">
+ <type>mielecloud:water_consumption_current</type>
+ </add-channel>
+ <add-channel id="energy_consumption_current">
+ <type>mielecloud:energy_consumption_current</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+</update:update-descriptions>
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.mielecloud.internal.handler.channel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * @author Björn Lange - Initial contribution
+ */
+@NonNullByDefault
+public class ChannelTypeUtilTest {
+ private static Stream<Arguments> quantityToStateConversionArguments() {
+ return Stream.of(Arguments.of(Optional.empty(), UnDefType.UNDEF),
+ Arguments.of(Optional.of(new Quantity(10.0, "Gold")), UnDefType.UNDEF),
+ Arguments.of(Optional.of(new Quantity(3.0, null)), new QuantityType<>(3.0, Units.ONE)),
+ Arguments.of(Optional.of(new Quantity(1.0 / 3.0, "l")), new QuantityType<>(0.333, Units.LITRE)),
+ Arguments.of(Optional.of(new Quantity(20.123, "kWh")), new QuantityType<>(20.123, Units.KILOWATT_HOUR)),
+ Arguments.of(Optional.of(new Quantity(0.5, "l")), new QuantityType<>(0.5, Units.LITRE)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("quantityToStateConversionArguments")
+ void quantityCanBeConvertedToState(Optional<Quantity> input, State expected) {
+ // when:
+ var state = ChannelTypeUtil.quantityToState(input);
+
+ // then:
+ assertEquals(expected, state);
+ }
+}
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DryingStep;
+import org.openhab.binding.mielecloud.internal.webservice.api.json.EcoFeedback;
+import org.openhab.binding.mielecloud.internal.webservice.api.json.EnergyConsumption;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Ident;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light;
import org.openhab.binding.mielecloud.internal.webservice.api.json.PlateStep;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Temperature;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Type;
import org.openhab.binding.mielecloud.internal.webservice.api.json.VentilationStep;
+import org.openhab.binding.mielecloud.internal.webservice.api.json.WaterConsumption;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channels and map
* signal flags from API
- * @author Björn Lange - Add elapsed time channel, robotic vacuum cleaner
+ * @author Björn Lange - Add elapsed time channel, robotic vacuum cleaner, eco feedback
*/
@NonNullByDefault
public class DeviceStateTest {
assertFalse(lightState.isPresent());
}
+ @Test
+ public void testGetCurrentWaterConsumptionWhenEcoFeedbackIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.empty());
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> waterConsumption = deviceState.getCurrentWaterConsumption();
+
+ // then:
+ assertFalse(waterConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentWaterConsumptionWhenCurrentWaterConsumptionIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentWaterConsumption()).thenReturn(Optional.empty());
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> waterConsumption = deviceState.getCurrentWaterConsumption();
+
+ // then:
+ assertFalse(waterConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentWaterConsumptionWhenCurrentWaterConsumptionIsEmpty() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ WaterConsumption currentWaterConsumption = mock(WaterConsumption.class);
+ when(currentWaterConsumption.getUnit()).thenReturn(Optional.empty());
+ when(currentWaterConsumption.getValue()).thenReturn(Optional.empty());
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentWaterConsumption()).thenReturn(Optional.of(currentWaterConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> waterConsumption = deviceState.getCurrentWaterConsumption();
+
+ // then:
+ assertFalse(waterConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentWaterConsumptionWhenValueIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ WaterConsumption currentWaterConsumption = mock(WaterConsumption.class);
+ when(currentWaterConsumption.getUnit()).thenReturn(Optional.of("l"));
+ when(currentWaterConsumption.getValue()).thenReturn(Optional.empty());
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentWaterConsumption()).thenReturn(Optional.of(currentWaterConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> waterConsumption = deviceState.getCurrentWaterConsumption();
+
+ // then:
+ assertFalse(waterConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentWaterConsumptionWhenUnitIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ WaterConsumption currentWaterConsumption = mock(WaterConsumption.class);
+ when(currentWaterConsumption.getUnit()).thenReturn(Optional.empty());
+ when(currentWaterConsumption.getValue()).thenReturn(Optional.of(0.5));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentWaterConsumption()).thenReturn(Optional.of(currentWaterConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Quantity waterConsumption = deviceState.getCurrentWaterConsumption().get();
+
+ // then:
+ assertEquals(0.5, waterConsumption.getValue());
+ assertFalse(waterConsumption.getUnit().isPresent());
+ }
+
+ @Test
+ public void testGetCurrentWaterConsumption() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ WaterConsumption currentWaterConsumption = mock(WaterConsumption.class);
+ when(currentWaterConsumption.getUnit()).thenReturn(Optional.of("l"));
+ when(currentWaterConsumption.getValue()).thenReturn(Optional.of(0.5));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentWaterConsumption()).thenReturn(Optional.of(currentWaterConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Quantity waterConsumption = deviceState.getCurrentWaterConsumption().get();
+
+ // then:
+ assertEquals(0.5, waterConsumption.getValue());
+ assertEquals(Optional.of("l"), waterConsumption.getUnit());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumptionWhenEcoFeedbackIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.empty());
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> energyConsumption = deviceState.getCurrentEnergyConsumption();
+
+ // then:
+ assertFalse(energyConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumptionWhenCurrentEnergyConsumptionIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> energyConsumption = deviceState.getCurrentEnergyConsumption();
+
+ // then:
+ assertFalse(energyConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumptionWhenCurrentEnergyConsumptionIsEmpty() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EnergyConsumption currentEnergyConsumption = mock(EnergyConsumption.class);
+ when(currentEnergyConsumption.getUnit()).thenReturn(Optional.empty());
+ when(currentEnergyConsumption.getValue()).thenReturn(Optional.empty());
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentEnergyConsumption()).thenReturn(Optional.of(currentEnergyConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> energyConsumption = deviceState.getCurrentEnergyConsumption();
+
+ // then:
+ assertFalse(energyConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumptionWhenValueIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EnergyConsumption currentEnergyConsumption = mock(EnergyConsumption.class);
+ when(currentEnergyConsumption.getUnit()).thenReturn(Optional.of("kWh"));
+ when(currentEnergyConsumption.getValue()).thenReturn(Optional.empty());
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentEnergyConsumption()).thenReturn(Optional.of(currentEnergyConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Optional<Quantity> energyConsumption = deviceState.getCurrentEnergyConsumption();
+
+ // then:
+ assertFalse(energyConsumption.isPresent());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumptionWhenUnitIsNotPresent() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EnergyConsumption currentEnergyConsumption = mock(EnergyConsumption.class);
+ when(currentEnergyConsumption.getUnit()).thenReturn(Optional.empty());
+ when(currentEnergyConsumption.getValue()).thenReturn(Optional.of(0.5));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentEnergyConsumption()).thenReturn(Optional.of(currentEnergyConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Quantity energyConsumption = deviceState.getCurrentEnergyConsumption().get();
+
+ // then:
+ assertEquals(0.5, energyConsumption.getValue());
+ assertFalse(energyConsumption.getUnit().isPresent());
+ }
+
+ @Test
+ public void testGetCurrentEnergyConsumption() {
+ // given:
+ Status status = mock(Status.class);
+ when(status.getValueRaw()).thenReturn(Optional.of(StateType.ON.getCode()));
+
+ EnergyConsumption currentEnergyConsumption = mock(EnergyConsumption.class);
+ when(currentEnergyConsumption.getUnit()).thenReturn(Optional.of("kWh"));
+ when(currentEnergyConsumption.getValue()).thenReturn(Optional.of(0.5));
+
+ EcoFeedback ecoFeedback = mock(EcoFeedback.class);
+ when(ecoFeedback.getCurrentEnergyConsumption()).thenReturn(Optional.of(currentEnergyConsumption));
+
+ State state = mock(State.class);
+ when(state.getStatus()).thenReturn(Optional.of(status));
+ when(state.getEcoFeedback()).thenReturn(Optional.of(ecoFeedback));
+
+ Device device = mock(Device.class);
+ when(device.getState()).thenReturn(Optional.of(state));
+
+ DeviceState deviceState = new DeviceState(DEVICE_IDENTIFIER, device);
+
+ // when:
+ Quantity energyConsumption = deviceState.getCurrentEnergyConsumption().get();
+
+ // then:
+ assertEquals(0.5, energyConsumption.getValue());
+ assertEquals(Optional.of("kWh"), energyConsumption.getUnit());
+ }
+
@Test
public void testGetBatteryLevel() {
// given:
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step
+ * @author Björn Lange - Add eco feedback
*/
@NonNullByDefault
public class DeviceCollectionTest {
assertEquals(Integer.valueOf(0), targetTemperature.getValueLocalized().get());
assertEquals("Celsius", targetTemperature.getUnit().get());
}
+
+ @Test
+ public void testCreateDeviceCollectionWithEcoFeedback() throws IOException {
+ // given:
+ String json = getResourceAsString(
+ "/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithEcoFeedback.json");
+
+ // when:
+ DeviceCollection collection = DeviceCollection.fromJson(json);
+
+ // then:
+ assertEquals(1, collection.getDeviceIdentifiers().size());
+ Device device = collection.getDevice(collection.getDeviceIdentifiers().iterator().next());
+
+ State state = device.getState().get();
+ EcoFeedback ecoFeedback = state.getEcoFeedback().get();
+
+ WaterConsumption currentWaterConsumption = ecoFeedback.getCurrentWaterConsumption().get();
+ assertEquals("l", currentWaterConsumption.getUnit().get());
+ assertEquals(0.0, currentWaterConsumption.getValue().get());
+
+ EnergyConsumption currentEnergyConsumption = ecoFeedback.getCurrentEnergyConsumption().get();
+ assertEquals("kWh", currentEnergyConsumption.getUnit().get());
+ assertEquals(0.5, currentEnergyConsumption.getValue().get());
+
+ assertEquals(0.0, ecoFeedback.getWaterForecast().get());
+ assertEquals(0.6, ecoFeedback.getEnergyForecast().get());
+ }
}
--- /dev/null
+{
+ "000124430017": {
+ "ident": {
+ "type": {
+ "key_localized": "Device type",
+ "value_raw": 2,
+ "value_localized": "Tumble dryer"
+ },
+ "deviceName": "TWH780WP",
+ "protocolVersion": 4,
+ "deviceIdentLabel": {
+ "fabNumber": "000124430017",
+ "fabIndex": "44",
+ "techType": "TWH780WP",
+ "matNumber": "11219891",
+ "swids": [
+ "5678",
+ "25359",
+ "25360",
+ "20559",
+ "25277",
+ "5136",
+ "20445",
+ "25234",
+ "4657"
+ ]
+ },
+ "xkmIdentLabel": {
+ "techType": "EK057",
+ "releaseVersion": "08.10"
+ }
+ },
+ "state": {
+ "ProgramID": {
+ "value_raw": 2,
+ "value_localized": "Cottons",
+ "key_localized": "Program name"
+ },
+ "status": {
+ "value_raw": 5,
+ "value_localized": "In use",
+ "key_localized": "status"
+ },
+ "programType": {
+ "value_raw": 3,
+ "value_localized": "Cleaning/Care programme",
+ "key_localized": "Program type"
+ },
+ "programPhase": {
+ "value_raw": 514,
+ "value_localized": "Drying",
+ "key_localized": "Program phase"
+ },
+ "remainingTime": [
+ 2,
+ 7
+ ],
+ "startTime": [
+ 0,
+ 0
+ ],
+ "targetTemperature": [
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ },
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ },
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ }
+ ],
+ "temperature": [
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ },
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ },
+ {
+ "value_raw": -32768,
+ "value_localized": null,
+ "unit": "Celsius"
+ }
+ ],
+ "signalInfo": false,
+ "signalFailure": false,
+ "signalDoor": false,
+ "remoteEnable": {
+ "fullRemoteControl": true,
+ "smartGrid": false,
+ "mobileStart": false
+ },
+ "ambientLight": null,
+ "light": null,
+ "elapsedTime": [
+ 1,
+ 9
+ ],
+ "spinningSpeed": {
+ "unit": "rpm",
+ "value_raw": null,
+ "value_localized": null,
+ "key_localized": "Spin speed"
+ },
+ "dryingStep": {
+ "value_raw": 0,
+ "value_localized": "Extra dry",
+ "key_localized": "Drying level"
+ },
+ "ventilationStep": {
+ "value_raw": null,
+ "value_localized": "",
+ "key_localized": "Fan level"
+ },
+ "plateStep": [],
+ "ecoFeedback": {
+ "currentWaterConsumption": {
+ "unit": "l",
+ "value": 0
+ },
+ "currentEnergyConsumption": {
+ "unit": "kWh",
+ "value": 0.5
+ },
+ "waterForecast": 0,
+ "energyForecast": 0.6
+ },
+ "batteryLevel": null
+ }
+ }
+}
\ No newline at end of file
}
protected AbstractMieleThingHandler createThingHandler(ThingTypeUID thingTypeUid, ThingUID thingUid,
- Class<? extends AbstractMieleThingHandler> expectedHandlerClass, String deviceIdentifier) {
+ Class<? extends AbstractMieleThingHandler> expectedHandlerClass, String deviceIdentifier,
+ String thingTypeVersion) {
ThingRegistry registry = getThingRegistry();
List<Channel> channels = createChannelsForThingHandler(thingTypeUid, thingUid);
Thing thing = ThingBuilder.create(thingTypeUid, thingUid)
.withConfiguration(new Configuration(Collections
.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, deviceIdentifier)))
- .withBridge(getBridge().getUID()).withChannels(channels).withLabel("DA-6996").build();
+ .withBridge(getBridge().getUID()).withChannels(channels).withLabel("DA-6996")
+ .withProperty("thingTypeVersion", thingTypeVersion).build();
assertNotNull(thing);
registry.add(thing);
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_SYSTEM_THING_UID,
- CoffeeSystemThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ CoffeeSystemThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, FRIDGE_FREEZER_DEVICE_THING_UID,
- CoolingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ CoolingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER,
MieleCloudBindingIntegrationTestConstants.DISH_WARMER_DEVICE_THING_UID,
- DishWarmerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ DishWarmerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus;
+import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_THING_UID,
- DishwasherDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ DishwasherDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
}
@Test
when(deviceState.getStartTime()).thenReturn(Optional.empty());
when(deviceState.getElapsedTime()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty());
+ when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.empty());
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(NULL_VALUE_STATE, getChannelState(DELAYED_START_TIME));
assertEquals(NULL_VALUE_STATE, getChannelState(PROGRAM_ELAPSED_TIME));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE));
+ assertEquals(NULL_VALUE_STATE, getChannelState(WATER_CONSUMPTION_CURRENT));
+ assertEquals(NULL_VALUE_STATE, getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
when(deviceState.hasError()).thenReturn(false);
when(deviceState.hasInfo()).thenReturn(true);
when(deviceState.getDoorState()).thenReturn(Optional.of(true));
+ when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.of(new Quantity(1.0, "l")));
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.of(new Quantity(2.5, "kWh")));
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(OnOffType.OFF, getChannelState(ERROR_STATE));
assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.ON, getChannelState(DOOR_STATE));
+ assertEquals(new QuantityType<>(1.0, Units.LITRE), getChannelState(WATER_CONSUMPTION_CURRENT));
+ assertEquals(new QuantityType<>(2.5, Units.KILOWATT_HOUR), getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus;
+import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_THING_UID,
- DryerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ DryerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
}
@Test
when(deviceState.getDryingTargetRaw()).thenReturn(Optional.empty());
when(deviceState.getLightState()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty());
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(NULL_VALUE_STATE, getChannelState(DRYING_TARGET_RAW));
assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE));
+ assertEquals(NULL_VALUE_STATE, getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
when(deviceState.hasInfo()).thenReturn(true);
when(deviceState.getLightState()).thenReturn(Optional.of(false));
when(deviceState.getDoorState()).thenReturn(Optional.of(false));
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.of(new Quantity(2.5, "Wh")));
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH));
assertEquals(OnOffType.OFF, getChannelState(DOOR_STATE));
+ assertEquals(new QuantityType<>(2.5, Units.WATT_HOUR), getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_THING_UID,
- HobDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ HobDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_THING_UID,
- HoodDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ HoodDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
}
private void testHandlerCanBeCreatedForMieleDevice(ThingTypeUID thingTypeUid, ThingUID thingUid, String label,
- Class<? extends ThingHandler> expectedHandlerClass)
+ Class<? extends ThingHandler> expectedHandlerClass, String thingTypeVersion)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
// given:
MieleWebservice webservice = mock(MieleWebservice.class);
Thing device = ThingBuilder.create(thingTypeUid, thingUid)
.withConfiguration(new Configuration(Collections
.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, DEVICE_IDENTIFIER)))
- .withLabel(label).build();
+ .withLabel(label).withProperty("thingTypeVersion", thingTypeVersion).build();
assertNotNull(device);
verifyHandlerCreation(webservice, device, expectedHandlerClass);
public void testHandlerCanBeCreatedForWashingDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE,
- WASHING_MACHINE_TYPE, "DA-6996", WashingDeviceThingHandler.class);
+ WASHING_MACHINE_TYPE, "DA-6996", WashingDeviceThingHandler.class, "1");
}
@Test
public void testHandlerCanBeCreatedForOvenDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_TYPE, "OV-6887",
- OvenDeviceThingHandler.class);
+ OvenDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForHobDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_TYPE, "HB-3887",
- HobDeviceThingHandler.class);
+ HobDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForFridgeFreezerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER,
- FRIDGE_FREEZER_DEVICE_TYPE, "CD-6097", CoolingDeviceThingHandler.class);
+ FRIDGE_FREEZER_DEVICE_TYPE, "CD-6097", CoolingDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForHoodDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_TYPE, "HD-2097",
- HoodDeviceThingHandler.class);
+ HoodDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForCoffeeDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_DEVICE_TYPE,
- "DA-6997", CoffeeSystemThingHandler.class);
+ "DA-6997", CoffeeSystemThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForWineStorageDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE,
- WINE_STORAGE_DEVICE_TYPE, "WS-6907", WineStorageDeviceThingHandler.class);
+ WINE_STORAGE_DEVICE_TYPE, "WS-6907", WineStorageDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForDryerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_TYPE, "DR-0907",
- DryerDeviceThingHandler.class);
+ DryerDeviceThingHandler.class, "1");
}
@Test
public void testHandlerCanBeCreatedForDishwasherDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_TYPE,
- "DR-0907", DishwasherDeviceThingHandler.class);
+ "DR-0907", DishwasherDeviceThingHandler.class, "1");
}
@Test
public void testHandlerCanBeCreatedForDishWarmerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER,
- DISH_WARMER_DEVICE_TYPE, "DW-0907", DishWarmerDeviceThingHandler.class);
+ DISH_WARMER_DEVICE_TYPE, "DW-0907", DishWarmerDeviceThingHandler.class, "0");
}
@Test
public void testHandlerCanBeCreatedForRoboticVacuumCleanerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_ROBOTIC_VACUUM_CLEANER,
- ROBOTIC_VACUUM_CLEANER_DEVICE_TYPE, "RVC-0907", RoboticVacuumCleanerDeviceThingHandler.class);
+ ROBOTIC_VACUUM_CLEANER_DEVICE_TYPE, "RVC-0907", RoboticVacuumCleanerDeviceThingHandler.class, "0");
}
/**
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_THING_UID,
- OvenDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ OvenDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_ROBOTIC_VACUUM_CLEANER,
MieleCloudBindingIntegrationTestConstants.ROBOTIC_VACUUM_CLEANER_THING_UID,
- RoboticVacuumCleanerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ RoboticVacuumCleanerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER,
+ "0");
}
@Test
import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus;
+import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests
- * @author Björn Lange - Add elapsed time channel
+ * @author Björn Lange - Add elapsed time, current water and energy consumption channels
*/
@NonNullByDefault
public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, WASHING_MACHINE_THING_UID,
- WashingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ WashingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
}
@Test
when(deviceState.getTargetTemperature(0)).thenReturn(Optional.empty());
when(deviceState.getLightState()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty());
+ when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.empty());
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(NULL_VALUE_STATE, getChannelState(TEMPERATURE_TARGET));
assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE));
+ assertEquals(NULL_VALUE_STATE, getChannelState(WATER_CONSUMPTION_CURRENT));
+ assertEquals(NULL_VALUE_STATE, getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
when(deviceState.hasInfo()).thenReturn(true);
when(deviceState.getLightState()).thenReturn(Optional.of(false));
when(deviceState.getDoorState()).thenReturn(Optional.of(true));
+ when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.of(new Quantity(0.5, "l")));
+ when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.of(new Quantity(1.5, "kWh")));
// when:
getBridgeHandler().onDeviceStateUpdated(deviceState);
assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH));
assertEquals(OnOffType.ON, getChannelState(DOOR_STATE));
+ assertEquals(new QuantityType<>(0.5, Units.LITRE), getChannelState(WATER_CONSUMPTION_CURRENT));
+ assertEquals(new QuantityType<>(1.5, Units.KILOWATT_HOUR), getChannelState(ENERGY_CONSUMPTION_CURRENT));
});
}
@Override
protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE, WINE_STORAGE_DEVICE_THING_UID,
- WineStorageDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER);
+ WineStorageDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
}
@Test