[mielecloud] Add channels energy and water consumption (#14456)

* Add POJOs for ecoFeedback from Miele REST API
* DeviceState offers  water and energy consumption
* Convert Quantity to State for channel population
* Add eco feedback channels to devices
* Fix item types and categories
* Add update instructions for eco feedback channels

Signed-off-by: Björn Lange <bjoern.lange@itemis.de>
This commit is contained in:
Björn Lange 2023-03-24 23:15:28 +01:00 committed by GitHub
parent 983efd76ea
commit fbcb412353
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1133 additions and 41 deletions

View File

@ -151,6 +151,8 @@ Channel ID and channel type ID match unless noted.
| 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
@ -215,6 +217,8 @@ Channel ID and channel type ID match unless noted.
- error_state
- info_state
- door_state
- water_consumption_current
- energy_consumption_current
### Tumble Dryer
@ -242,6 +246,7 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- energy_consumption_current
### Freezer
@ -387,6 +392,8 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- water_consumption_current
- energy_consumption_current
### Washing Machine
@ -415,6 +422,8 @@ Channel ID and channel type ID match unless noted.
- light_switch
- light_can_be_controlled
- door_state
- water_consumption_current
- energy_consumption_current
### Wine Storage

View File

@ -23,7 +23,7 @@ import org.openhab.core.thing.ThingTypeUID;
* @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 {
@ -214,6 +214,8 @@ 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";
}

View File

@ -26,7 +26,7 @@ import org.openhab.core.thing.Thing;
* @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 {
@ -54,6 +54,8 @@ 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

View File

@ -26,7 +26,7 @@ import org.openhab.core.thing.Thing;
* @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 {
@ -57,6 +57,7 @@ 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

View File

@ -26,7 +26,7 @@ import org.openhab.core.thing.Thing;
* @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 {
@ -58,6 +58,8 @@ 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

View File

@ -13,9 +13,12 @@
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;
@ -23,6 +26,8 @@ import org.openhab.core.library.types.StringType;
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.
@ -31,6 +36,8 @@ import org.openhab.core.types.UnDefType;
*/
@NonNullByDefault
public final class ChannelTypeUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelTypeUtil.class);
private ChannelTypeUtil() {
throw new IllegalStateException("ChannelTypeUtil cannot be instantiated.");
}
@ -71,4 +78,52 @@ public final class ChannelTypeUtil {
// 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();
}
}
}

View File

@ -35,7 +35,7 @@ import org.openhab.core.types.State;
* @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 {
@ -185,6 +185,14 @@ 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());
}

View File

@ -22,6 +22,7 @@ import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
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;
@ -43,7 +44,7 @@ import org.openhab.binding.mielecloud.internal.webservice.api.json.VentilationSt
* @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 {
@ -458,6 +459,36 @@ 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.
*

View File

@ -0,0 +1,70 @@
/**
* 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 + "]";
}
}

View File

@ -0,0 +1,89 @@
/**
* 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
+ "]";
}
}

View File

@ -0,0 +1,71 @@
/**
* 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 + "]";
}
}

View File

@ -25,7 +25,7 @@ import org.eclipse.jdt.annotation.Nullable;
*
* @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 {
@ -74,6 +74,8 @@ public class State {
@Nullable
private final List<PlateStep> plateStep = null;
@Nullable
private EcoFeedback ecoFeedback;
@Nullable
private Integer batteryLevel;
public Optional<Status> getStatus() {
@ -189,6 +191,10 @@ public class State {
return Collections.unmodifiableList(plateStep);
}
public Optional<EcoFeedback> getEcoFeedback() {
return Optional.ofNullable(ecoFeedback);
}
public Optional<Integer> getBatteryLevel() {
return Optional.ofNullable(batteryLevel);
}
@ -197,7 +203,7 @@ public class State {
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
@ -222,7 +228,7 @@ public class State {
&& 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
@ -232,7 +238,7 @@ public class State {
+ ", 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 + "]";
}
}

View File

@ -0,0 +1,71 @@
/**
* 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 + "]";
}
}

View File

@ -238,6 +238,12 @@ channel-type.mielecloud.door_state.description=Indicates if the door of the devi
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.

View File

@ -438,6 +438,30 @@
<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>

View File

@ -34,9 +34,12 @@
<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>

View File

@ -38,9 +38,11 @@
<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>

View File

@ -41,9 +41,12 @@
<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>

View File

@ -39,9 +39,12 @@
<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>

View File

@ -0,0 +1,46 @@
<?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>

View File

@ -0,0 +1,53 @@
/**
* 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);
}
}

View File

@ -26,6 +26,8 @@ import org.openhab.binding.mielecloud.internal.webservice.api.json.Device;
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;
@ -40,12 +42,13 @@ import org.openhab.binding.mielecloud.internal.webservice.api.json.Status;
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 {
@ -2106,6 +2109,336 @@ 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:

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
/**
* @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step
* @author Björn Lange - Add eco feedback
*/
@NonNullByDefault
public class DeviceCollectionTest {
@ -287,4 +288,32 @@ 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());
}
}

View File

@ -0,0 +1,142 @@
{
"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
}
}
}

View File

@ -209,7 +209,8 @@ public abstract class AbstractMieleThingHandlerTest extends JavaOSGiTest {
}
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);
@ -217,7 +218,8 @@ public abstract class AbstractMieleThingHandlerTest extends JavaOSGiTest {
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);

View File

@ -42,7 +42,7 @@ public class CoffeeDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
@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

View File

@ -45,7 +45,7 @@ public class CoolingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
@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

View File

@ -42,7 +42,7 @@ public class DishWarmerDeviceThingHandlerTest extends AbstractMieleThingHandlerT
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

View File

@ -28,22 +28,25 @@ import org.openhab.binding.mielecloud.internal.webservice.api.ActionsState;
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
@ -64,6 +67,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
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);
@ -81,6 +86,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
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));
});
}
@ -105,6 +112,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
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);
@ -124,6 +133,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
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));
});
}

View File

@ -28,22 +28,25 @@ import org.openhab.binding.mielecloud.internal.webservice.api.ActionsState;
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
@ -67,6 +70,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
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);
@ -87,6 +91,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
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));
});
}
@ -114,6 +119,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
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);
@ -136,6 +142,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
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));
});
}

View File

@ -40,7 +40,7 @@ public class HobDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@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

View File

@ -40,7 +40,7 @@ public class HoodDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@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

View File

@ -184,7 +184,7 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
}
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);
@ -196,7 +196,7 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
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);
@ -227,77 +227,77 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
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");
}
/**

View File

@ -46,7 +46,7 @@ public class OvenDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@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

View File

@ -41,7 +41,8 @@ public class RoboticVacuumCleanerDeviceThingHandlerTest extends AbstractMieleThi
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

View File

@ -28,17 +28,19 @@ import org.openhab.binding.mielecloud.internal.webservice.api.ActionsState;
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 {
@ -46,7 +48,7 @@ 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
@ -71,6 +73,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
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);
@ -92,6 +96,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
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));
});
}
@ -120,6 +126,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
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);
@ -143,6 +151,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
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));
});
}

View File

@ -43,7 +43,7 @@ public class WineStorageDeviceThingHandlerTest extends AbstractMieleThingHandler
@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