--- /dev/null
+/**
+ * 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);
+ }
+}
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");
* returned by the appliance to a compatible State
*
* @author Karel Goderis - Initial contribution
+ * @author Jacob Laursen - Added power/water consumption channels
*/
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
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
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;
* @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);
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);
+ }
}
*/
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;
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;
*
* @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();
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();
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();
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();
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)) {
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);
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
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return isExtendedState;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
--- /dev/null
+/**
+ * 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);
+}
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
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;
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;
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) {
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);
}
}
+ 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) {
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return false;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
*/
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;
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;
*
* @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();
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();
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();
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();
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)) {
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) {
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);
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
return isProperty;
}
+ @Override
+ public boolean isExtendedState() {
+ return isExtendedState;
+ }
+
@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
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;
* @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);
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);
+ }
}
<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>
<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>
<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>
--- /dev/null
+/**
+ * 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);
+ }
+}