]> git.basschouten.com Git - openhab-addons.git/commitdiff
Add new channels for water and power consumption for washing machines and dishwashers...
authorjlaur <jacob-github@vindvejr.dk>
Sat, 25 Sep 2021 19:09:26 +0000 (21:09 +0200)
committerGitHub <noreply@github.com>
Sat, 25 Sep 2021 19:09:26 +0000 (21:09 +0200)
Fixes #11297

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
20 files changed:
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtil.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java
bundles/org.openhab.binding.miele/src/main/resources/OH-INF/thing/channeltypes.xml
bundles/org.openhab.binding.miele/src/main/resources/OH-INF/thing/dishwasher.xml
bundles/org.openhab.binding.miele/src/main/resources/OH-INF/thing/washingmachine.xml
bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtilTest.java [new file with mode: 0644]

diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtil.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtil.java
new file mode 100644 (file)
index 0000000..409c9fe
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.miele.internal;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The {@link ExtendedDeviceStateUtil} class contains utility methods for parsing
+ * ExtendedDeviceState information
+ *
+ * @author Jacob Laursen - Added power/water consumption channels
+ */
+public class ExtendedDeviceStateUtil {
+    private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
+
+    /**
+     * Convert byte array to hex representation.
+     */
+    public static String bytesToHex(byte[] bytes) {
+        byte[] hexChars = new byte[bytes.length * 2];
+        for (int j = 0; j < bytes.length; j++) {
+            int v = bytes[j] & 0xFF;
+            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
+            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+        }
+
+        return new String(hexChars, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Convert string consisting of 8 bit characters to byte array.
+     * Note: This simple operation has been extracted and pure here to document
+     * and ensure correct behavior for 8 bit characters that should be turned
+     * into single bytes without any UTF-8 encoding.
+     */
+    public static byte[] stringToBytes(String input) {
+        return input.getBytes(StandardCharsets.ISO_8859_1);
+    }
+}
index df74fceaa225f643ba84b95e38e4bf49d67f621b..1c306cd9ee84d5c9bd99d5a68348ed86f9f70576 100644 (file)
@@ -31,6 +31,11 @@ public class MieleBindingConstants {
     public static final String DEVICE_CLASS = "dc";
     public static final String PROTOCOL_PROPERTY_NAME = "protocol";
     public static final String SERIAL_NUMBER_PROPERTY_NAME = "serialNumber";
+    public static final String EXTENDED_DEVICE_STATE_PROPERTY_NAME = "extendedDeviceState";
+
+    // Shared Channel ID's
+    public static final String POWER_CONSUMPTION_CHANNEL_ID = "powerConsumption";
+    public static final String WATER_CONSUMPTION_CHANNEL_ID = "waterConsumption";
 
     // List of all Thing Type UIDs
     public static final ThingTypeUID THING_TYPE_XGW3000 = new ThingTypeUID(BINDING_ID, "xgw3000");
index 663ee195ebe4849a7160514c377eca4bce1ffdf6..260449f3b0ade962b5512fbf02ba58842416f14f 100644 (file)
@@ -23,6 +23,7 @@ import org.openhab.core.types.Type;
  * returned by the appliance to a compatible State
  *
  * @author Karel Goderis - Initial contribution
+ * @author Jacob Laursen - Added power/water consumption channels
  */
 public interface ApplianceChannelSelector {
 
@@ -45,6 +46,12 @@ public interface ApplianceChannelSelector {
      */
     boolean isProperty();
 
+    /**
+     * Returns true if the given channel is extracted from extended
+     * state information
+     */
+    boolean isExtendedState();
+
     /**
      *
      * Returns a State for the given string, taking into
index 6360b1f1393d7bedf0e505bf5420ff6c001de792..2395f847091e8041cb09b599bb176e16a865f8f3 100644 (file)
@@ -99,6 +99,11 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index dc06b24575fb8317f1b35cecf57cde4b8aa55f76..b7086f89f9cd145910f790aac5c1e5e75d912afb 100644 (file)
 package org.openhab.binding.miele.internal.handler;
 
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.POWER_CONSUMPTION_CHANNEL_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.PROTOCOL_PROPERTY_NAME;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CONSUMPTION_CHANNEL_ID;
+
+import java.math.BigDecimal;
 
 import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.types.Command;
@@ -33,9 +39,14 @@ import com.google.gson.JsonElement;
  * @author Karel Goderis - Initial contribution
  * @author Kai Kreuzer - fixed handling of REFRESH commands
  * @author Martin Lepsy - fixed handling of empty JSON results
- * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
+ * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
  */
-public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector> {
+public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector>
+        implements ExtendedDeviceStateListener {
+
+    private static final int POWER_CONSUMPTION_BYTE_POSITION = 16;
+    private static final int WATER_CONSUMPTION_BYTE_POSITION = 18;
+    private static final int EXTENDED_STATE_SIZE_BYTES = 24;
 
     private final Logger logger = LoggerFactory.getLogger(DishWasherHandler.class);
 
@@ -84,4 +95,20 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
                     channelID, command.toString());
         }
     }
+
+    public void onApplianceExtendedStateChanged(byte[] extendedDeviceState) {
+        if (extendedDeviceState.length != EXTENDED_STATE_SIZE_BYTES) {
+            logger.error("Unexpected size of extended state: {}", extendedDeviceState);
+            return;
+        }
+
+        BigDecimal kiloWattHoursTenths = BigDecimal
+                .valueOf(extendedDeviceState[POWER_CONSUMPTION_BYTE_POSITION] & 0xff);
+        var kiloWattHours = new QuantityType<>(kiloWattHoursTenths.divide(BigDecimal.valueOf(10)), Units.KILOWATT_HOUR);
+        updateExtendedState(POWER_CONSUMPTION_CHANNEL_ID, kiloWattHours);
+
+        BigDecimal decilitres = BigDecimal.valueOf(extendedDeviceState[WATER_CONSUMPTION_BYTE_POSITION] & 0xff);
+        var litres = new QuantityType<>(decilitres.divide(BigDecimal.valueOf(10)), Units.LITRE);
+        updateExtendedState(WATER_CONSUMPTION_CHANNEL_ID, litres);
+    }
 }
index f44b5008436a38d9366a181288d3639303284700..d9b227bc00f412ec7e71692803c0b231eeb6bc5f 100644 (file)
  */
 package org.openhab.binding.miele.internal.handler;
 
+import static org.openhab.binding.miele.internal.MieleBindingConstants.EXTENDED_DEVICE_STATE_PROPERTY_NAME;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.POWER_CONSUMPTION_CHANNEL_ID;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CONSUMPTION_CHANNEL_ID;
+
 import java.lang.reflect.Method;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -22,6 +26,7 @@ import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaD
 import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.Type;
@@ -36,17 +41,18 @@ import com.google.gson.JsonElement;
  *
  * @author Karel Goderis - Initial contribution
  * @author Kai Kreuzer - Changed START_TIME to DateTimeType
+ * @author Jacob Laursen - Added power/water consumption channels
  */
 public enum DishwasherChannelSelector implements ApplianceChannelSelector {
 
-    PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
-    DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
-    BRAND_ID("brandId", "brandId", StringType.class, true),
-    COMPANY_ID("companyId", "companyId", StringType.class, true),
-    STATE("state", "state", StringType.class, false),
-    PROGRAMID("programId", "program", StringType.class, false),
-    PROGRAMPHASE("phase", "phase", StringType.class, false),
-    START_TIME("startTime", "start", DateTimeType.class, false) {
+    PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
+    DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
+    BRAND_ID("brandId", "brandId", StringType.class, true, false),
+    COMPANY_ID("companyId", "companyId", StringType.class, true, false),
+    STATE("state", "state", StringType.class, false, false),
+    PROGRAMID("programId", "program", StringType.class, false, false),
+    PROGRAMPHASE("phase", "phase", StringType.class, false, false),
+    START_TIME("startTime", "start", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -60,7 +66,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    DURATION("duration", "duration", DateTimeType.class, false) {
+    DURATION("duration", "duration", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -74,7 +80,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
+    ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -88,7 +94,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
+    FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -102,7 +108,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    DOOR("signalDoor", "door", OpenClosedType.class, false) {
+    DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             if ("true".equals(s)) {
@@ -116,7 +122,11 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
             return UnDefType.UNDEF;
         }
     },
-    SWITCH(null, "switch", OnOffType.class, false);
+    SWITCH(null, "switch", OnOffType.class, false, false),
+    POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
+            true),
+    WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
+            true);
 
     private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);
 
@@ -124,13 +134,15 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
     private final String channelID;
     private final Class<? extends Type> typeClass;
     private final boolean isProperty;
+    private final boolean isExtendedState;
 
-    DishwasherChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
-            boolean isProperty) {
+    DishwasherChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass, boolean isProperty,
+            boolean isExtendedState) {
         this.mieleID = propertyID;
         this.channelID = channelID;
         this.typeClass = typeClass;
         this.isProperty = isProperty;
+        this.isExtendedState = isExtendedState;
     }
 
     @Override
@@ -158,6 +170,11 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return isExtendedState;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java
new file mode 100644 (file)
index 0000000..f34623d
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.miele.internal.handler;
+
+/**
+ * Appliance handlers can implement the {@link ExtendedDeviceStateListener} interface
+ * to extract additional information from the ExtendedDeviceState property.
+ *
+ * @author Jacob Laursen - Added power/water consumption channels
+ */
+public interface ExtendedDeviceStateListener {
+    void onApplianceExtendedStateChanged(byte[] extendedDeviceState);
+}
index 34460ded5fb68b3c196218015a4c866c13d3b928..ceb8706356b363c7ad626c5fe5e8271afdd2831a 100644 (file)
@@ -109,6 +109,11 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index 10e16c8da9bb5e20ca19c854761f5621cde253a8..c2dbd7417cf28ccac64db5cf13e7dbf5f28a67bb 100644 (file)
@@ -126,6 +126,11 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index ebc86433d8dcaedc4ee8c5fccae62a5598eb274b..d588c9b11bb4006c78535f6b61ad36f06e48a316 100644 (file)
@@ -129,6 +129,11 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index bdb416829cdf3272e67baea3c15b0e49068f58bd..c36f85711b94a96483698d2754df69a0ac411150 100644 (file)
@@ -96,6 +96,11 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index f63002f3a260830223407fbc84ddb104069035fe..b743d9864a910e5d91f5f0c443e4dc8a13694795 100644 (file)
@@ -21,6 +21,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.StringUtils;
+import org.openhab.binding.miele.internal.ExtendedDeviceStateUtil;
 import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
 import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
@@ -36,6 +37,7 @@ import org.openhab.core.thing.binding.BaseThingHandler;
 import org.openhab.core.thing.binding.ThingHandler;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -155,8 +157,10 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                 for (JsonElement prop : dco.Properties.getAsJsonArray()) {
                     try {
                         DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
-                        dp.Value = StringUtils.trim(dp.Value);
-                        dp.Value = StringUtils.strip(dp.Value);
+                        if (!dp.Name.equals(EXTENDED_DEVICE_STATE_PROPERTY_NAME)) {
+                            dp.Value = StringUtils.trim(dp.Value);
+                            dp.Value = StringUtils.strip(dp.Value);
+                        }
 
                         onAppliancePropertyChanged(applicationIdentifier, dp);
                     } catch (Exception p) {
@@ -211,6 +215,18 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                 metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
             }
 
+            if (dp.Name.equals(EXTENDED_DEVICE_STATE_PROPERTY_NAME)) {
+                if (!dp.Value.isEmpty()) {
+                    byte[] extendedStateBytes = ExtendedDeviceStateUtil.stringToBytes(dp.Value);
+                    logger.trace("Extended device state for {}: {}", getThing().getUID(),
+                            ExtendedDeviceStateUtil.bytesToHex(extendedStateBytes));
+                    if (this instanceof ExtendedDeviceStateListener) {
+                        ((ExtendedDeviceStateListener) this).onApplianceExtendedStateChanged(extendedStateBytes);
+                    }
+                }
+                return;
+            }
+
             ApplianceChannelSelector selector = null;
             try {
                 selector = getValueSelectorFromMieleID(dp.Name);
@@ -244,6 +260,12 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
         }
     }
 
+    protected void updateExtendedState(String channelId, State state) {
+        ChannelUID channelUid = new ChannelUID(getThing().getUID(), channelId);
+        logger.trace("Update state of {} with extended state '{}'", channelUid, state);
+        updateState(channelUid, state);
+    }
+
     @Override
     public void onApplianceRemoved(HomeDevice appliance) {
         if (applianceId == null) {
index 255275e2dee277cb4d4c2c333dd11deddce3293f..5749b303bc4c5414eb0dd5bcaa25299f7533de05 100644 (file)
@@ -185,6 +185,11 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index c8a4f18f0b2542593378aad23bb44b9759ed3a66..e6b2b5e2c085611ab98345bffd0bedaeb3ceb59d 100644 (file)
@@ -167,6 +167,11 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return false;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index 7f1e43fee6478561180d638a94ac772d84947883..d54c19236b91e7198e189ce9e9fcaa0e87b153d5 100644 (file)
  */
 package org.openhab.binding.miele.internal.handler;
 
+import static org.openhab.binding.miele.internal.MieleBindingConstants.EXTENDED_DEVICE_STATE_PROPERTY_NAME;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.POWER_CONSUMPTION_CHANNEL_ID;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CONSUMPTION_CHANNEL_ID;
+
 import java.lang.reflect.Method;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -24,6 +28,7 @@ import org.openhab.core.library.types.DateTimeType;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.Type;
@@ -38,18 +43,19 @@ import com.google.gson.JsonElement;
  *
  * @author Karel Goderis - Initial contribution
  * @author Kai Kreuzer - Changed START_TIME to DateTimeType
+ * @author Jacob Laursen - Added power/water consumption channels
  */
 public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
 
-    PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
-    DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
-    BRAND_ID("brandId", "brandId", StringType.class, true),
-    COMPANY_ID("companyId", "companyId", StringType.class, true),
-    STATE("state", "state", StringType.class, false),
-    PROGRAMID("programId", "program", StringType.class, false),
-    PROGRAMTYPE("programType", "type", StringType.class, false),
-    PROGRAMPHASE("phase", "phase", StringType.class, false),
-    START_TIME("startTime", "start", DateTimeType.class, false) {
+    PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
+    DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
+    BRAND_ID("brandId", "brandId", StringType.class, true, false),
+    COMPANY_ID("companyId", "companyId", StringType.class, true, false),
+    STATE("state", "state", StringType.class, false, false),
+    PROGRAMID("programId", "program", StringType.class, false, false),
+    PROGRAMTYPE("programType", "type", StringType.class, false, false),
+    PROGRAMPHASE("phase", "phase", StringType.class, false, false),
+    START_TIME("startTime", "start", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -63,7 +69,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    DURATION("duration", "duration", DateTimeType.class, false) {
+    DURATION("duration", "duration", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -77,7 +83,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
+    ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -91,7 +97,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
+    FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             Date date = new Date();
@@ -105,13 +111,13 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return getState(dateFormatter.format(date));
         }
     },
-    TARGET_TEMP("targetTemperature", "target", DecimalType.class, false) {
+    TARGET_TEMP("targetTemperature", "target", DecimalType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             return getState(s);
         }
     },
-    SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false) {
+    SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false, false) {
         @Override
         public State getState(String s, DeviceMetaData dmd) {
             if ("0".equals(s)) {
@@ -123,7 +129,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return getState(Integer.toString((Integer.valueOf(s) * 10)));
         }
     },
-    DOOR("signalDoor", "door", OpenClosedType.class, false) {
+    DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
         @Override
 
         public State getState(String s, DeviceMetaData dmd) {
@@ -138,7 +144,11 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             return UnDefType.UNDEF;
         }
     },
-    SWITCH(null, "switch", OnOffType.class, false);
+    SWITCH(null, "switch", OnOffType.class, false, false),
+    POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
+            true),
+    WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
+            true);
 
     private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
 
@@ -146,13 +156,15 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     private final String channelID;
     private final Class<? extends Type> typeClass;
     private final boolean isProperty;
+    private final boolean isExtendedState;
 
     WashingMachineChannelSelector(String propertyID, String channelID, Class<? extends Type> typeClass,
-            boolean isProperty) {
+            boolean isProperty, boolean isExtendedState) {
         this.mieleID = propertyID;
         this.channelID = channelID;
         this.typeClass = typeClass;
         this.isProperty = isProperty;
+        this.isExtendedState = isExtendedState;
     }
 
     @Override
@@ -180,6 +192,11 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
         return isProperty;
     }
 
+    @Override
+    public boolean isExtendedState() {
+        return isExtendedState;
+    }
+
     @Override
     public State getState(String s, DeviceMetaData dmd) {
         if (dmd != null) {
index 4abea606bb7faf576d0293b6f19e2803aac5a498..b7145a3196eeaea43a261163737957f88c0efbda 100644 (file)
 package org.openhab.binding.miele.internal.handler;
 
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.POWER_CONSUMPTION_CHANNEL_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.PROTOCOL_PROPERTY_NAME;
+import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CONSUMPTION_CHANNEL_ID;
+
+import java.math.BigDecimal;
 
 import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
 import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.types.Command;
@@ -33,9 +39,14 @@ import com.google.gson.JsonElement;
  * @author Karel Goderis - Initial contribution
  * @author Kai Kreuzer - fixed handling of REFRESH commands
  * @author Martin Lepsy - fixed handling of empty JSON results
- * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
+ * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
  **/
-public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector> {
+public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector>
+        implements ExtendedDeviceStateListener {
+
+    private static final int POWER_CONSUMPTION_BYTE_POSITION = 51;
+    private static final int WATER_CONSUMPTION_BYTE_POSITION = 53;
+    private static final int EXTENDED_STATE_SIZE_BYTES = 59;
 
     private final Logger logger = LoggerFactory.getLogger(WashingMachineHandler.class);
 
@@ -85,4 +96,20 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
                     channelID, command.toString());
         }
     }
+
+    public void onApplianceExtendedStateChanged(byte[] extendedDeviceState) {
+        if (extendedDeviceState.length != EXTENDED_STATE_SIZE_BYTES) {
+            logger.error("Unexpected size of extended state: {}", extendedDeviceState);
+            return;
+        }
+
+        BigDecimal kiloWattHoursTenths = BigDecimal
+                .valueOf(extendedDeviceState[POWER_CONSUMPTION_BYTE_POSITION] & 0xff);
+        var kiloWattHours = new QuantityType<>(kiloWattHoursTenths.divide(BigDecimal.valueOf(10)), Units.KILOWATT_HOUR);
+        updateExtendedState(POWER_CONSUMPTION_CHANNEL_ID, kiloWattHours);
+
+        var litres = new QuantityType<>(BigDecimal.valueOf(extendedDeviceState[WATER_CONSUMPTION_BYTE_POSITION] & 0xff),
+                Units.LITRE);
+        updateExtendedState(WATER_CONSUMPTION_CHANNEL_ID, litres);
+    }
 }
index f507cfe41c64bc4464b05e59e25a6d8a3714798b..38b136e7dbe6aa1b48a710e191575965fbb27dab 100644 (file)
                <state readOnly="true"></state>
        </channel-type>
 
+       <channel-type id="powerConsumption" advanced="false">
+               <item-type>Number:Power</item-type>
+               <label>Power Consumption</label>
+               <description>Power consumption by the currently running program on the appliance</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
+       <channel-type id="waterConsumption" advanced="false">
+               <item-type>Number:Volume</item-type>
+               <label>Water Consumption</label>
+               <description>Water consumption by the currently running program on the appliance</description>
+               <state readOnly="true" pattern="%.1f %unit%"/>
+       </channel-type>
+
 </thing:thing-descriptions>
index e87446bef61e466cc62b66853620a0822835afe3..f081d6146fbf286318de74e0676c078a95e9bcf4 100644 (file)
@@ -23,6 +23,8 @@
                        <channel id="finish" typeId="finish"/>
                        <channel id="door" typeId="door"/>
                        <channel id="switch" typeId="switch"/>
+                       <channel id="powerConsumption" typeId="powerConsumption"/>
+                       <channel id="waterConsumption" typeId="waterConsumption"/>
                </channels>
 
                <representation-property>uid</representation-property>
index deafab98af24d5829f31b710961a1fe776f5c1ae..cb138659676aea0acb418fe300fd4ebe1322b4c9 100644 (file)
@@ -26,6 +26,8 @@
                        <channel id="switch" typeId="switch"/>
                        <channel id="target" typeId="target"/>
                        <channel id="spinningspeed" typeId="spinningspeed"/>
+                       <channel id="powerConsumption" typeId="powerConsumption"/>
+                       <channel id="waterConsumption" typeId="waterConsumption"/>
                </channels>
 
                <representation-property>uid</representation-property>
diff --git a/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtilTest.java b/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/ExtendedDeviceStateUtilTest.java
new file mode 100644 (file)
index 0000000..5bb52b7
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.miele.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.openhab.core.test.java.JavaTest;
+
+/**
+ * This class provides test cases for {@link
+ * org.openhab.binding.miele.internal.ExtendedDeviceStateUtil}
+ *
+ * @author Jacob Laursen - Added power/water consumption channels
+ */
+
+public class ExtendedDeviceStateUtilTest extends JavaTest {
+
+    @Test
+    public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
+        String actual = ExtendedDeviceStateUtil
+                .bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
+        assertEquals("DEADBEEF", actual);
+    }
+
+    /**
+     * This test guards that the UTF-16 returned by the RPC-JSON API will be
+     * considered as a sequence of 8-bit characters and converted into bytes
+     * accordingly. Default behaviour of String.getBytes() assumes UTF-8
+     * and adds a 0xc2 byte before any character out of ASCII range.
+     */
+    @Test
+    public void stringToBytesWhenTopBitIsUsedReturnsSingleByte() {
+        byte[] expected = new byte[] { (byte) 0x00, (byte) 0x80, (byte) 0x00 };
+        byte[] actual = ExtendedDeviceStateUtil.stringToBytes("\u0000\u0080\u0000");
+        assertArrayEquals(expected, actual);
+    }
+}