* 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>
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.miele.internal;
+
+import java.nio.charset.StandardCharsets;
+
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * The {@link DeviceUtil} class contains utility methods for extracting
+ * and parsing device information, for example from ExtendedDeviceState.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+public class DeviceUtil {
+ private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
+ private static final String TEMPERATURE_UNDEFINED = "32768";
+
+ /**
+ * Convert byte array to hex representation.
+ */
+ public static String bytesToHex(byte[] bytes) {
+ byte[] hexChars = new byte[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = HEX_ARRAY[v >>> 4];
+ hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
+ }
+
+ return new String(hexChars, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Convert string consisting of 8 bit characters to byte array.
+ * Note: This simple operation has been extracted and put here to document
+ * and ensure correct behavior for 8 bit characters that should be turned
+ * into single bytes without any UTF-8 encoding.
+ */
+ public static byte[] stringToBytes(String input) {
+ return input.getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ /**
+ * Convert string to Number:Temperature state with unit Celcius
+ */
+ public static State getTemperatureState(String s) throws NumberFormatException {
+ if (TEMPERATURE_UNDEFINED.equals(s)) {
+ return UnDefType.UNDEF;
+ }
+ int temperature = Integer.parseInt(s);
+ return new QuantityType<>(temperature, SIUnits.CELSIUS);
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.miele.internal;
-
-import java.nio.charset.StandardCharsets;
-
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-/**
- * The {@link ExtendedDeviceStateUtil} class contains utility methods for parsing
- * ExtendedDeviceState information
- *
- * @author Jacob Laursen - Added power/water consumption channels
- */
-public class ExtendedDeviceStateUtil {
- private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
- private static final String TEMPERATURE_UNDEFINED = "32768";
-
- /**
- * Convert byte array to hex representation.
- */
- public static String bytesToHex(byte[] bytes) {
- byte[] hexChars = new byte[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = HEX_ARRAY[v >>> 4];
- hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
- }
-
- return new String(hexChars, StandardCharsets.UTF_8);
- }
-
- /**
- * Convert string consisting of 8 bit characters to byte array.
- * Note: This simple operation has been extracted and put here to document
- * and ensure correct behavior for 8 bit characters that should be turned
- * into single bytes without any UTF-8 encoding.
- */
- public static byte[] stringToBytes(String input) {
- return input.getBytes(StandardCharsets.ISO_8859_1);
- }
-
- /**
- * Convert string to Number:Temperature state with unit Celcius
- */
- public static State getTemperatureState(String s) throws NumberFormatException {
- if (TEMPERATURE_UNDEFINED.equals(s)) {
- return UnDefType.UNDEF;
- }
- int temperature = Integer.parseInt(s);
- return new QuantityType<>(temperature, SIUnits.CELSIUS);
- }
-}
* 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;
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";
// 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;
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
*/
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;
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)
// 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);
* 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;
}
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;
*/
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.
*
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) {
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;
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;
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
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;
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
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
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) {
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;
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;
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),
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;
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;
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),
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;
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;
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
protected void onAppliancePropertyChanged(DeviceProperty dp) {
super.onAppliancePropertyChanged(dp);
- if (!dp.Name.equals(STATE_PROPERTY_NAME)) {
+ if (!STATE_PROPERTY_NAME.equals(dp.Name)) {
return;
}
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;
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
protected void onAppliancePropertyChanged(DeviceProperty dp) {
super.onAppliancePropertyChanged(dp);
- if (!dp.Name.equals(STATE_PROPERTY_NAME)) {
+ if (!STATE_PROPERTY_NAME.equals(dp.Name)) {
return;
}
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),
*/
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;
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing) {
- super(thing, HobChannelSelector.class, "Hob");
+ super(thing, HobChannelSelector.class, MIELE_DEVICE_CLASS_HOB);
}
@Override
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),
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;
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
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
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;
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;
}
}
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;
}
}
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());
}
}
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;
}
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);
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);
}
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()) {
} 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);
}
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);
}
}
protected boolean isResultProcessable(JsonElement result) {
- return result != null && !result.isJsonNull();
+ if (result == null) {
+ throw new IllegalArgumentException("Provided result is null");
+ }
+ return !result.isJsonNull();
}
}
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;
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;
**/
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])$");
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;
// 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;
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 {
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;
- }
- }
- if (!isExisting) {
- logger.debug("A new appliance with ID '{}' has been added", hd.UID);
- for (ApplianceStatusListener listener : applianceStatusListeners) {
- listener.onApplianceAdded(hd);
- }
- }
- }
+ for (Thing appliance : getThing().getThings()) {
+ if (appliance.getStatus() == ThingStatus.ONLINE) {
+ String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID);
+ FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(
+ applianceId);
- 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);
+ 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;
}
- }
- }
- 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());
- }
- }
+ for (ApplianceStatusListener listener : applianceStatusListeners) {
+ 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());
}
}
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) {
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": {
// 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 {
}
};
- 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) {
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;
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),
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"));
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;
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;
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
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
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) {
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"),
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"));
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;
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
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;
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
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;
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) {
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);
}
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"),
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"));
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;
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;
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
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);
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;
}
}
}
// process result
- if (isResultProcessable(result)) {
+ if (result != null && isResultProcessable(result)) {
logger.debug("Result of operation is {}", result.getAsString());
}
} catch (IllegalArgumentException e) {
--- /dev/null
+<?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>
--- /dev/null
+# 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
--- /dev/null
+# 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.
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<!-- 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>
<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>
<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>
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.miele.internal;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.test.java.JavaTest;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * This class provides test cases for {@link
+ * org.openhab.binding.miele.internal.DeviceUtil}
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+
+public class DeviceUtilTest extends JavaTest {
+
+ @Test
+ public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
+ String actual = DeviceUtil.bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
+ assertEquals("DEADBEEF", actual);
+ }
+
+ /**
+ * This test guards that the UTF-16 returned by the RPC-JSON API will be
+ * considered as a sequence of 8-bit characters and converted into bytes
+ * accordingly. Default behaviour of String.getBytes() assumes UTF-8
+ * and adds a 0xc2 byte before any character out of ASCII range.
+ */
+ @Test
+ public void stringToBytesWhenTopBitIsUsedReturnsSingleByte() {
+ byte[] expected = new byte[] { (byte) 0x00, (byte) 0x80, (byte) 0x00 };
+ byte[] actual = DeviceUtil.stringToBytes("\u0000\u0080\u0000");
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void getTemperatureStateWellFormedValueReturnsQuantityType() throws NumberFormatException {
+ assertEquals(new QuantityType<>(42, SIUnits.CELSIUS), DeviceUtil.getTemperatureState("42"));
+ }
+
+ @Test
+ public void getTemperatureStateMagicValueReturnsUndefined() throws NumberFormatException {
+ assertEquals(UnDefType.UNDEF, DeviceUtil.getTemperatureState("32768"));
+ }
+
+ @Test
+ public void getTemperatureStateNonNumericValueThrowsNumberFormatException() {
+ assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A"));
+ }
+
+ @Test
+ public void getTemperatureStateNullValueThrowsNumberFormatException() {
+ assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState(null));
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.miele.internal;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.junit.jupiter.api.Test;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.test.java.JavaTest;
-import org.openhab.core.types.UnDefType;
-
-/**
- * This class provides test cases for {@link
- * org.openhab.binding.miele.internal.ExtendedDeviceStateUtil}
- *
- * @author Jacob Laursen - Added power/water consumption channels
- */
-
-public class ExtendedDeviceStateUtilTest extends JavaTest {
-
- @Test
- public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
- String actual = ExtendedDeviceStateUtil
- .bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef });
- assertEquals("DEADBEEF", actual);
- }
-
- /**
- * This test guards that the UTF-16 returned by the RPC-JSON API will be
- * considered as a sequence of 8-bit characters and converted into bytes
- * accordingly. Default behaviour of String.getBytes() assumes UTF-8
- * and adds a 0xc2 byte before any character out of ASCII range.
- */
- @Test
- public void stringToBytesWhenTopBitIsUsedReturnsSingleByte() {
- byte[] expected = new byte[] { (byte) 0x00, (byte) 0x80, (byte) 0x00 };
- byte[] actual = ExtendedDeviceStateUtil.stringToBytes("\u0000\u0080\u0000");
- assertArrayEquals(expected, actual);
- }
-
- @Test
- public void getTemperatureStateWellFormedValueReturnsQuantityType() throws NumberFormatException {
- assertEquals(new QuantityType<>(42, SIUnits.CELSIUS), ExtendedDeviceStateUtil.getTemperatureState("42"));
- }
-
- @Test
- public void getTemperatureStateMagicValueReturnsUndefined() throws NumberFormatException {
- assertEquals(UnDefType.UNDEF, ExtendedDeviceStateUtil.getTemperatureState("32768"));
- }
-
- @Test
- public void getTemperatureStateNonNumericValueThrowsNumberFormatException() {
- assertThrows(NumberFormatException.class, () -> ExtendedDeviceStateUtil.getTemperatureState("A"));
- }
-
- @Test
- public void getTemperatureStateNullValueThrowsNumberFormatException() {
- assertThrows(NumberFormatException.class, () -> ExtendedDeviceStateUtil.getTemperatureState(null));
- }
-}
* 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 {