]> git.basschouten.com Git - openhab-addons.git/commitdiff
[mielecloud] Add channels energy and water consumption (#14456)
authorBjörn Lange <bjoern.lange@udo.edu>
Fri, 24 Mar 2023 22:15:28 +0000 (23:15 +0100)
committerGitHub <noreply@github.com>
Fri, 24 Mar 2023 22:15:28 +0000 (23:15 +0100)
* 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>
37 files changed:
bundles/org.openhab.binding.mielecloud/README.md
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/MieleCloudBindingConstants.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/handler/DishwasherDeviceThingHandler.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/handler/DryerDeviceThingHandler.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/handler/WashingDeviceThingHandler.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/handler/channel/ChannelTypeUtil.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/handler/channel/DeviceChannelState.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/DeviceState.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/Quantity.java [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EcoFeedback.java [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EnergyConsumption.java [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/State.java
bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/WaterConsumption.java [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/i18n/mielecloud.properties
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/thing/channelTypes.xml
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/thing/dishwasherDevice.xml
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/thing/dryerDevice.xml
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/thing/washerDryer.xml
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/thing/washingMachine.xml
bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/update/instructions.xml [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/test/java/org/openhab/binding/mielecloud/internal/handler/channel/ChannelTypeUtilTest.java [new file with mode: 0644]
bundles/org.openhab.binding.mielecloud/src/test/java/org/openhab/binding/mielecloud/internal/webservice/api/DeviceStateTest.java
bundles/org.openhab.binding.mielecloud/src/test/java/org/openhab/binding/mielecloud/internal/webservice/api/json/DeviceCollectionTest.java
bundles/org.openhab.binding.mielecloud/src/test/resources/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithEcoFeedback.json [new file with mode: 0644]
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/AbstractMieleThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/CoffeeDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/CoolingDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/DishWarmerDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/DishwasherDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/DryerDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/HobDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/HoodDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/MieleHandlerFactoryTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/OvenDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/RoboticVacuumCleanerDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/WashingDeviceThingHandlerTest.java
itests/org.openhab.binding.mielecloud.tests/src/main/java/org/openhab/binding/mielecloud/internal/handler/WineStorageDeviceThingHandlerTest.java

index eca75279f5b099154cbe60bb104480cb12ab8b2c..8e8e42e21a7f6547b44740349462ebacaac3ae0a 100644 (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
 
index cbb3f8810699e93e815499d43be8a097e9165ee2..5e9549afdf186600484ac15b56fe3b7fe0e9c7ff 100644 (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";
     }
 
index 1d5dd0faba589adb36dfc5de539283727eaa3e0a..d210207bce3a9ed2ec082874149771ce247b1acb 100644 (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
index a69b2c3c70ded1584c237565e22fcae15b6ac1f8..d78ce68bcfb9036c93d4a0460530241d0b14670f 100644 (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
index 08e05a26d7d8d523372bb525cb3aacae1d48d323..6102e0e32c68d5464eceb3f293eb94cb101ddbe8 100644 (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
index 864de6975733b0e13c8ac91218c2f6e47dd13b3f..a1a50d28a71a486d657fb01c0ea0aa1702eb0635 100644 (file)
 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();
+        }
+    }
 }
index f517002f297c8f873c86e2745e8d2e8f734e6be8..dd472fc635cf7da7e779aeb66f26f6d79acbb1da 100644 (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());
     }
index 7c99e1b54f3f8ca0267263ff6a7247f01e86b9e4..64477b6f911d325427bd8f9345a09cf856f42d9d 100644 (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.
      *
diff --git a/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/Quantity.java b/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/Quantity.java
new file mode 100644 (file)
index 0000000..f529190
--- /dev/null
@@ -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 + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EcoFeedback.java b/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EcoFeedback.java
new file mode 100644 (file)
index 0000000..f90cfd2
--- /dev/null
@@ -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
+                + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EnergyConsumption.java b/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/EnergyConsumption.java
new file mode 100644 (file)
index 0000000..8bdf770
--- /dev/null
@@ -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 + "]";
+    }
+}
index 6b813e7a089346d5d5c2b396f53b2514898da0f6..e95bcca8fb624e3392088434e7b3628a19407838 100644 (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 + "]";
     }
 }
diff --git a/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/WaterConsumption.java b/bundles/org.openhab.binding.mielecloud/src/main/java/org/openhab/binding/mielecloud/internal/webservice/api/json/WaterConsumption.java
new file mode 100644 (file)
index 0000000..aa95995
--- /dev/null
@@ -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 + "]";
+    }
+}
index e74b7d860a38fe54e5cb7655b9758127ec48cb90..95e138e11558aa1d9f7c419e3ee101e9fb7cda8c 100644 (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.
 
index e1cb16cca921bf28a45e06995fc4405e9f08e381..c0451fc296caa3f76c4852b57bc4fd80a8ac2d7b 100644 (file)
                <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>
index 2d97427f21bd9ca3270330c89293f3936823766b..64fe077e019aad4efab3591a1fd68d6e3e9382a9 100644 (file)
                        <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>
 
index 0522f0021003d10e64e24f5b1fc945010655b9dd..6d26b619804b5b074be49b74a760721a42b68069 100644 (file)
                        <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>
 
index 21f21cb0e662b2ae8aa57287c2470a9e347fd459..a9b7895c1a44ffb8110fe7ebb84379a02bf25af3 100644 (file)
                        <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>
 
index 8143ebb70f00f2783d036e98b295cb8c8deec141..8be636fb3643dc0092dd93420bf45995f257a0f5 100644 (file)
                        <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>
 
diff --git a/bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.mielecloud/src/main/resources/OH-INF/update/instructions.xml
new file mode 100644 (file)
index 0000000..07d1291
--- /dev/null
@@ -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>
diff --git a/bundles/org.openhab.binding.mielecloud/src/test/java/org/openhab/binding/mielecloud/internal/handler/channel/ChannelTypeUtilTest.java b/bundles/org.openhab.binding.mielecloud/src/test/java/org/openhab/binding/mielecloud/internal/handler/channel/ChannelTypeUtilTest.java
new file mode 100644 (file)
index 0000000..70f5a28
--- /dev/null
@@ -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);
+    }
+}
index d2387d0d0f78599f9e24ff2a0795064ac2ff9b4f..616c9fbdd0c893df472f30c8b7fe77cb31019fcd 100644 (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:
index b73051127c032438c64a758addf21a030effb1ca..26c9325e11993ffc86827364c6a567ea1983aaba 100644 (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());
+    }
 }
diff --git a/bundles/org.openhab.binding.mielecloud/src/test/resources/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithEcoFeedback.json b/bundles/org.openhab.binding.mielecloud/src/test/resources/org/openhab/binding/mielecloud/internal/webservice/api/json/deviceCollectionWithEcoFeedback.json
new file mode 100644 (file)
index 0000000..6ca3226
--- /dev/null
@@ -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
+        }
+    }
+}
\ No newline at end of file
index 7ed43b57253ca4343fb04fbe48d82f368bd59a07..c29a9f15c3c5adbe8782fe5742b42434e6ea3b7d 100644 (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);
index fecedc11efe11a31bb2034057a43070998b301c1..d97582c375e2fcea4d593bd90da5b9a142ccd4bb 100644 (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
index fcb8d41b1045813e0d9bf260c2b72cf5f498e484..70024bca1c9764779a93fc9d26843801f181e474 100644 (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
index 7f9498388482c9292950aef5cb022e0b1450dc3f..ef685835f3b975393e1b40eb071d5ee2ea435d85 100644 (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
index ca453fe30f02e0d06ac466fb3da90691dc61829a..a4d73edbcabb9dd26952cc72dbb787975b8ef7af 100644 (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));
         });
     }
 
index c1bb65f9bdd9cadccb70f80518389b0c0502af56..c7069ff784ebf99e0f21d25dfd865a4e98a4f597 100644 (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));
         });
     }
 
index 4a57cc474e9bc9f43e72a9ac321efc1166f3973e..007ddd08cc5b31a394a5a262c5d33bc2f680036c 100644 (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
index ef87d664860cce0ff8f913425b8c040d45f3061c..ecef8bfde7b1d9e2693c9eb6238a4685a1e7069c 100644 (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
index 61b810142e1c5c06504fb73015cbf483efe49adc..ac7ed386a7451cab9940a2cd58d3b4e79c6a2a80 100644 (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");
     }
 
     /**
index 762483af7babb790dd12873a53445353d657a937..beaa6a33b760c4c330f470718355e876eaa281d6 100644 (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
index 23a74a9e72ff699bd47d8d127c69adb1b061486e..f63bea755eaeaf8d22fdc291ccde516fbc482bc2 100644 (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
index e495cab3ee7b9ac68106fd070f1ae9852ae55afa..176a2b259b209b268cc6cd4d85e9c94ba34afa7b 100644 (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));
         });
     }
 
index b6540ba0e7d559ba5e9b96b54cebfbbc09fc21bb..8cfad8cf5b3e95c5d8c980264fdf4f5cca7a14e3 100644 (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