[miele] Clean up properties and improve reliability and performance (#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 #11422

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
jlaur 2021-11-01 18:44:19 +01:00 committed by GitHub
parent dd4019c172
commit 745bf76569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 514 additions and 374 deletions

View File

@ -20,12 +20,12 @@ import org.openhab.core.types.State;
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";

View File

@ -16,7 +16,7 @@ package org.openhab.binding.miele.internal;
* 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;

View File

@ -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";
@ -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;

View File

@ -35,8 +35,6 @@ import org.openhab.core.thing.ThingUID;
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
@ -47,9 +45,6 @@ import com.google.gson.JsonElement;
*/
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;
@ -104,16 +99,17 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
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)
@ -146,22 +142,9 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
// 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);
@ -183,7 +166,7 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
* 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;
}

View File

@ -26,7 +26,6 @@ import org.openhab.binding.miele.internal.MieleBindingConstants;
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;

View File

@ -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.
*

View File

@ -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) {
@ -88,9 +86,9 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -14,9 +14,7 @@ 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_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;
@ -49,8 +47,6 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
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;
@ -60,9 +56,9 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
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;
}
@ -75,7 +71,7 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -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;
@ -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
@ -60,8 +59,6 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
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;
@ -71,9 +68,9 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
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;
}
@ -86,7 +83,7 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -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) {
@ -152,12 +150,12 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -17,7 +17,7 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
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;
@ -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),
@ -148,7 +146,7 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -17,7 +17,7 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
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;
@ -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),
@ -165,7 +163,7 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -14,7 +14,6 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -48,8 +47,6 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
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);
FridgeFreezerChannelSelector selector = (FridgeFreezerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
@ -59,17 +56,17 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "startSuperCooling");
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stopSuperCooling");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
break;
}
case SUPERFREEZE: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "startSuperFreezing");
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stopSuperFreezing");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing");
}
break;
}
@ -80,7 +77,7 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
@ -94,7 +91,7 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
protected void onAppliancePropertyChanged(DeviceProperty dp) {
super.onAppliancePropertyChanged(dp);
if (!dp.Name.equals(STATE_PROPERTY_NAME)) {
if (!STATE_PROPERTY_NAME.equals(dp.Name)) {
return;
}

View File

@ -14,7 +14,6 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
@ -49,8 +48,6 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
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);
FridgeChannelSelector selector = (FridgeChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
@ -60,15 +57,15 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
switch (selector) {
case SUPERCOOL: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "startSuperCooling");
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stopSuperCooling");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
}
break;
}
case START: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "start");
result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
}
break;
}
@ -81,7 +78,7 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
@ -95,7 +92,7 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
protected void onAppliancePropertyChanged(DeviceProperty dp) {
super.onAppliancePropertyChanged(dp);
if (!dp.Name.equals(STATE_PROPERTY_NAME)) {
if (!STATE_PROPERTY_NAME.equals(dp.Name)) {
return;
}

View File

@ -37,8 +37,6 @@ public enum HobChannelSelector 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),
PLATES("plateNumbers", "plates", DecimalType.class, true),

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOB;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
@ -26,7 +28,7 @@ import org.openhab.core.types.Command;
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing) {
super(thing, HobChannelSelector.class, "Hob");
super(thing, HobChannelSelector.class, MIELE_DEVICE_CLASS_HOB);
}
@Override

View File

@ -39,8 +39,6 @@ public enum HoodChannelSelector 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),
VENTILATION("ventilationPower", "ventilation", DecimalType.class, false),

View File

@ -13,9 +13,8 @@
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.PROTOCOL_PROPERTY_NAME;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOOD;
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;
@ -39,7 +38,7 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
public HoodHandler(Thing thing) {
super(thing, HoodChannelSelector.class, "Hood");
super(thing, HoodChannelSelector.class, MIELE_DEVICE_CLASS_HOOD);
}
@Override
@ -48,8 +47,6 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
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);
HoodChannelSelector selector = (HoodChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
@ -59,15 +56,15 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
switch (selector) {
case LIGHT: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "startLighting");
result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stopLighting");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting");
}
break;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stop");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
@ -78,7 +75,7 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -20,8 +20,8 @@ import java.util.Set;
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.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.miele.internal.DeviceUtil;
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;
@ -84,8 +84,13 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
throws IllegalArgumentException {
for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
if (c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
E[] enumConstants = selectorType.getEnumConstants();
if (enumConstants == null) {
throw new IllegalArgumentException(
String.format("Could not get enum constants for value selector: %s", valueSelectorText));
}
for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) {
return c;
}
}
@ -95,8 +100,13 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelectorText)
throws IllegalArgumentException {
for (ApplianceChannelSelector c : selectorType.getEnumConstants()) {
if (c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
E[] enumConstants = selectorType.getEnumConstants();
if (enumConstants == null) {
throw new IllegalArgumentException(
String.format("Could not get enum constants for value selector: %s", valueSelectorText));
}
for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) {
return c;
}
}
@ -110,16 +120,14 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
final String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId != null) {
this.applianceId = applianceId;
if (getMieleBridgeHandler() != null) {
ThingStatusInfo statusInfo = getBridge().getStatusInfo();
updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
}
this.onBridgeConnectionResumed();
}
}
public void onBridgeConnectionResumed() {
if (getMieleBridgeHandler() != null) {
ThingStatusInfo statusInfo = getBridge().getStatusInfo();
Bridge bridge = getBridge();
if (bridge != null && getMieleBridgeHandler() != null) {
ThingStatusInfo statusInfo = bridge.getStatusInfo();
updateStatus(statusInfo.getStatus(), statusInfo.getStatusDetail(), statusInfo.getDescription());
}
}
@ -149,43 +157,32 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicationIdentifier,
DeviceClassObject dco) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
String modelID = StringUtils.right(dco.DeviceClass,
dco.DeviceClass.length() - new String("com.miele.xgw3000.gateway.hdm.deviceclasses.Miele").length());
if (myApplianceId.equals(applicationIdentifier.getApplianceId())) {
if (modelID.equals(this.modelID)) {
for (JsonElement prop : dco.Properties.getAsJsonArray()) {
try {
DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
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) {
// Ignore - this is due to an unrecognized and not yet reverse-engineered array property
}
}
}
}
}
@Override
public void onAppliancePropertyChanged(String serialNumber, DeviceProperty dp) {
String mySerialNumber = getThing().getProperties().get(SERIAL_NUMBER_PROPERTY_NAME);
if (!mySerialNumber.equals(serialNumber)) {
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) {
return;
}
this.onAppliancePropertyChanged(dp);
for (JsonElement prop : dco.Properties.getAsJsonArray()) {
try {
DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
if (dp == null) {
continue;
}
if (!EXTENDED_DEVICE_STATE_PROPERTY_NAME.equals(dp.Name)) {
dp.Value = dp.Value.trim();
dp.Value = dp.Value.strip();
}
onAppliancePropertyChanged(applicationIdentifier, dp);
} catch (Exception p) {
// Ignore - this is due to an unrecognized and not yet reverse-engineered array property
}
}
}
@Override
public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, DeviceProperty dp) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (!myApplianceId.equals(applicationIdentifier.getApplianceId())) {
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) {
return;
}
@ -202,14 +199,16 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
// only keep the enum, if any - that's all we care for events we receive via multicast
// all other fields are nulled
dmd.LocalizedID = null;
dmd.LocalizedValue = null;
dmd.Filter = null;
dmd.description = null;
if (dmd != null) {
dmd.LocalizedID = null;
dmd.LocalizedValue = null;
dmd.Filter = null;
dmd.description = null;
}
}
}
if (dp.Metadata != null) {
String metadata = StringUtils.replace(dp.Metadata.toString(), "enum", "MieleEnum");
String metadata = dp.Metadata.toString().replace("enum", "MieleEnum");
JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
@ -217,9 +216,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (dp.Name.equals(EXTENDED_DEVICE_STATE_PROPERTY_NAME)) {
if (!dp.Value.isEmpty()) {
byte[] extendedStateBytes = ExtendedDeviceStateUtil.stringToBytes(dp.Value);
byte[] extendedStateBytes = DeviceUtil.stringToBytes(dp.Value);
logger.trace("Extended device state for {}: {}", getThing().getUID(),
ExtendedDeviceStateUtil.bytesToHex(extendedStateBytes));
DeviceUtil.bytesToHex(extendedStateBytes));
if (this instanceof ExtendedDeviceStateListener) {
((ExtendedDeviceStateListener) this).onApplianceExtendedStateChanged(extendedStateBytes);
}
@ -234,7 +233,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
logger.trace("{} is not a valid channel for a {}", dp.Name, modelID);
}
String dpValue = StringUtils.trim(StringUtils.strip(dp.Value));
String dpValue = dp.Value.strip().trim();
if (selector != null) {
if (!selector.isProperty()) {
@ -248,10 +247,11 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
} else {
updateState(theChannelUID, UnDefType.UNDEF);
}
} else if (dpValue != null) {
} else {
logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
getThing().getUID(), selector.getState(dpValue, dmd).toString());
Map<String, String> properties = editProperties();
@NonNull
Map<@NonNull String, @NonNull String> properties = editProperties();
properties.put(selector.getChannelID(), selector.getState(dpValue, dmd).toString());
updateProperties(properties);
}
@ -317,9 +317,19 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
if (applianceId.equals(applianceIdentifier.getApplianceId())) {
Map<String, String> properties = editProperties();
properties.put(PROTOCOL_PROPERTY_NAME, applianceIdentifier.getProtocol());
@NonNull
Map<@NonNull String, @NonNull String> properties = editProperties();
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(SERIAL_NUMBER_PROPERTY_NAME, appliance.getSerialNumber());
String connectionType = appliance.getConnectionType();
if (connectionType != null) {
properties.put(CONNECTION_TYPE_PROPERTY_NAME, connectionType);
}
updateProperties(properties);
updateStatus(ThingStatus.ONLINE);
}
@ -343,6 +353,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
}
protected boolean isResultProcessable(JsonElement result) {
return result != null && !result.isJsonNull();
if (result == null) {
throw new IllegalArgumentException("Provided result is null");
}
return !result.isJsonNull();
}
}

View File

@ -31,10 +31,13 @@ import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -44,7 +47,7 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.thing.Bridge;
@ -76,7 +79,10 @@ import com.google.gson.JsonParser;
**/
public class MieleBridgeHandler extends BaseBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_XGW3000);
@NonNull
public static final Set<@NonNull ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_XGW3000);
private static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele";
private static final Pattern IP_PATTERN = Pattern
.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
@ -97,7 +103,9 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
protected ExecutorService executor;
protected Future<?> eventListenerJob;
protected List<HomeDevice> previousHomeDevices = new CopyOnWriteArrayList<>();
@NonNull
protected Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<String, HomeDevice>();
protected Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<String, HomeDevice>();
protected URL url;
protected Map<String, String> headers;
@ -105,6 +113,8 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
// Data structures to de-JSONify whatever Miele appliances are sending us
public class HomeDevice {
private static final String MIELE_APPLIANCE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance";
public String Name;
public String Status;
public String ParentUID;
@ -125,9 +135,49 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return new FullyQualifiedApplianceIdentifier(this.UID);
}
@NonNull
public String getSerialNumber() {
return Properties.get("serial.number").getAsString();
}
@NonNull
public String getRemoteUid() {
JsonElement remoteUid = Properties.get("remote.uid");
if (remoteUid == null) {
// remote.uid and serial.number seems to be the same. If remote.uid
// is missing for some reason, it makes sense to provide fallback
// to serial number.
return getSerialNumber();
}
return remoteUid.getAsString();
}
public String getConnectionType() {
JsonElement connectionType = Properties.get("connection.type");
if (connectionType == null) {
return null;
}
return connectionType.getAsString();
}
@NonNull
public String getApplianceModel() {
JsonElement model = Properties.get("miele.model");
if (model == null) {
return "";
}
return model.getAsString();
}
public String getDeviceClass() {
for (JsonElement dc : DeviceClasses) {
String dcStr = dc.getAsString();
if (dcStr.contains(MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
return dcStr.substring(MIELE_CLASS.length());
}
}
return null;
}
}
public class DeviceClassObject {
@ -206,95 +256,101 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private Runnable pollingRunnable = new Runnable() {
@Override
public void run() {
if (IP_PATTERN.matcher((String) getConfig().get(HOST)).matches()) {
try {
if (isReachable((String) getConfig().get(HOST))) {
currentBridgeConnectionState = true;
} else {
currentBridgeConnectionState = false;
lastBridgeConnectionState = false;
onConnectionLost();
if (!IP_PATTERN.matcher((String) getConfig().get(HOST)).matches()) {
logger.debug("Invalid IP address for the Miele@Home gateway : '{}'", getConfig().get(HOST));
return;
}
try {
if (isReachable((String) getConfig().get(HOST))) {
currentBridgeConnectionState = true;
} else {
currentBridgeConnectionState = false;
lastBridgeConnectionState = false;
onConnectionLost();
}
if (!lastBridgeConnectionState && currentBridgeConnectionState) {
logger.debug("Connection to Miele Gateway {} established.", getConfig().get(HOST));
lastBridgeConnectionState = true;
onConnectionResumed();
}
if (!currentBridgeConnectionState || getThing().getStatus() != ThingStatus.ONLINE) {
return;
}
List<HomeDevice> homeDevices = getHomeDevices();
for (HomeDevice hd : homeDevices) {
String key = hd.getApplianceIdentifier().getApplianceId();
if (!cachedHomeDevicesByApplianceId.containsKey(key)) {
logger.debug("A new appliance with ID '{}' has been added", hd.UID);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceAdded(hd);
}
}
cachedHomeDevicesByApplianceId.put(key, hd);
cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd);
}
if (!lastBridgeConnectionState && currentBridgeConnectionState) {
logger.debug("Connection to Miele Gateway {} established.", getConfig().get(HOST));
lastBridgeConnectionState = true;
onConnectionResumed();
@NonNull
Set<@NonNull Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
@NonNull
Iterator<@NonNull Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
while (iterator.hasNext()) {
Entry<String, HomeDevice> cachedEntry = iterator.next();
HomeDevice cachedHomeDevice = cachedEntry.getValue();
if (!homeDevices.stream().anyMatch(d -> d.UID.equals(cachedHomeDevice.UID))) {
logger.debug("The appliance with ID '{}' has been removed", cachedHomeDevice.UID);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceRemoved(cachedHomeDevice);
}
cachedHomeDevicesByRemoteUid.remove(cachedHomeDevice.getRemoteUid());
iterator.remove();
}
}
if (currentBridgeConnectionState) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
List<HomeDevice> currentHomeDevices = getHomeDevices();
for (HomeDevice hd : currentHomeDevices) {
boolean isExisting = false;
for (HomeDevice phd : previousHomeDevices) {
if (phd.UID.equals(hd.UID)) {
isExisting = true;
break;
for (Thing appliance : getThing().getThings()) {
if (appliance.getStatus() == ThingStatus.ONLINE) {
String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID);
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(
applianceId);
if (applianceIdentifier == null) {
logger.error("The appliance with ID '{}' was not found in appliance list from bridge.",
applianceId);
continue;
}
Object[] args = new Object[2];
args[0] = applianceIdentifier.getUid();
args[1] = true;
JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args);
if (result != null) {
for (JsonElement obj : result.getAsJsonArray()) {
try {
DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
// Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl
if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) {
continue;
}
}
if (!isExisting) {
logger.debug("A new appliance with ID '{}' has been added", hd.UID);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceAdded(hd);
}
}
}
for (HomeDevice hd : previousHomeDevices) {
boolean isCurrent = false;
for (HomeDevice chd : currentHomeDevices) {
if (chd.UID.equals(hd.UID)) {
isCurrent = true;
break;
}
}
if (!isCurrent) {
logger.debug("The appliance with ID '{}' has been removed", hd);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceRemoved(hd);
}
}
}
previousHomeDevices = currentHomeDevices;
for (Thing appliance : getThing().getThings()) {
if (appliance.getStatus() == ThingStatus.ONLINE) {
String applianceId = (String) appliance.getConfiguration().getProperties()
.get(APPLIANCE_ID);
String protocol = appliance.getProperties().get(PROTOCOL_PROPERTY_NAME);
var applianceIdentifier = new FullyQualifiedApplianceIdentifier(applianceId,
protocol);
Object[] args = new Object[2];
args[0] = applianceIdentifier.getUid();
args[1] = true;
JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args);
if (result != null) {
for (JsonElement obj : result.getAsJsonArray()) {
try {
DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onApplianceStateChanged(applianceIdentifier, dco);
}
} catch (Exception e) {
logger.debug("An exception occurred while querying an appliance : '{}'",
e.getMessage());
}
}
listener.onApplianceStateChanged(applianceIdentifier, dco);
}
} catch (Exception e) {
logger.debug("An exception occurred while querying an appliance : '{}'",
e.getMessage());
}
}
}
}
} catch (Exception e) {
logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage());
}
} else {
logger.debug("Invalid IP address for the Miele@Home gateway : '{}'", getConfig().get(HOST));
} catch (Exception e) {
logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage());
}
}
@ -339,6 +395,15 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return devices;
}
private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) {
HomeDevice homeDevice = this.cachedHomeDevicesByApplianceId.get(applianceId);
if (homeDevice == null) {
return null;
}
return homeDevice.getApplianceIdentifier();
}
private Runnable eventListenerRunnable = () -> {
if (IP_PATTERN.matcher((String) getConfig().get(INTERFACE)).matches()) {
while (true) {
@ -378,16 +443,16 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
DeviceProperty dp = new DeviceProperty();
String id = null;
String[] parts = StringUtils.split(event, "&");
String[] parts = event.split("&");
for (String p : parts) {
String[] subparts = StringUtils.split(p, "=");
String[] subparts = p.split("=");
switch (subparts[0]) {
case "property": {
dp.Name = subparts[1];
break;
}
case "value": {
dp.Value = StringUtils.trim(StringUtils.strip(subparts[1]));
dp.Value = subparts[1].strip().trim();
break;
}
case "id": {
@ -403,15 +468,19 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
// In XGW 3000 firmware 2.03 this was changed from UID (hdm:ZigBee:0123456789abcdef#210)
// to serial number (001234567890)
FullyQualifiedApplianceIdentifier applianceIdentifier;
if (id.startsWith("hdm:")) {
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(new FullyQualifiedApplianceIdentifier(id),
dp);
}
applianceIdentifier = new FullyQualifiedApplianceIdentifier(id);
} else {
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(id, dp);
HomeDevice device = cachedHomeDevicesByRemoteUid.get(id);
if (device == null) {
logger.debug("Multicast event not handled as id {} is unknown.", id);
continue;
}
applianceIdentifier = device.getApplianceIdentifier();
}
for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(applianceIdentifier, dp);
}
} catch (SocketTimeoutException e) {
try {
@ -445,19 +514,27 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
}
};
public JsonElement invokeOperation(FullyQualifiedApplianceIdentifier applianceIdentifier, String modelID,
String methodName) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
Object[] args = new Object[4];
args[0] = applianceIdentifier.getUid();
args[1] = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele" + modelID;
args[2] = methodName;
args[3] = null;
return invokeRPC("HDAccess/invokeDCOOperation", args);
} else {
public JsonElement invokeOperation(String applianceId, String modelID, String methodName) {
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("The Bridge is offline - operations can not be invoked.");
return null;
}
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId);
if (applianceIdentifier == null) {
logger.error(
"The appliance with ID '{}' was not found in appliance list from bridge - operations can not be invoked.",
applianceId);
return null;
}
Object[] args = new Object[4];
args[0] = applianceIdentifier.getUid();
args[1] = MIELE_CLASS + modelID;
args[2] = methodName;
args[3] = null;
return invokeRPC("HDAccess/invokeDCOOperation", args);
}
protected JsonElement invokeRPC(String methodName, Object[] args) {

View File

@ -22,7 +22,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
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.DateTimeType;
import org.openhab.core.library.types.DecimalType;
@ -49,8 +49,6 @@ public enum OvenChannelSelector 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),
@ -167,7 +165,7 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class);
private final static Map<String, String> phases = Map.ofEntries(entry("1", "Heating"), entry("2", "Temp. hold"),
private static final Map<String, String> phases = Map.ofEntries(entry("1", "Heating"), entry("2", "Temp. hold"),
entry("3", "Door Open"), entry("4", "Pyrolysis"), entry("7", "Lighting"), entry("8", "Searing phase"),
entry("10", "Defrost"), entry("11", "Cooling down"), entry("12", "Energy save phase"));
@ -241,7 +239,7 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -13,9 +13,8 @@
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.PROTOCOL_PROPERTY_NAME;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_OVEN;
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;
@ -40,7 +39,7 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
public OvenHandler(Thing thing) {
super(thing, OvenChannelSelector.class, "Oven");
super(thing, OvenChannelSelector.class, MIELE_DEVICE_CLASS_OVEN);
}
@Override
@ -49,8 +48,6 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
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);
OvenChannelSelector selector = (OvenChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
@ -60,15 +57,15 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
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;
}
case STOP: {
if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceIdentifier, modelID, "stop");
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
}
@ -81,7 +78,7 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -47,8 +47,6 @@ public enum TumbleDryerChannelSelector 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) {
@ -155,7 +153,7 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class);
private final static Map<String, String> programs = Map.ofEntries(entry("10", "Automatic Plus"),
private static final Map<String, String> programs = Map.ofEntries(entry("10", "Automatic Plus"),
entry("23", "Cottons hygiene"), entry("30", "Minimum iron"), entry("31", "Gentle minimum iron"),
entry("40", "Woollens handcare"), entry("50", "Delicates"), entry("60", "Warm Air"),
entry("70", "Cool air"), entry("80", "Express"), entry("90", "Cottons"), entry("100", "Gentle smoothing"),
@ -165,7 +163,7 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
entry("240", "Smoothing"), entry("65000", "Cottons, auto load control"),
entry("65001", "Minimum iron, auto load control"));
private final static Map<String, String> phases = Map.ofEntries(entry("1", "Programme running"),
private static final Map<String, String> phases = Map.ofEntries(entry("1", "Programme running"),
entry("2", "Drying"), entry("3", "Drying Machine iron"), entry("4", "Drying Hand iron"),
entry("5", "Drying Normal"), entry("6", "Drying Normal+"), entry("7", "Cooling down"),
entry("8", "Drying Hand iron"), entry("10", "Finished"));

View File

@ -13,9 +13,8 @@
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.PROTOCOL_PROPERTY_NAME;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_TUMBLE_DRYER;
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;
@ -40,7 +39,7 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
public TumbleDryerHandler(Thing thing) {
super(thing, TumbleDryerChannelSelector.class, "TumbleDryer");
super(thing, TumbleDryerChannelSelector.class, MIELE_DEVICE_CLASS_TUMBLE_DRYER);
}
@Override
@ -49,8 +48,6 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
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);
TumbleDryerChannelSelector selector = (TumbleDryerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null;
@ -60,9 +57,9 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
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;
}
@ -75,7 +72,7 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -22,8 +22,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import org.apache.commons.lang3.StringUtils;
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.DateTimeType;
import org.openhab.core.library.types.DecimalType;
@ -50,8 +49,6 @@ public enum WashingMachineChannelSelector 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) {
@ -98,7 +95,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
try {
date.setTime(Long.valueOf(StringUtils.trim(s)) * 60000);
date.setTime(Long.valueOf(s.trim()) * 60000);
} catch (Exception e) {
date.setTime(0);
}
@ -174,7 +171,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
private final static Map<String, String> programs = Map.ofEntries(entry("1", "Cottons"), entry("3", "Minimum iron"),
private static final Map<String, String> programs = Map.ofEntries(entry("1", "Cottons"), entry("3", "Minimum iron"),
entry("4", "Delicates"), entry("8", "Woollens"), entry("9", "Silks"), entry("17", "Starch"),
entry("18", "Rinse"), entry("21", "Drain/Spin"), entry("22", "Curtains"), entry("23", "Shirts"),
entry("24", "Denim"), entry("27", "Proofing"), entry("29", "Sportswear"), entry("31", "Automatic Plus"),
@ -183,7 +180,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
entry("95", "Down duvets"), entry("122", "Express 20"), entry("129", "Down filled items"),
entry("133", "Cottons Eco"), entry("146", "QuickPowerWash"), entry("65532", "Mix"));
private final static Map<String, String> phases = Map.ofEntries(entry("1", "Pre-wash"), entry("4", "Washing"),
private static final Map<String, String> phases = Map.ofEntries(entry("1", "Pre-wash"), entry("4", "Washing"),
entry("5", "Rinses"), entry("7", "Clean"), entry("9", "Drain"), entry("10", "Spin"),
entry("11", "Anti-crease"), entry("12", "Finished"));
@ -260,7 +257,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
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;

View File

@ -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_WASHING_MACHINE;
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;
@ -51,7 +50,7 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
private final Logger logger = LoggerFactory.getLogger(WashingMachineHandler.class);
public WashingMachineHandler(Thing thing) {
super(thing, WashingMachineChannelSelector.class, "WashingMachine");
super(thing, WashingMachineChannelSelector.class, MIELE_DEVICE_CLASS_WASHING_MACHINE);
}
@Override
@ -60,8 +59,6 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
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);
WashingMachineChannelSelector selector = (WashingMachineChannelSelector) getValueSelectorFromChannelID(
channelID);
@ -72,9 +69,9 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
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;
}
@ -87,7 +84,7 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
}
}
// process result
if (isResultProcessable(result)) {
if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:miele:appliance">
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>Unique identifier for specific appliance on the gateway.</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,107 @@
# binding
binding.miele.name = Miele Binding
binding.miele.description = This is the binding for Miele@home appliances
# thing types
thing-type.miele.coffeemachine.label = Coffee Machine
thing-type.miele.coffeemachine.description = This is a Miele@home compatible coffee machine
thing-type.miele.dishwasher.label = Dishwasher
thing-type.miele.dishwasher.description = This is a Miele@home compatible dishwasher
thing-type.miele.fridge.label = Fridge
thing-type.miele.fridge.description = This is a Miele@home compatible fridge
thing-type.miele.fridgefreezer.label = Fridge Freezer
thing-type.miele.fridgefreezer.description = This is a Miele@home compatible fridgefreezer
thing-type.miele.hob.label = Hob
thing-type.miele.hob.description = This is a Miele@home compatible hob
thing-type.miele.hood.label = Hood
thing-type.miele.hood.description = This is a Miele@home compatible hood
thing-type.miele.oven.label = Oven
thing-type.miele.oven.description = This is a Miele@home compatible oven
thing-type.miele.tumbledryer.label = Tumbledryer
thing-type.miele.tumbledryer.description = This is a Miele@home compatible tumbledryer
thing-type.miele.washingmachine.label = Washing Machine
thing-type.miele.washingmachine.description = This is a Miele@home compatible washing machine
thing-type.miele.xgw3000.label = Miele XGW3000
thing-type.miele.xgw3000.description = The Miele bridge represents the Miele@home XGW3000 gateway.
# thing types config
thing-type.config.miele.appliance.uid.label = ID
thing-type.config.miele.appliance.uid.description = Unique identifier for specific appliance on the gateway.
thing-type.config.miele.xgw3000.interface.label = Network Address of the Multicast Interface
thing-type.config.miele.xgw3000.interface.description = Network address of openHAB host interface where the binding will listen for multicast events coming from the Miele@home gateway.
thing-type.config.miele.xgw3000.ipAddress.label = Network Address
thing-type.config.miele.xgw3000.ipAddress.description = Network address of the Miele@home gateway.
thing-type.config.miele.xgw3000.password.label = Password
thing-type.config.miele.xgw3000.password.description = Password for the registered Miele@home user.
thing-type.config.miele.xgw3000.userName.label = Username
thing-type.config.miele.xgw3000.userName.description = Name of a registered Miele@home user.
# channel types
channel-type.miele.currentTemperature.label = Current Temperature
channel-type.miele.currentTemperature.description = Current temperature of the appliance
channel-type.miele.door.label = Door
channel-type.miele.door.description = Current state of the door of the appliance
channel-type.miele.duration.label = Duration
channel-type.miele.duration.description = Duration of the program running on the appliance
channel-type.miele.duration.state.pattern = %1$tH:%1$tM
channel-type.miele.elapsed.label = Elapsed Time
channel-type.miele.elapsed.description = Time elapsed in the program running on the appliance
channel-type.miele.elapsed.state.pattern = %1$tH:%1$tM
channel-type.miele.finish.label = Finish Time
channel-type.miele.finish.description = Time to finish the program running on the appliance
channel-type.miele.finish.state.pattern = %1$tH:%1$tM
channel-type.miele.freezerstate.label = Status
channel-type.miele.freezerstate.description = Current status of the freezer compartment
channel-type.miele.fridgestate.label = Status
channel-type.miele.fridgestate.description = Current status of the fridge compartment
channel-type.miele.heat.label = Remaining Heat
channel-type.miele.heat.description = Remaining heat level of the heating zone/plate
channel-type.miele.phase.label = Phase
channel-type.miele.phase.description = Current phase of the program running on the appliance
channel-type.miele.plates.label = Plates
channel-type.miele.plates.description = Number of heating zones/plates on the hob
channel-type.miele.power.label = Power Step
channel-type.miele.power.description = Power level of the heating zone/plate
channel-type.miele.powerConsumption.label = Power Consumption
channel-type.miele.powerConsumption.description = Power consumption by the currently running program on the appliance
channel-type.miele.program.label = Program
channel-type.miele.program.description = Current program or function running on the appliance
channel-type.miele.rawPhase.label = Raw Phase
channel-type.miele.rawPhase.description = Current phase of the program running on the appliance as raw number
channel-type.miele.rawProgram.label = Raw Program
channel-type.miele.rawProgram.description = Current program or function running on the appliance as raw number
channel-type.miele.rawState.label = Raw State
channel-type.miele.rawState.description = Current status of the appliance as raw number
channel-type.miele.spinningspeed.label = Spinning Speed
channel-type.miele.spinningspeed.description = Spinning speed in the program running on the appliance
channel-type.miele.start.label = Start Time
channel-type.miele.start.description = Programmed start time of the program
channel-type.miele.start.state.pattern = %1$tH:%1$tM
channel-type.miele.state.label = State
channel-type.miele.state.description = Current status of the appliance
channel-type.miele.step.label = Step
channel-type.miele.step.description = Current step in the program running on the appliance
channel-type.miele.stop.label = Stop
channel-type.miele.stop.description = Stop the appliance
channel-type.miele.supercool.label = Super Cool
channel-type.miele.supercool.description = Start or stop Super Cooling
channel-type.miele.superfreeze.label = Super Freeze
channel-type.miele.superfreeze.description = Start or stop Super Freezing
channel-type.miele.switch.label = Switch
channel-type.miele.switch.description = Switch the appliance on or off
channel-type.miele.targetTemperature.label = Target Temperature
channel-type.miele.targetTemperature.description = Target temperature to be reached by the appliance
channel-type.miele.temperature.label = Temperature
channel-type.miele.temperature.description = Temperature reported by the appliance
channel-type.miele.time.label = Remaining Time
channel-type.miele.time.description = Remaining time of the heating zone/plate
channel-type.miele.type.label = Program Type
channel-type.miele.type.description = Type of the program running on the appliance
channel-type.miele.ventilation.label = Ventilation Power
channel-type.miele.ventilation.description = Current ventilation power
channel-type.miele.waterConsumption.label = Water Consumption
channel-type.miele.waterConsumption.description = Water consumption by the currently running program on the appliance

View File

@ -0,0 +1,40 @@
# binding
binding.miele.name = Miele Binding
binding.miele.description = Dette er bindingen til Miele@home-husholdningsapparater
# thing types
thing-type.miele.coffeemachine.label = Kaffemaskine
thing-type.miele.coffeemachine.description = Dette er en Miele@home-kompatibel kaffemaskine
thing-type.miele.dishwasher.label = Opvaskemaskine
thing-type.miele.dishwasher.description = Dette er en Miele@home-kompatibel opvaskemaskine
thing-type.miele.fridge.label = Køleskab
thing-type.miele.fridge.description = Dette er et Miele@home-kompatibelt køleskab
thing-type.miele.fridgefreezer.label = Kølefryseskab
thing-type.miele.fridgefreezer.description = Dette er et Miele@home-kompatibelt kølefryseskab
thing-type.miele.hob.label = Kogeplader
thing-type.miele.hob.description = Dette er Miele@home-kompatible kogeplader
thing-type.miele.hood.label = Emhætte
thing-type.miele.hood.description = Dette er en Miele@home-kompatibel emhætte
thing-type.miele.oven.label = Ovn
thing-type.miele.oven.description = Dette er en Miele@home-kompatibel ovn
thing-type.miele.tumbledryer.label = Tørretumbler
thing-type.miele.tumbledryer.description = Dette er en Miele@home-kompatibel tørretumbler
thing-type.miele.washingmachine.label = Vaskemaskine
thing-type.miele.washingmachine.description = Dette er en Miele@home-kompatibel vaskemaskine
thing-type.miele.xgw3000.label = Miele XGW3000
thing-type.miele.xgw3000.description = Miele-bridgen repræsenterer Miele@home XGW3000-gateway'en.
# thing types config
thing-type.config.miele.appliance.uid.label = ID
thing-type.config.miele.appliance.uid.description = Unik identifikator til specifikt husholdningsapparat på gateway'en.
thing-type.config.miele.xgw3000.interface.label = Netværksadresse til multicast-interfacet
thing-type.config.miele.xgw3000.interface.description = Netværksadresse til openHAB værts-interfacet hvor bindingen vil lytte på multicast-hændelser fra Miele@home-gateway'en.
thing-type.config.miele.xgw3000.ipAddress.label = Netværksadresse
thing-type.config.miele.xgw3000.ipAddress.description = Netværksadresse til Miele@home-gateway'en.
thing-type.config.miele.xgw3000.password.label = Adgangskode
thing-type.config.miele.xgw3000.password.description = Adgangskode til registreret Miele@home-bruger.
thing-type.config.miele.xgw3000.userName.label = Brugernavn
thing-type.config.miele.xgw3000.userName.description = Navn på en registeret Miele@home-bruger.

View File

@ -27,12 +27,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -32,13 +32,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -29,13 +29,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -38,13 +38,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -38,13 +38,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -23,12 +23,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies the appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -47,13 +47,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -32,13 +32,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>

View File

@ -38,12 +38,7 @@
<representation-property>uid</representation-property>
<config-description>
<parameter name="uid" type="text" required="true">
<label>ID</label>
<description>The identifier identifies one certain appliance on the ZigBee network.</description>
</parameter>
</config-description>
<config-description-ref uri="thing-type:miele:appliance"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -7,7 +7,7 @@
<!-- Miele Bridge -->
<bridge-type id="xgw3000">
<label>Miele XGW3000</label>
<description>The miele bridge represents the Miele@home XGW3000 gateway.</description>
<description>The Miele bridge represents the Miele@home XGW3000 gateway.</description>
<properties>
<property name="vendor">Miele</property>
@ -25,7 +25,7 @@
<context>network-address</context>
<label>Network Address of the Multicast Interface</label>
<description>Network address of openHAB host interface where the binding will listen for multicast events coming
from the Miele@home gateway</description>
from the Miele@home gateway.</description>
</parameter>
<parameter name="userName" type="text" required="false">
<label>Username</label>
@ -36,7 +36,7 @@
<parameter name="password" type="text" required="false">
<context>password</context>
<label>Password</label>
<description>Password for the registered Miele@home</description>
<description>Password for the registered Miele@home user.</description>
</parameter>
</config-description>
</bridge-type>

View File

@ -22,17 +22,16 @@ import org.openhab.core.types.UnDefType;
/**
* This class provides test cases for {@link
* org.openhab.binding.miele.internal.ExtendedDeviceStateUtil}
* org.openhab.binding.miele.internal.DeviceUtil}
*
* @author Jacob Laursen - Added power/water consumption channels
* @author Jacob Laursen - Initial contribution
*/
public class ExtendedDeviceStateUtilTest extends JavaTest {
public class DeviceUtilTest extends JavaTest {
@Test
public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
String actual = ExtendedDeviceStateUtil
.bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
String actual = DeviceUtil.bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
assertEquals("DEADBEEF", actual);
}
@ -45,27 +44,27 @@ public class ExtendedDeviceStateUtilTest extends JavaTest {
@Test
public void stringToBytesWhenTopBitIsUsedReturnsSingleByte() {
byte[] expected = new byte[] { (byte) 0x00, (byte) 0x80, (byte) 0x00 };
byte[] actual = ExtendedDeviceStateUtil.stringToBytes("\u0000\u0080\u0000");
byte[] actual = DeviceUtil.stringToBytes("\u0000\u0080\u0000");
assertArrayEquals(expected, actual);
}
@Test
public void getTemperatureStateWellFormedValueReturnsQuantityType() throws NumberFormatException {
assertEquals(new QuantityType<>(42, SIUnits.CELSIUS), ExtendedDeviceStateUtil.getTemperatureState("42"));
assertEquals(new QuantityType<>(42, SIUnits.CELSIUS), DeviceUtil.getTemperatureState("42"));
}
@Test
public void getTemperatureStateMagicValueReturnsUndefined() throws NumberFormatException {
assertEquals(UnDefType.UNDEF, ExtendedDeviceStateUtil.getTemperatureState("32768"));
assertEquals(UnDefType.UNDEF, DeviceUtil.getTemperatureState("32768"));
}
@Test
public void getTemperatureStateNonNumericValueThrowsNumberFormatException() {
assertThrows(NumberFormatException.class, () -> ExtendedDeviceStateUtil.getTemperatureState("A"));
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A"));
}
@Test
public void getTemperatureStateNullValueThrowsNumberFormatException() {
assertThrows(NumberFormatException.class, () -> ExtendedDeviceStateUtil.getTemperatureState(null));
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState(null));
}
}

View File

@ -21,7 +21,7 @@ import org.openhab.core.test.java.JavaTest;
* This class provides test cases for {@link
* org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier}
*
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
* @author Jacob Laursen - Initial contribution
*/
public class FullyQualifiedApplianceIdentifierTest extends JavaTest {