[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 | | 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_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 | | 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 | | battery_level | Number | The battery level of the robotic vacuum cleaner. | Yes |
### Coffee System ### Coffee System
@ -215,6 +217,8 @@ Channel ID and channel type ID match unless noted.
- error_state - error_state
- info_state - info_state
- door_state - door_state
- water_consumption_current
- energy_consumption_current
### Tumble Dryer ### Tumble Dryer
@ -242,6 +246,7 @@ Channel ID and channel type ID match unless noted.
- light_switch - light_switch
- light_can_be_controlled - light_can_be_controlled
- door_state - door_state
- energy_consumption_current
### Freezer ### Freezer
@ -387,6 +392,8 @@ Channel ID and channel type ID match unless noted.
- light_switch - light_switch
- light_can_be_controlled - light_can_be_controlled
- door_state - door_state
- water_consumption_current
- energy_consumption_current
### Washing Machine ### Washing Machine
@ -415,6 +422,8 @@ Channel ID and channel type ID match unless noted.
- light_switch - light_switch
- light_can_be_controlled - light_can_be_controlled
- door_state - door_state
- water_consumption_current
- energy_consumption_current
### Wine Storage ### 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 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 * @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 * 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 @NonNullByDefault
public final class MieleCloudBindingConstants { 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 PLATE_6_POWER_STEP_RAW = "plate_6_power_step_raw";
public static final String DOOR_STATE = "door_state"; public static final String DOOR_STATE = "door_state";
public static final String DOOR_ALARM = "door_alarm"; 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"; 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 Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers * @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API * @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 @NonNullByDefault
public class DishwasherDeviceThingHandler extends AbstractMieleThingHandler { public class DishwasherDeviceThingHandler extends AbstractMieleThingHandler {
@ -54,6 +54,8 @@ public class DishwasherDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(ERROR_STATE), device.getErrorState()); updateState(channel(ERROR_STATE), device.getErrorState());
updateState(channel(INFO_STATE), device.getInfoState()); updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(DOOR_STATE), device.getDoorState()); updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import org.openhab.core.thing.Thing;
* @author Roland Edelhoff - Initial contribution * @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers * @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API * @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 @NonNullByDefault
public class DryerDeviceThingHandler extends AbstractMieleThingHandler { public class DryerDeviceThingHandler extends AbstractMieleThingHandler {
@ -57,6 +57,7 @@ public class DryerDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(INFO_STATE), device.getInfoState()); updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch()); updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState()); updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import org.openhab.core.thing.Thing;
* @author Roland Edelhoff - Initial contribution * @author Roland Edelhoff - Initial contribution
* @author Björn Lange - Add channel state wrappers * @author Björn Lange - Add channel state wrappers
* @author Benjamin Bolte - Add info state channel and map signal flags from API * @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 @NonNullByDefault
public class WashingDeviceThingHandler extends AbstractMieleThingHandler { public class WashingDeviceThingHandler extends AbstractMieleThingHandler {
@ -58,6 +58,8 @@ public class WashingDeviceThingHandler extends AbstractMieleThingHandler {
updateState(channel(INFO_STATE), device.getInfoState()); updateState(channel(INFO_STATE), device.getInfoState());
updateState(channel(LIGHT_SWITCH), device.getLightSwitch()); updateState(channel(LIGHT_SWITCH), device.getLightSwitch());
updateState(channel(DOOR_STATE), device.getDoorState()); updateState(channel(DOOR_STATE), device.getDoorState());
updateState(channel(WATER_CONSUMPTION_CURRENT), device.getCurrentWaterConsumption());
updateState(channel(ENERGY_CONSUMPTION_CURRENT), device.getCurrentEnergyConsumption());
} }
@Override @Override

View File

@ -13,9 +13,12 @@
package org.openhab.binding.mielecloud.internal.handler.channel; package org.openhab.binding.mielecloud.internal.handler.channel;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; 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.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType; 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.library.unit.SIUnits;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; 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. * Utility class handling type conversions from Java types to channel types.
@ -31,6 +36,8 @@ import org.openhab.core.types.UnDefType;
*/ */
@NonNullByDefault @NonNullByDefault
public final class ChannelTypeUtil { public final class ChannelTypeUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelTypeUtil.class);
private ChannelTypeUtil() { private ChannelTypeUtil() {
throw new IllegalStateException("ChannelTypeUtil cannot be instantiated."); 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). // 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); 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 Björn Lange - Initial contribution
* @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channel and map * @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channel and map
* signal flags from API * 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 @NonNullByDefault
public final class DeviceChannelState { public final class DeviceChannelState {
@ -185,6 +185,14 @@ public final class DeviceChannelState {
return ChannelTypeUtil.intToState(device.getSpinningSpeedRaw()); return ChannelTypeUtil.intToState(device.getSpinningSpeedRaw());
} }
public State getCurrentWaterConsumption() {
return ChannelTypeUtil.quantityToState(device.getCurrentWaterConsumption());
}
public State getCurrentEnergyConsumption() {
return ChannelTypeUtil.quantityToState(device.getCurrentEnergyConsumption());
}
public State getBatteryLevel() { public State getBatteryLevel() {
return ChannelTypeUtil.intToState(device.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.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType; 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.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.Ident;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light; 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.PlateStep;
@ -43,7 +44,7 @@ import org.openhab.binding.mielecloud.internal.webservice.api.json.VentilationSt
* @author Björn Lange - Introduced null handling * @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 * @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm, info state channel and map signal
* flags from API * 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 @NonNullByDefault
public class DeviceState { public class DeviceState {
@ -458,6 +459,36 @@ public class DeviceState {
return Optional.of(doorState.get() && failure.get()); 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. * 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 Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step * @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 @NonNullByDefault
public class State { public class State {
@ -74,6 +74,8 @@ public class State {
@Nullable @Nullable
private final List<PlateStep> plateStep = null; private final List<PlateStep> plateStep = null;
@Nullable @Nullable
private EcoFeedback ecoFeedback;
@Nullable
private Integer batteryLevel; private Integer batteryLevel;
public Optional<Status> getStatus() { public Optional<Status> getStatus() {
@ -189,6 +191,10 @@ public class State {
return Collections.unmodifiableList(plateStep); return Collections.unmodifiableList(plateStep);
} }
public Optional<EcoFeedback> getEcoFeedback() {
return Optional.ofNullable(ecoFeedback);
}
public Optional<Integer> getBatteryLevel() { public Optional<Integer> getBatteryLevel() {
return Optional.ofNullable(batteryLevel); return Optional.ofNullable(batteryLevel);
} }
@ -197,7 +203,7 @@ public class State {
public int hashCode() { public int hashCode() {
return Objects.hash(dryingStep, elapsedTime, light, programPhase, ProgramID, programId, programType, return Objects.hash(dryingStep, elapsedTime, light, programPhase, ProgramID, programId, programType,
remainingTime, remoteEnable, signalDoor, signalFailure, signalInfo, startTime, status, remainingTime, remoteEnable, signalDoor, signalFailure, signalInfo, startTime, status,
targetTemperature, temperature, ventilationStep, plateStep, batteryLevel); targetTemperature, temperature, ventilationStep, plateStep, ecoFeedback, batteryLevel);
} }
@Override @Override
@ -222,7 +228,7 @@ public class State {
&& Objects.equals(targetTemperature, other.targetTemperature) && Objects.equals(targetTemperature, other.targetTemperature)
&& Objects.equals(temperature, other.temperature) && Objects.equals(temperature, other.temperature)
&& Objects.equals(ventilationStep, other.ventilationStep) && Objects.equals(plateStep, other.plateStep) && 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 @Override
@ -232,7 +238,7 @@ public class State {
+ ", targetTemperature=" + targetTemperature + ", temperature=" + temperature + ", signalInfo=" + ", targetTemperature=" + targetTemperature + ", temperature=" + temperature + ", signalInfo="
+ signalInfo + ", signalFailure=" + signalFailure + ", signalDoor=" + signalDoor + ", remoteEnable=" + signalInfo + ", signalFailure=" + signalFailure + ", signalDoor=" + signalDoor + ", remoteEnable="
+ remoteEnable + ", light=" + light + ", elapsedTime=" + elapsedTime + ", dryingStep=" + dryingStep + 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.label=Door Alarm
channel-type.mielecloud.door_alarm.description=Indicates if the door alarm of the device is active. 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.label=Battery Level
channel-type.mielecloud.battery_level.description=The battery level of the robotic vacuum cleaner. channel-type.mielecloud.battery_level.description=The battery level of the robotic vacuum cleaner.

View File

@ -438,6 +438,30 @@
<state readOnly="true"/> <state readOnly="true"/>
</channel-type> </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"> <channel-type id="battery_level">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>@text/channel-type.mielecloud.battery_level.label</label> <label>@text/channel-type.mielecloud.battery_level.label</label>

View File

@ -34,9 +34,12 @@
<channel id="error_state" typeId="error_state"/> <channel id="error_state" typeId="error_state"/>
<channel id="info_state" typeId="info_state"/> <channel id="info_state" typeId="info_state"/>
<channel id="door_state" typeId="door_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> </channels>
<properties> <properties>
<property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property> <property name="vendor">Miele</property>
</properties> </properties>

View File

@ -38,9 +38,11 @@
<channel id="light_switch" typeId="light_switch"/> <channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/> <channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_state"/> <channel id="door_state" typeId="door_state"/>
<channel id="energy_consumption_current" typeId="energy_consumption_current"/>
</channels> </channels>
<properties> <properties>
<property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property> <property name="vendor">Miele</property>
</properties> </properties>

View File

@ -41,9 +41,12 @@
<channel id="light_switch" typeId="light_switch"/> <channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/> <channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_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> </channels>
<properties> <properties>
<property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property> <property name="vendor">Miele</property>
</properties> </properties>

View File

@ -39,9 +39,12 @@
<channel id="light_switch" typeId="light_switch"/> <channel id="light_switch" typeId="light_switch"/>
<channel id="light_can_be_controlled" typeId="light_can_be_controlled"/> <channel id="light_can_be_controlled" typeId="light_can_be_controlled"/>
<channel id="door_state" typeId="door_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> </channels>
<properties> <properties>
<property name="thingTypeVersion">1</property>
<property name="vendor">Miele</property> <property name="vendor">Miele</property>
</properties> </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.DeviceIdentLabel;
import org.openhab.binding.mielecloud.internal.webservice.api.json.DeviceType; 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.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.Ident;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Light; 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.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.Temperature;
import org.openhab.binding.mielecloud.internal.webservice.api.json.Type; 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.VentilationStep;
import org.openhab.binding.mielecloud.internal.webservice.api.json.WaterConsumption;
/** /**
* @author Björn Lange - Initial contribution * @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 * @author Benjamin Bolte - Add pre-heat finished, plate step, door state, door alarm and info state channels and map
* signal flags from API * 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 @NonNullByDefault
public class DeviceStateTest { public class DeviceStateTest {
@ -2106,6 +2109,336 @@ public class DeviceStateTest {
assertFalse(lightState.isPresent()); 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 @Test
public void testGetBatteryLevel() { public void testGetBatteryLevel() {
// given: // given:

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
/** /**
* @author Björn Lange - Initial contribution * @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add plate step * @author Benjamin Bolte - Add plate step
* @author Björn Lange - Add eco feedback
*/ */
@NonNullByDefault @NonNullByDefault
public class DeviceCollectionTest { public class DeviceCollectionTest {
@ -287,4 +288,32 @@ public class DeviceCollectionTest {
assertEquals(Integer.valueOf(0), targetTemperature.getValueLocalized().get()); assertEquals(Integer.valueOf(0), targetTemperature.getValueLocalized().get());
assertEquals("Celsius", targetTemperature.getUnit().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, protected AbstractMieleThingHandler createThingHandler(ThingTypeUID thingTypeUid, ThingUID thingUid,
Class<? extends AbstractMieleThingHandler> expectedHandlerClass, String deviceIdentifier) { Class<? extends AbstractMieleThingHandler> expectedHandlerClass, String deviceIdentifier,
String thingTypeVersion) {
ThingRegistry registry = getThingRegistry(); ThingRegistry registry = getThingRegistry();
List<Channel> channels = createChannelsForThingHandler(thingTypeUid, thingUid); List<Channel> channels = createChannelsForThingHandler(thingTypeUid, thingUid);
@ -217,7 +218,8 @@ public abstract class AbstractMieleThingHandlerTest extends JavaOSGiTest {
Thing thing = ThingBuilder.create(thingTypeUid, thingUid) Thing thing = ThingBuilder.create(thingTypeUid, thingUid)
.withConfiguration(new Configuration(Collections .withConfiguration(new Configuration(Collections
.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, deviceIdentifier))) .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); assertNotNull(thing);
registry.add(thing); registry.add(thing);

View File

@ -42,7 +42,7 @@ public class CoffeeDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_SYSTEM_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_SYSTEM_THING_UID,
CoffeeSystemThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); CoffeeSystemThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test

View File

@ -45,7 +45,7 @@ public class CoolingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, FRIDGE_FREEZER_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, FRIDGE_FREEZER_DEVICE_THING_UID,
CoolingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); CoolingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test

View File

@ -42,7 +42,7 @@ public class DishWarmerDeviceThingHandlerTest extends AbstractMieleThingHandlerT
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER,
MieleCloudBindingIntegrationTestConstants.DISH_WARMER_DEVICE_THING_UID, MieleCloudBindingIntegrationTestConstants.DISH_WARMER_DEVICE_THING_UID,
DishWarmerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); DishWarmerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @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.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus; 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.ProgramStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType; import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; 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.types.StringType;
import org.openhab.core.library.unit.Units;
/** /**
* @author Björn Lange - Initial contribution * @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests * @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 @NonNullByDefault
public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerTest { public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_THING_UID,
DishwasherDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); DishwasherDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
} }
@Test @Test
@ -64,6 +67,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
when(deviceState.getStartTime()).thenReturn(Optional.empty()); when(deviceState.getStartTime()).thenReturn(Optional.empty());
when(deviceState.getElapsedTime()).thenReturn(Optional.empty()); when(deviceState.getElapsedTime()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty()); when(deviceState.getDoorState()).thenReturn(Optional.empty());
when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.empty());
when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); 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(DELAYED_START_TIME));
assertEquals(NULL_VALUE_STATE, getChannelState(PROGRAM_ELAPSED_TIME)); assertEquals(NULL_VALUE_STATE, getChannelState(PROGRAM_ELAPSED_TIME));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE)); 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.hasError()).thenReturn(false);
when(deviceState.hasInfo()).thenReturn(true); when(deviceState.hasInfo()).thenReturn(true);
when(deviceState.getDoorState()).thenReturn(Optional.of(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: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); getBridgeHandler().onDeviceStateUpdated(deviceState);
@ -124,6 +133,8 @@ public class DishwasherDeviceThingHandlerTest extends AbstractMieleThingHandlerT
assertEquals(OnOffType.OFF, getChannelState(ERROR_STATE)); assertEquals(OnOffType.OFF, getChannelState(ERROR_STATE));
assertEquals(OnOffType.ON, getChannelState(INFO_STATE)); assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.ON, getChannelState(DOOR_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.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus; 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.ProgramStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType; import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; 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.types.StringType;
import org.openhab.core.library.unit.Units;
/** /**
* @author Björn Lange - Initial contribution * @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests * @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 @NonNullByDefault
public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest { public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_THING_UID,
DryerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); DryerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
} }
@Test @Test
@ -67,6 +70,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
when(deviceState.getDryingTargetRaw()).thenReturn(Optional.empty()); when(deviceState.getDryingTargetRaw()).thenReturn(Optional.empty());
when(deviceState.getLightState()).thenReturn(Optional.empty()); when(deviceState.getLightState()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty()); when(deviceState.getDoorState()).thenReturn(Optional.empty());
when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); 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(DRYING_TARGET_RAW));
assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH)); assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE)); 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.hasInfo()).thenReturn(true);
when(deviceState.getLightState()).thenReturn(Optional.of(false)); when(deviceState.getLightState()).thenReturn(Optional.of(false));
when(deviceState.getDoorState()).thenReturn(Optional.of(false)); when(deviceState.getDoorState()).thenReturn(Optional.of(false));
when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.of(new Quantity(2.5, "Wh")));
// when: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); getBridgeHandler().onDeviceStateUpdated(deviceState);
@ -136,6 +142,7 @@ public class DryerDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
assertEquals(OnOffType.ON, getChannelState(INFO_STATE)); assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH)); assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH));
assertEquals(OnOffType.OFF, getChannelState(DOOR_STATE)); 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 @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_THING_UID,
HobDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); HobDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test

View File

@ -40,7 +40,7 @@ public class HoodDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_THING_UID,
HoodDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); HoodDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test

View File

@ -184,7 +184,7 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
} }
private void testHandlerCanBeCreatedForMieleDevice(ThingTypeUID thingTypeUid, ThingUID thingUid, String label, private void testHandlerCanBeCreatedForMieleDevice(ThingTypeUID thingTypeUid, ThingUID thingUid, String label,
Class<? extends ThingHandler> expectedHandlerClass) Class<? extends ThingHandler> expectedHandlerClass, String thingTypeVersion)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
// given: // given:
MieleWebservice webservice = mock(MieleWebservice.class); MieleWebservice webservice = mock(MieleWebservice.class);
@ -196,7 +196,7 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
Thing device = ThingBuilder.create(thingTypeUid, thingUid) Thing device = ThingBuilder.create(thingTypeUid, thingUid)
.withConfiguration(new Configuration(Collections .withConfiguration(new Configuration(Collections
.singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, DEVICE_IDENTIFIER))) .singletonMap(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER, DEVICE_IDENTIFIER)))
.withLabel(label).build(); .withLabel(label).withProperty("thingTypeVersion", thingTypeVersion).build();
assertNotNull(device); assertNotNull(device);
verifyHandlerCreation(webservice, device, expectedHandlerClass); verifyHandlerCreation(webservice, device, expectedHandlerClass);
@ -227,77 +227,77 @@ public class MieleHandlerFactoryTest extends JavaOSGiTest {
public void testHandlerCanBeCreatedForWashingDevice() public void testHandlerCanBeCreatedForWashingDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE,
WASHING_MACHINE_TYPE, "DA-6996", WashingDeviceThingHandler.class); WASHING_MACHINE_TYPE, "DA-6996", WashingDeviceThingHandler.class, "1");
} }
@Test @Test
public void testHandlerCanBeCreatedForOvenDevice() public void testHandlerCanBeCreatedForOvenDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_TYPE, "OV-6887", testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_TYPE, "OV-6887",
OvenDeviceThingHandler.class); OvenDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForHobDevice() public void testHandlerCanBeCreatedForHobDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_TYPE, "HB-3887", testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOB, HOB_DEVICE_TYPE, "HB-3887",
HobDeviceThingHandler.class); HobDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForFridgeFreezerDevice() public void testHandlerCanBeCreatedForFridgeFreezerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_FRIDGE_FREEZER,
FRIDGE_FREEZER_DEVICE_TYPE, "CD-6097", CoolingDeviceThingHandler.class); FRIDGE_FREEZER_DEVICE_TYPE, "CD-6097", CoolingDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForHoodDevice() public void testHandlerCanBeCreatedForHoodDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_TYPE, "HD-2097", testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_HOOD, HOOD_DEVICE_TYPE, "HD-2097",
HoodDeviceThingHandler.class); HoodDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForCoffeeDevice() public void testHandlerCanBeCreatedForCoffeeDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_DEVICE_TYPE, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_COFFEE_SYSTEM, COFFEE_DEVICE_TYPE,
"DA-6997", CoffeeSystemThingHandler.class); "DA-6997", CoffeeSystemThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForWineStorageDevice() public void testHandlerCanBeCreatedForWineStorageDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE,
WINE_STORAGE_DEVICE_TYPE, "WS-6907", WineStorageDeviceThingHandler.class); WINE_STORAGE_DEVICE_TYPE, "WS-6907", WineStorageDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForDryerDevice() public void testHandlerCanBeCreatedForDryerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_TYPE, "DR-0907", testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DRYER, DRYER_DEVICE_TYPE, "DR-0907",
DryerDeviceThingHandler.class); DryerDeviceThingHandler.class, "1");
} }
@Test @Test
public void testHandlerCanBeCreatedForDishwasherDevice() public void testHandlerCanBeCreatedForDishwasherDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_TYPE, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISHWASHER, DISHWASHER_DEVICE_TYPE,
"DR-0907", DishwasherDeviceThingHandler.class); "DR-0907", DishwasherDeviceThingHandler.class, "1");
} }
@Test @Test
public void testHandlerCanBeCreatedForDishWarmerDevice() public void testHandlerCanBeCreatedForDishWarmerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER, testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_DISH_WARMER,
DISH_WARMER_DEVICE_TYPE, "DW-0907", DishWarmerDeviceThingHandler.class); DISH_WARMER_DEVICE_TYPE, "DW-0907", DishWarmerDeviceThingHandler.class, "0");
} }
@Test @Test
public void testHandlerCanBeCreatedForRoboticVacuumCleanerDevice() public void testHandlerCanBeCreatedForRoboticVacuumCleanerDevice()
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
testHandlerCanBeCreatedForMieleDevice(MieleCloudBindingConstants.THING_TYPE_ROBOTIC_VACUUM_CLEANER, 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 @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_OVEN, OVEN_DEVICE_THING_UID,
OvenDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); OvenDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test

View File

@ -41,7 +41,8 @@ public class RoboticVacuumCleanerDeviceThingHandlerTest extends AbstractMieleThi
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_ROBOTIC_VACUUM_CLEANER, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_ROBOTIC_VACUUM_CLEANER,
MieleCloudBindingIntegrationTestConstants.ROBOTIC_VACUUM_CLEANER_THING_UID, MieleCloudBindingIntegrationTestConstants.ROBOTIC_VACUUM_CLEANER_THING_UID,
RoboticVacuumCleanerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); RoboticVacuumCleanerDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER,
"0");
} }
@Test @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.DeviceState;
import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus; 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.ProgramStatus;
import org.openhab.binding.mielecloud.internal.webservice.api.Quantity;
import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType; import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
/** /**
* @author Björn Lange - Initial contribution * @author Björn Lange - Initial contribution
* @author Benjamin Bolte - Add info state channel and map signal flags from API tests * @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 @NonNullByDefault
public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest { public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest {
@ -46,7 +48,7 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
@Override @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, WASHING_MACHINE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WASHING_MACHINE, WASHING_MACHINE_THING_UID,
WashingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); WashingDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "1");
} }
@Test @Test
@ -71,6 +73,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
when(deviceState.getTargetTemperature(0)).thenReturn(Optional.empty()); when(deviceState.getTargetTemperature(0)).thenReturn(Optional.empty());
when(deviceState.getLightState()).thenReturn(Optional.empty()); when(deviceState.getLightState()).thenReturn(Optional.empty());
when(deviceState.getDoorState()).thenReturn(Optional.empty()); when(deviceState.getDoorState()).thenReturn(Optional.empty());
when(deviceState.getCurrentWaterConsumption()).thenReturn(Optional.empty());
when(deviceState.getCurrentEnergyConsumption()).thenReturn(Optional.empty());
// when: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); getBridgeHandler().onDeviceStateUpdated(deviceState);
@ -92,6 +96,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
assertEquals(NULL_VALUE_STATE, getChannelState(TEMPERATURE_TARGET)); assertEquals(NULL_VALUE_STATE, getChannelState(TEMPERATURE_TARGET));
assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH)); assertEquals(NULL_VALUE_STATE, getChannelState(LIGHT_SWITCH));
assertEquals(NULL_VALUE_STATE, getChannelState(DOOR_STATE)); 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.hasInfo()).thenReturn(true);
when(deviceState.getLightState()).thenReturn(Optional.of(false)); when(deviceState.getLightState()).thenReturn(Optional.of(false));
when(deviceState.getDoorState()).thenReturn(Optional.of(true)); 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: // when:
getBridgeHandler().onDeviceStateUpdated(deviceState); getBridgeHandler().onDeviceStateUpdated(deviceState);
@ -143,6 +151,8 @@ public class WashingDeviceThingHandlerTest extends AbstractMieleThingHandlerTest
assertEquals(OnOffType.ON, getChannelState(INFO_STATE)); assertEquals(OnOffType.ON, getChannelState(INFO_STATE));
assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH)); assertEquals(OnOffType.OFF, getChannelState(LIGHT_SWITCH));
assertEquals(OnOffType.ON, getChannelState(DOOR_STATE)); 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 @Override
protected AbstractMieleThingHandler setUpThingHandler() { protected AbstractMieleThingHandler setUpThingHandler() {
return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE, WINE_STORAGE_DEVICE_THING_UID, return createThingHandler(MieleCloudBindingConstants.THING_TYPE_WINE_STORAGE, WINE_STORAGE_DEVICE_THING_UID,
WineStorageDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER); WineStorageDeviceThingHandler.class, MieleCloudBindingIntegrationTestConstants.SERIAL_NUMBER, "0");
} }
@Test @Test