Skip to content

Commit

Permalink
Add new channels for water and power consumption for washing machines…
Browse files Browse the repository at this point in the history
… and dishwashers. (openhab#11298)

Fixes openhab#11297

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
  • Loading branch information
jlaur authored and thinkingstone committed Nov 7, 2021
1 parent c0e18a0 commit 706db1e
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* returned by the appliance to a compatible State
*
* @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added power/water consumption channels
*/
public interface ApplianceChannelSelector {

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return false;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
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;
Expand All @@ -33,9 +39,14 @@
* @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);

Expand Down Expand Up @@ -84,4 +95,20 @@ public void handleCommand(ChannelUID channelUID, Command command) {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
*/
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;
Expand All @@ -22,6 +26,7 @@
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;
Expand All @@ -36,17 +41,18 @@
*
* @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();
Expand All @@ -60,7 +66,7 @@ public State getState(String s, DeviceMetaData dmd) {
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();
Expand All @@ -74,7 +80,7 @@ public State getState(String s, DeviceMetaData dmd) {
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();
Expand All @@ -88,7 +94,7 @@ public State getState(String s, DeviceMetaData dmd) {
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();
Expand All @@ -102,7 +108,7 @@ public State getState(String s, DeviceMetaData dmd) {
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)) {
Expand All @@ -116,21 +122,27 @@ 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(DishwasherChannelSelector.class);

private final String mieleID;
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
Expand Down Expand Up @@ -158,6 +170,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return isExtendedState;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return false;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return false;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return false;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public boolean isProperty() {
return isProperty;
}

@Override
public boolean isExtendedState() {
return false;
}

@Override
public State getState(String s, DeviceMetaData dmd) {
if (dmd != null) {
Expand Down
Loading

0 comments on commit 706db1e

Please sign in to comment.