Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[miele] Add new power and water consumption channels for dishwashers and washing machines #11298

Merged
merged 1 commit into from
Sep 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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