Skip to content

Commit

Permalink
[miele] Clean up properties and improve reliability and performance (o…
Browse files Browse the repository at this point in the history
…penhab#11423)

* Use appliance cache for getting full UID with protocol prefix instead of relying on property.
* Set bare protocol name as property.
* Fix potential null pointer access warnings.
* Remove unused import.
* Renamed property protocol to protocolAdapter for correctness.
* Add connectionType property.
* Add appliance model property.
* Remove useless properties brandId and companyId always having value MI.
* Rename property dc to deviceClass and set it consistently (not only from auto-discovered things).
* Added constants for remaining handlers with hardcoded device classes.
* Fix SCA: AuthorContributionDescriptionCheck
* Fix SCA: ModifierOrderCheck
* Rename ExtendedDeviceStateUtil to be a bit more generic.
* Extract device class string parsing to utility method.
* Fix SCA: ForbiddenPackageUsageCheck
* Fix redundant null check.
* Fix potential null pointer access warnings.
* Fix unsafe null type conversion (type annotations)
* Share same configuration (UID) for all appliance types.
* Refer to gateway instead of ZigBee network in configuration.
* Remove dependency to seriaNumber property for multicast channel updates.
* Simplified filtering of irrelevant device class.
* Remove devices from remoteUid cache also when disappearing from gateway, although this is a quite rare scenario.
* Add default i18n properties file.
* Add partial Danish translation.

Fixes openhab#11422

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
  • Loading branch information
jlaur authored and jpg0 committed Nov 10, 2021
1 parent b938711 commit f956221
Show file tree
Hide file tree
Showing 41 changed files with 513 additions and 373 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
import org.openhab.core.types.UnDefType;

/**
* The {@link ExtendedDeviceStateUtil} class contains utility methods for parsing
* ExtendedDeviceState information
* The {@link DeviceUtil} class contains utility methods for extracting
* and parsing device information, for example from ExtendedDeviceState.
*
* @author Jacob Laursen - Added power/water consumption channels
* @author Jacob Laursen - Initial contribution
*/
public class ExtendedDeviceStateUtil {
public class DeviceUtil {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private static final String TEMPERATURE_UNDEFINED = "32768";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* The {@link FullyQualifiedApplianceIdentifier} class represents a fully qualified appliance identifier.
* Example: "hdm:ZigBee:0123456789abcdef#210"
*
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
* @author Jacob Laursen - Initial contribution
*/
public class FullyQualifiedApplianceIdentifier {
private String uid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ public class MieleBindingConstants {

public static final String BINDING_ID = "miele";
public static final String APPLIANCE_ID = "uid";
public static final String DEVICE_CLASS = "dc";
public static final String PROTOCOL_PROPERTY_NAME = "protocol";
public static final String DEVICE_CLASS = "deviceClass";
public static final String MODEL_PROPERTY_NAME = "model";
public static final String PROTOCOL_ADAPTER_PROPERTY_NAME = "protocolAdapter";
public static final String CONNECTION_TYPE_PROPERTY_NAME = "connectionType";

// JSON-RPC property names
public static final String SERIAL_NUMBER_PROPERTY_NAME = "serialNumber";
Expand Down Expand Up @@ -65,8 +67,14 @@ public class MieleBindingConstants {

// Miele devices classes
public static final String MIELE_DEVICE_CLASS_COFFEE_SYSTEM = "CoffeeSystem";
public static final String MIELE_DEVICE_CLASS_DISHWASHER = "Dishwasher";
public static final String MIELE_DEVICE_CLASS_FRIDGE = "Fridge";
public static final String MIELE_DEVICE_CLASS_FRIDGE_FREEZER = "FridgeFreezer";
public static final String MIELE_DEVICE_CLASS_HOB = "Hob";
public static final String MIELE_DEVICE_CLASS_HOOD = "Hood";
public static final String MIELE_DEVICE_CLASS_OVEN = "Oven";
public static final String MIELE_DEVICE_CLASS_TUMBLE_DRYER = "TumbleDryer";
public static final String MIELE_DEVICE_CLASS_WASHING_MACHINE = "WashingMachine";

// Miele appliance states
public static final int STATE_UNKNOWN = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonElement;

/**
* The {@link MieleApplianceDiscoveryService} tracks appliances that are
* associated with the Miele@Home gateway
Expand All @@ -47,9 +45,6 @@
*/
public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener {

private static final String MIELE_APPLIANCE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance";
private static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele";

private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class);

private static final int SEARCH_TIME = 60;
Expand Down Expand Up @@ -104,16 +99,17 @@ private void onApplianceAddedInternal(HomeDevice appliance) {
Map<String, Object> properties = new HashMap<>(2);

FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
properties.put(PROTOCOL_PROPERTY_NAME, applianceIdentifier.getProtocol());
properties.put(MODEL_PROPERTY_NAME, appliance.getApplianceModel());
String deviceClass = appliance.getDeviceClass();
if (deviceClass != null) {
properties.put(DEVICE_CLASS, deviceClass);
}
properties.put(PROTOCOL_ADAPTER_PROPERTY_NAME, appliance.ProtocolAdapterName);
properties.put(APPLIANCE_ID, applianceIdentifier.getApplianceId());
properties.put(SERIAL_NUMBER_PROPERTY_NAME, appliance.getSerialNumber());

for (JsonElement dc : appliance.DeviceClasses) {
String dcStr = dc.getAsString();
if (dcStr.contains(MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
properties.put(DEVICE_CLASS, dcStr.substring(MIELE_CLASS.length()));
break;
}
String connectionType = appliance.getConnectionType();
if (connectionType != null) {
properties.put(CONNECTION_TYPE_PROPERTY_NAME, connectionType);
}

DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
Expand Down Expand Up @@ -146,22 +142,9 @@ public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applian
// nothing to do
}

@Override
public void onAppliancePropertyChanged(String serialNumber, DeviceProperty dp) {
// nothing to do
}

private ThingUID getThingUID(HomeDevice appliance) {
ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID();
String modelId = null;

for (JsonElement dc : appliance.DeviceClasses) {
String dcStr = dc.getAsString();
if (dcStr.contains(MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
modelId = dcStr.substring(MIELE_CLASS.length());
break;
}
}
String modelId = appliance.getDeviceClass();

if (modelId != null) {
ThingTypeUID thingTypeUID = getThingTypeUidFromModelId(modelId);
Expand All @@ -183,7 +166,7 @@ private ThingTypeUID getThingTypeUidFromModelId(String modelId) {
* coffeemachine. At least until it is known if any models are actually reported
* as CoffeeMachine, we need this special mapping.
*/
if (modelId.equals(MIELE_DEVICE_CLASS_COFFEE_SYSTEM)) {
if (MIELE_DEVICE_CLASS_COFFEE_SYSTEM.equals(modelId)) {
return THING_TYPE_COFFEEMACHINE;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ public interface ApplianceStatusListener {
*/
void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applianceIdentifier, DeviceProperty dp);

/**
* This method is called whenever a "property" of the given appliance has changed.
*
* @param serialNumber The serial number of the appliance that has changed
* @param dco the POJO containing the new state of the property
*/
void onAppliancePropertyChanged(String serialNumber, DeviceProperty dp);

/**
* This method is called whenever an appliance is removed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ public enum CoffeeMachineChannelSelector 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_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false),
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) {
Expand Down Expand Up @@ -88,9 +86,9 @@ public State getState(String s, DeviceMetaData dmd) {

private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class);

private final static Map<String, String> programs = Collections.<String, String> emptyMap();
private static final Map<String, String> programs = Collections.<String, String> emptyMap();

private final static Map<String, String> phases = Collections.<String, String> emptyMap();
private static final Map<String, String> phases = Collections.<String, String> emptyMap();

private final String mieleID;
private final String channelID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@

import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_COFFEE_SYSTEM;
import static org.openhab.binding.miele.internal.MieleBindingConstants.PROTOCOL_PROPERTY_NAME;

import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
Expand Down Expand Up @@ -49,8 +47,6 @@ public void handleCommand(ChannelUID channelUID, Command command) {

String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String protocol = getThing().getProperties().get(PROTOCOL_PROPERTY_NAME);
var applianceIdentifier = new FullyQualifiedApplianceIdentifier(applianceId, protocol);

CoffeeMachineChannelSelector selector = (CoffeeMachineChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
Expand All @@ -60,9 +56,9 @@ public void handleCommand(ChannelUID channelUID, Command command) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "switchOn");
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "switchOff");
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
break;
}
Expand All @@ -75,7 +71,7 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
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.MIELE_DEVICE_CLASS_DISHWASHER;
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;
Expand Down Expand Up @@ -51,7 +50,7 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
private final Logger logger = LoggerFactory.getLogger(DishWasherHandler.class);

public DishWasherHandler(Thing thing) {
super(thing, DishwasherChannelSelector.class, "Dishwasher");
super(thing, DishwasherChannelSelector.class, MIELE_DEVICE_CLASS_DISHWASHER);
}

@Override
Expand All @@ -60,8 +59,6 @@ public void handleCommand(ChannelUID channelUID, Command command) {

String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String protocol = getThing().getProperties().get(PROTOCOL_PROPERTY_NAME);
var applianceIdentifier = new FullyQualifiedApplianceIdentifier(applianceId, protocol);

DishwasherChannelSelector selector = (DishwasherChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
Expand All @@ -71,9 +68,9 @@ public void handleCommand(ChannelUID channelUID, Command command) {
switch (selector) {
case SWITCH: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "start");
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stop");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
Expand All @@ -86,7 +83,7 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {

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_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false),
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) {
Expand Down Expand Up @@ -152,12 +150,12 @@ public State getState(String s, DeviceMetaData dmd) {

private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);

private final static Map<String, String> programs = Map.ofEntries(entry("26", "Pots & Pans"),
private static final Map<String, String> programs = Map.ofEntries(entry("26", "Pots & Pans"),
entry("27", "Clean Machine"), entry("28", "Economy"), entry("30", "Normal"), entry("32", "Sensor Wash"),
entry("34", "Energy Saver"), entry("35", "China & Crystal"), entry("36", "Extra Quiet"),
entry("37", "SaniWash"), entry("38", "QuickPowerWash"), entry("42", "Tall items"));

private final static Map<String, String> phases = Map.ofEntries(entry("2", "Pre-Wash"), entry("3", "Main Wash"),
private static final Map<String, String> phases = Map.ofEntries(entry("2", "Pre-Wash"), entry("3", "Main Wash"),
entry("4", "Rinses"), entry("6", "Final rinse"), entry("7", "Drying"), entry("8", "Finished"));

private final String mieleID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.lang.reflect.Method;
import java.util.Map.Entry;

import org.openhab.binding.miele.internal.ExtendedDeviceStateUtil;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
Expand All @@ -42,8 +42,6 @@ public enum FridgeChannelSelector 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_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false),
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
Expand Down Expand Up @@ -148,7 +146,7 @@ public State getState(String s) {

public State getTemperatureState(String s) {
try {
return ExtendedDeviceStateUtil.getTemperatureState(s);
return DeviceUtil.getTemperatureState(s);
} catch (NumberFormatException e) {
logger.warn("An exception occurred while converting '{}' into a State", s);
return UnDefType.UNDEF;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.lang.reflect.Method;
import java.util.Map.Entry;

import org.openhab.binding.miele.internal.ExtendedDeviceStateUtil;
import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceMetaData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
Expand All @@ -43,8 +43,6 @@ public enum FridgeFreezerChannelSelector 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_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false),
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false),
FREEZERSTATE("freezerState", "freezerstate", StringType.class, false),
Expand Down Expand Up @@ -165,7 +163,7 @@ public State getState(String s) {

public State getTemperatureState(String s) {
try {
return ExtendedDeviceStateUtil.getTemperatureState(s);
return DeviceUtil.getTemperatureState(s);
} catch (NumberFormatException e) {
logger.warn("An exception occurred while converting '{}' into a State", s);
return UnDefType.UNDEF;
Expand Down
Loading

0 comments on commit f956221

Please sign in to comment.