[miele] Add null annotations and improve error handling robustness (#12497)

* Add null annotations and improve error handling robustness
* Fix compliancy with rule ConstantNameCheck

Fixes #12496

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-03-21 16:49:03 +01:00 committed by GitHub
parent 7c29e4d565
commit f21bbc5945
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 991 additions and 666 deletions

View File

@ -17,6 +17,9 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
@ -29,13 +32,14 @@ import org.openhab.core.types.UnDefType;
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault
public class DeviceUtil { public class DeviceUtil {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private static final String TEMPERATURE_UNDEFINED = "32768"; private static final String TEMPERATURE_UNDEFINED = "32768";
private static final String TEMPERATURE_COLD = "-32760"; private static final String TEMPERATURE_COLD = "-32760";
private static final String TEXT_PREFIX = "miele."; private static final String TEXT_PREFIX = "miele.";
private static final Map<String, String> states = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"), private static final Map<String, String> STATES = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"),
Map.entry("3", "programmed"), Map.entry("4", "waiting-to-start"), Map.entry("5", "running"), Map.entry("3", "programmed"), Map.entry("4", "waiting-to-start"), Map.entry("5", "running"),
Map.entry("6", "paused"), Map.entry("7", "end"), Map.entry("8", "failure"), Map.entry("9", "abort"), Map.entry("6", "paused"), Map.entry("7", "end"), Map.entry("8", "failure"), Map.entry("9", "abort"),
Map.entry("10", "idle"), Map.entry("11", "rinse-hold"), Map.entry("12", "service"), Map.entry("10", "idle"), Map.entry("11", "rinse-hold"), Map.entry("12", "service"),
@ -84,8 +88,9 @@ public class DeviceUtil {
* Get state text for provided string taking into consideration {@link DeviceMetaData} * Get state text for provided string taking into consideration {@link DeviceMetaData}
* as well as built-in/translated strings. * as well as built-in/translated strings.
*/ */
public static State getStateTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public static State getStateTextState(String s, @Nullable DeviceMetaData dmd,
return getTextState(s, dmd, translationProvider, states, MISSING_STATE_TEXT_PREFIX, ""); @Nullable MieleTranslationProvider translationProvider) {
return getTextState(s, dmd, translationProvider, STATES, MISSING_STATE_TEXT_PREFIX, "");
} }
/** /**
@ -100,8 +105,9 @@ public class DeviceUtil {
* @param appliancePrefix Appliance prefix appended to text key (including dot) * @param appliancePrefix Appliance prefix appended to text key (including dot)
* @return Text string as State * @return Text string as State
*/ */
public static State getTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider, public static State getTextState(String s, @Nullable DeviceMetaData dmd,
Map<String, String> valueMap, String propertyPrefix, String appliancePrefix) { @Nullable MieleTranslationProvider translationProvider, Map<String, String> valueMap, String propertyPrefix,
String appliancePrefix) {
if ("0".equals(s)) { if ("0".equals(s)) {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
@ -116,7 +122,7 @@ public class DeviceUtil {
} }
String value = valueMap.get(s); String value = valueMap.get(s);
if (value != null) { if (value != null && translationProvider != null) {
String key = TEXT_PREFIX + propertyPrefix + appliancePrefix + value; String key = TEXT_PREFIX + propertyPrefix + appliancePrefix + value;
return new StringType( return new StringType(
translationProvider.getText(key, gatewayText != null ? gatewayText : propertyPrefix + s)); translationProvider.getText(key, gatewayText != null ? gatewayText : propertyPrefix + s));

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.miele.internal; package org.openhab.binding.miele.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link FullyQualifiedApplianceIdentifier} class represents a fully qualified appliance identifier. * The {@link FullyQualifiedApplianceIdentifier} class represents a fully qualified appliance identifier.
* Example: "hdm:ZigBee:0123456789abcdef#210" * Example: "hdm:ZigBee:0123456789abcdef#210"
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault
public class FullyQualifiedApplianceIdentifier { public class FullyQualifiedApplianceIdentifier {
private String uid; private String uid;
private String protocol; private String protocol;
@ -56,7 +59,7 @@ public class FullyQualifiedApplianceIdentifier {
} }
/** /**
* @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:"") * @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:")
*/ */
public String getProtocol() { public String getProtocol() {
return this.protocol; return this.protocol;

View File

@ -28,6 +28,7 @@ public class MieleBindingConstants {
public static final String BINDING_ID = "miele"; public static final String BINDING_ID = "miele";
public static final String APPLIANCE_ID = "uid"; public static final String APPLIANCE_ID = "uid";
public static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele";
// Properties // Properties
public static final String PROPERTY_DEVICE_CLASS = "deviceClass"; public static final String PROPERTY_DEVICE_CLASS = "deviceClass";

View File

@ -21,6 +21,8 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService; import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService;
import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler; import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler;
import org.openhab.binding.miele.internal.handler.DishWasherHandler; import org.openhab.binding.miele.internal.handler.DishWasherHandler;
@ -56,6 +58,7 @@ import org.osgi.service.component.annotations.Reference;
* *
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
*/ */
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele")
public class MieleHandlerFactory extends BaseThingHandlerFactory { public class MieleHandlerFactory extends BaseThingHandlerFactory {
@ -82,8 +85,8 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
} }
@Override @Override
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID, public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
ThingUID bridgeUID) { @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
ThingUID mieleBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration); ThingUID mieleBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, mieleBridgeUID, null); return super.createThing(thingTypeUID, configuration, mieleBridgeUID, null);
@ -97,7 +100,7 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
} }
@Override @Override
protected ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing); MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing);
registerApplianceDiscoveryService(handler); registerApplianceDiscoveryService(handler);
@ -135,7 +138,8 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
return null; return null;
} }
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) { private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
Configuration configuration) {
if (thingUID == null) { if (thingUID == null) {
String hostID = (String) configuration.get(HOST); String hostID = (String) configuration.get(HOST);
thingUID = new ThingUID(thingTypeUID, hostID); thingUID = new ThingUID(thingTypeUID, hostID);
@ -143,12 +147,16 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
return thingUID; return thingUID;
} }
private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration, private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
ThingUID bridgeUID) { Configuration configuration, @Nullable ThingUID bridgeUID) {
String applianceId = (String) configuration.get(APPLIANCE_ID); String applianceId = (String) configuration.get(APPLIANCE_ID);
if (thingUID == null) { if (thingUID == null) {
thingUID = new ThingUID(thingTypeUID, applianceId, bridgeUID.getId()); if (bridgeUID == null) {
thingUID = new ThingUID(thingTypeUID, applianceId);
} else {
thingUID = new ThingUID(thingTypeUID, bridgeUID, applianceId);
}
} }
return thingUID; return thingUID;
} }

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import com.google.gson.JsonArray;
/**
* The {@link DeviceClassObject} class represents the DeviceClassObject node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
public class DeviceClassObject {
public String DeviceClassType;
public JsonArray Operations;
public String DeviceClass;
public JsonArray Properties;
public DeviceClassObject() {
}
}

View File

@ -10,7 +10,7 @@
* *
* SPDX-License-Identifier: EPL-2.0 * SPDX-License-Identifier: EPL-2.0
*/ */
package org.openhab.binding.miele.internal; package org.openhab.binding.miele.internal.api.dto;
import java.util.Map.Entry; import java.util.Map.Entry;

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import com.google.gson.JsonObject;
/**
* The {@link DeviceProperty} class represents the DeviceProperty node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
public class DeviceProperty {
public String Name;
public String Value;
public int Polling;
public JsonObject Metadata;
public DeviceProperty() {
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2022 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.api.dto;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.MieleBindingConstants;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* The {@link HomeDevice} class represents the HomeDevice node in the response JSON.
*
* @author Jacob Laursen - Initial contribution
**/
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;
public String ProtocolAdapterName;
public String Vendor;
public String UID;
public String Type;
public JsonArray DeviceClasses;
public String Version;
public String TimestampAdded;
public JsonObject Error;
public JsonObject Properties;
public HomeDevice() {
}
public FullyQualifiedApplianceIdentifier getApplianceIdentifier() {
return new FullyQualifiedApplianceIdentifier(this.UID);
}
public String getSerialNumber() {
return Properties.get("serial.number").getAsString();
}
public String getFirmwareVersion() {
return Properties.get("firmware.version").getAsString();
}
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();
}
public String getConnectionBaudRate() {
JsonElement baudRate = Properties.get("connection.baud.rate");
if (baudRate == null) {
return null;
}
return baudRate.getAsString();
}
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(MieleBindingConstants.MIELE_CLASS) && !dcStr.equals(MIELE_APPLIANCE_CLASS)) {
return dcStr.substring(MieleBindingConstants.MIELE_CLASS.length());
}
}
return null;
}
}

View File

@ -20,13 +20,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
import org.openhab.binding.miele.internal.handler.ApplianceStatusListener; import org.openhab.binding.miele.internal.handler.ApplianceStatusListener;
import org.openhab.binding.miele.internal.handler.MieleApplianceHandler; import org.openhab.binding.miele.internal.handler.MieleApplianceHandler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler; import org.openhab.binding.miele.internal.handler.MieleBridgeHandler;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
* @author Martin Lepsy - Added protocol information in order so support WiFi devices * @author Martin Lepsy - Added protocol information in order so support WiFi devices
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener { public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener {
private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class);
@ -75,10 +78,8 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
@Override @Override
public void startScan() { public void startScan() {
List<HomeDevice> appliances = mieleBridgeHandler.getHomeDevices(); List<HomeDevice> appliances = mieleBridgeHandler.getHomeDevices();
if (appliances != null) { for (HomeDevice l : appliances) {
for (HomeDevice l : appliances) { onApplianceAddedInternal(l);
onApplianceAddedInternal(l);
}
} }
} }
@ -100,11 +101,17 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
Map<String, Object> properties = new HashMap<>(9); Map<String, Object> properties = new HashMap<>(9);
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier(); FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
properties.put(Thing.PROPERTY_VENDOR, appliance.Vendor); String vendor = appliance.Vendor;
if (vendor != null) {
properties.put(Thing.PROPERTY_VENDOR, vendor);
}
properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel()); properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber()); properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion()); properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion());
properties.put(PROPERTY_PROTOCOL_ADAPTER, appliance.ProtocolAdapterName); String protocolAdapterName = appliance.ProtocolAdapterName;
if (protocolAdapterName != null) {
properties.put(PROPERTY_PROTOCOL_ADAPTER, protocolAdapterName);
}
properties.put(APPLIANCE_ID, applianceIdentifier.getApplianceId()); properties.put(APPLIANCE_ID, applianceIdentifier.getApplianceId());
String deviceClass = appliance.getDeviceClass(); String deviceClass = appliance.getDeviceClass();
if (deviceClass != null) { if (deviceClass != null) {
@ -149,7 +156,7 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
// nothing to do // nothing to do
} }
private ThingUID getThingUID(HomeDevice appliance) { private @Nullable ThingUID getThingUID(HomeDevice appliance) {
ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID(); ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID();
String modelId = appliance.getDeviceClass(); String modelId = appliance.getDeviceClass();

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 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.exceptions;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MieleRpcException} indicates failure to perform JSON-RPC call.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class MieleRpcException extends IOException {
private static final long serialVersionUID = -8147063891196639054L;
public MieleRpcException(String message) {
super(message);
}
public MieleRpcException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -12,8 +12,10 @@
*/ */
package org.openhab.binding.miele.internal.handler; package org.openhab.binding.miele.internal.handler;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.types.State; import org.openhab.core.types.State;
/** /**
@ -25,6 +27,7 @@ import org.openhab.core.types.State;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added power/water consumption channels * @author Jacob Laursen - Added power/water consumption channels
*/ */
@NonNullByDefault
public interface ApplianceChannelSelector { public interface ApplianceChannelSelector {
@Override @Override
@ -61,7 +64,7 @@ public interface ApplianceChannelSelector {
* @param dmd - the device meta data * @param dmd - the device meta data
* @param translationProvider {@link MieleTranslationProvider} instance * @param translationProvider {@link MieleTranslationProvider} instance
*/ */
State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider); State getState(String s, @Nullable DeviceMetaData dmd, @Nullable MieleTranslationProvider translationProvider);
/** /**
* Returns a State for the given string, taking into * Returns a State for the given string, taking into
@ -71,7 +74,7 @@ public interface ApplianceChannelSelector {
* @param s - the value to be used to instantiate the State * @param s - the value to be used to instantiate the State
* @param dmd - the device meta data * @param dmd - the device meta data
*/ */
State getState(String s, DeviceMetaData dmd); State getState(String s, @Nullable DeviceMetaData dmd);
/** /**
* Returns a raw State for the given string, not taking into * Returns a raw State for the given string, not taking into

View File

@ -12,10 +12,11 @@
*/ */
package org.openhab.binding.miele.internal.handler; package org.openhab.binding.miele.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject; import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty; import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice; import org.openhab.binding.miele.internal.api.dto.HomeDevice;
/** /**
* *
@ -25,6 +26,7 @@ import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public interface ApplianceStatusListener { public interface ApplianceStatusListener {
/** /**

View File

@ -15,12 +15,13 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*; import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
@ -37,50 +38,43 @@ import org.slf4j.LoggerFactory;
* @author Stephan Esch - Initial contribution * @author Stephan Esch - Initial contribution
* @author Jacob Laursen - Added raw channels * @author Jacob Laursen - Added raw channels
*/ */
@NonNullByDefault
public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector { public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) { PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_COFFEE_MACHINE_TEXT_PREFIX); MIELE_COFFEE_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false), PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false), PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_COFFEE_MACHINE_TEXT_PREFIX); MIELE_COFFEE_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false), PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
// lightingStatus signalFailure signalInfo // lightingStatus signalFailure signalInfo
DOOR("signalDoor", "door", OpenClosedType.class, false) { DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -92,13 +86,13 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
SWITCH(null, "switch", OnOffType.class, false); SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class);
private static final Map<String, String> programs = Collections.<String, String> emptyMap(); private static final Map<String, String> PROGRAMS = Map.of();
private static final Map<String, String> phases = Collections.<String, String> emptyMap(); private static final Map<String, String> PHASES = Map.of();
private final String mieleID; private final String mieleID;
private final String channelID; private final String channelID;
@ -139,12 +133,13 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -171,6 +166,6 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
} }

View File

@ -15,6 +15,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.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_COFFEE_SYSTEM; import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_COFFEE_SYSTEM;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineChannelSelector> { public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class); private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class);
@ -50,26 +53,33 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
CoffeeMachineChannelSelector selector = (CoffeeMachineChannelSelector) getValueSelectorFromChannelID(channelID); CoffeeMachineChannelSelector selector = (CoffeeMachineChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { switch (selector) {
switch (selector) { case SWITCH: {
case SWITCH: { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (command.equals(OnOffType.ON)) { if (bridgeHandler == null) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); logger.warn("Command '{}' failed, missing bridge handler", command);
} else if (command.equals(OnOffType.OFF)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
}
break;
} }
default: { if (command.equals(OnOffType.ON)) {
if (!(command instanceof RefreshType)) { result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
logger.debug("{} is a read-only channel that does not accept commands", } else if (command.equals(OnOffType.OFF)) {
selector.getChannelID()); result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
} }
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -81,6 +91,14 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }
} }

View File

@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
import java.math.BigDecimal; import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -42,6 +44,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
*/ */
@NonNullByDefault
public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector> public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSelector>
implements ExtendedDeviceStateListener { implements ExtendedDeviceStateListener {
@ -61,26 +64,33 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
DishwasherChannelSelector selector = (DishwasherChannelSelector) getValueSelectorFromChannelID(channelID); DishwasherChannelSelector selector = (DishwasherChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { switch (selector) {
switch (selector) { case SWITCH: {
case SWITCH: { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (command.equals(OnOffType.ON)) { if (bridgeHandler == null) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); logger.warn("Command '{}' failed, missing bridge handler", command);
} else if (command.equals(OnOffType.OFF)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
} }
default: { if (command.equals(OnOffType.ON)) {
if (!(command instanceof RefreshType)) { result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
logger.debug("{} is a read-only channel that does not accept commands", } else if (command.equals(OnOffType.OFF)) {
selector.getChannelID()); result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
} }
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -92,6 +102,14 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -43,48 +45,41 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added power/water consumption channels, raw channels * @author Jacob Laursen - Added power/water consumption channels, raw channels
*/ */
@NonNullByDefault
public enum DishwasherChannelSelector implements ApplianceChannelSelector { public enum DishwasherChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) { PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_DISHWASHER_TEXT_PREFIX); MIELE_DISHWASHER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false, false), PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) { PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_DISHWASHER_TEXT_PREFIX); MIELE_DISHWASHER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false), PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) { START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -98,7 +93,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}, },
DURATION("duration", "duration", DateTimeType.class, false, false) { DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -112,7 +108,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}, },
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) { ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -126,7 +123,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}, },
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) { FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -140,7 +138,8 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false, false) { DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -152,7 +151,7 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
SWITCH(null, "switch", OnOffType.class, false, false), SWITCH("", "switch", OnOffType.class, false, false),
POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
true), true),
WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
@ -160,12 +159,12 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);
private static final Map<String, String> programs = Map.ofEntries(entry("26", "intensive"), private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("26", "intensive"),
entry("27", "maintenance-programme"), entry("28", "eco"), entry("30", "normal"), entry("32", "automatic"), entry("27", "maintenance-programme"), entry("28", "eco"), entry("30", "normal"), entry("32", "automatic"),
entry("34", "solarsave"), entry("35", "gentle"), entry("36", "extra-quiet"), entry("37", "hygiene"), entry("34", "solarsave"), entry("35", "gentle"), entry("36", "extra-quiet"), entry("37", "hygiene"),
entry("38", "quickpowerwash"), entry("42", "tall-items")); entry("38", "quickpowerwash"), entry("42", "tall-items"));
private static final 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")); entry("4", "rinses"), entry("6", "final-rinse"), entry("7", "drying"), entry("8", "finished"));
private final String mieleID; private final String mieleID;
@ -209,12 +208,13 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -241,6 +241,6 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
} }

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.miele.internal.handler; package org.openhab.binding.miele.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* Appliance handlers can implement the {@link ExtendedDeviceStateListener} interface * Appliance handlers can implement the {@link ExtendedDeviceStateListener} interface
* to extract additional information from the ExtendedDeviceState property. * to extract additional information from the ExtendedDeviceState property.
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault
public interface ExtendedDeviceStateListener { public interface ExtendedDeviceStateListener {
void onApplianceExtendedStateChanged(byte[] extendedDeviceState); void onApplianceExtendedStateChanged(byte[] extendedDeviceState);
} }

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
@ -36,38 +38,39 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added UoM for temperatures, raw channels * @author Jacob Laursen - Added UoM for temperatures, raw channels
*/ */
@NonNullByDefault
public enum FridgeChannelSelector implements ApplianceChannelSelector { public enum FridgeChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false), SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
FRIDGECURRENTTEMP("currentTemperature", "current", QuantityType.class, false) { FRIDGECURRENTTEMP("currentTemperature", "current", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
FRIDGETARGETTEMP("targetTemperature", "target", QuantityType.class, false) { FRIDGETARGETTEMP("targetTemperature", "target", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false) { DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -79,7 +82,7 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
START(null, "start", OnOffType.class, false); START("", "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(FridgeChannelSelector.class);
@ -121,12 +124,13 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -153,7 +157,7 @@ public enum FridgeChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
public State getTemperatureState(String s) { public State getTemperatureState(String s) {

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
@ -37,53 +39,55 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added UoM for temperatures, raw channels * @author Jacob Laursen - Added UoM for temperatures, raw channels
*/ */
@NonNullByDefault
public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector { public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
FREEZERSTATE("freezerState", "freezerstate", StringType.class, false), FREEZERSTATE("freezerState", "freezerstate", StringType.class, false),
FRIDGESTATE("fridgeState", "fridgestate", StringType.class, false), FRIDGESTATE("fridgeState", "fridgestate", StringType.class, false),
SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false), SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false),
SUPERFREEZE(null, SUPERFREEZE_CHANNEL_ID, OnOffType.class, false), SUPERFREEZE("", SUPERFREEZE_CHANNEL_ID, OnOffType.class, false),
FREEZERCURRENTTEMP("freezerCurrentTemperature", "freezercurrent", QuantityType.class, false) { FREEZERCURRENTTEMP("freezerCurrentTemperature", "freezercurrent", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
FREEZERTARGETTEMP("freezerTargetTemperature", "freezertarget", QuantityType.class, false) { FREEZERTARGETTEMP("freezerTargetTemperature", "freezertarget", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
FRIDGECURRENTTEMP("fridgeCurrentTemperature", "fridgecurrent", QuantityType.class, false) { FRIDGECURRENTTEMP("fridgeCurrentTemperature", "fridgecurrent", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
FRIDGETARGETTEMP("fridgeTargetTemperature", "fridgetarget", QuantityType.class, false) { FRIDGETARGETTEMP("fridgeTargetTemperature", "fridgetarget", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false) { DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override @Override
public State getState(String s, @Nullable DeviceMetaData dmd,
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { @Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -95,7 +99,7 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
START(null, "start", OnOffType.class, false); START("", "start", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(FridgeFreezerChannelSelector.class);
@ -138,12 +142,13 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -170,7 +175,7 @@ public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
public State getTemperatureState(String s) { public State getTemperatureState(String s) {

View File

@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*; import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerChannelSelector> { public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class); private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class);
@ -50,33 +53,39 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
FridgeFreezerChannelSelector selector = (FridgeFreezerChannelSelector) getValueSelectorFromChannelID(channelID); FridgeFreezerChannelSelector selector = (FridgeFreezerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
switch (selector) { if (bridgeHandler == null) {
case SUPERCOOL: { logger.warn("Command '{}' failed, missing bridge handler", command);
if (command.equals(OnOffType.ON)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); }
} else if (command.equals(OnOffType.OFF)) { switch (selector) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); case SUPERCOOL: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
} }
case SUPERFREEZE: { break;
if (command.equals(OnOffType.ON)) { }
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing"); case SUPERFREEZE: {
} else if (command.equals(OnOffType.OFF)) { if (command.equals(OnOffType.ON)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing"); result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing");
} } else if (command.equals(OnOffType.OFF)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing");
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID());
} }
} }
// process result // process result
@ -87,6 +96,14 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }

View File

@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.*; import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector> { public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class);
@ -50,32 +53,39 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
FridgeChannelSelector selector = (FridgeChannelSelector) getValueSelectorFromChannelID(channelID); FridgeChannelSelector selector = (FridgeChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
switch (selector) { if (bridgeHandler == null) {
case SUPERCOOL: { logger.warn("Command '{}' failed, missing bridge handler", command);
if (command.equals(OnOffType.ON)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); }
} else if (command.equals(OnOffType.OFF)) { switch (selector) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); case SUPERCOOL: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling");
} }
case START: { break;
if (command.equals(OnOffType.ON)) { }
result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); case START: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
} }
default: { break;
if (!(command instanceof RefreshType)) { }
logger.debug("{} is a read-only channel that does not accept commands", default: {
selector.getChannelID()); if (!(command instanceof RefreshType)) {
} logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -87,6 +97,14 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }

View File

@ -16,13 +16,16 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.Type; import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,26 +35,25 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added raw channels * @author Jacob Laursen - Added raw channels
*/ */
@NonNullByDefault
public enum HobChannelSelector implements ApplianceChannelSelector { public enum HobChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PLATES("plateNumbers", "plates", DecimalType.class, true), PLATES("plateNumbers", "plates", DecimalType.class, true),
PLATE1_POWER("plate1PowerStep", "plate1power", DecimalType.class, false), PLATE1_POWER("plate1PowerStep", "plate1power", DecimalType.class, false),
PLATE1_HEAT("plate1RemainingHeat", "plate1heat", DecimalType.class, false) { PLATE1_HEAT("plate1RemainingHeat", "plate1heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
// If there is remaining heat, the device metadata contains some informative string which can not be // If there is remaining heat, the device metadata contains some informative string which can not be
// converted into a DecimalType. We therefore ignore the metadata and return the device property value as a // converted into a DecimalType. We therefore ignore the metadata and return the device property value as a
// State // State
@ -62,7 +64,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE2_POWER("plate2PowerStep", "plate2power", DecimalType.class, false), PLATE2_POWER("plate2PowerStep", "plate2power", DecimalType.class, false),
PLATE2_HEAT("plate2RemainingHeat", "plate2heat", DecimalType.class, false) { PLATE2_HEAT("plate2RemainingHeat", "plate2heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
@ -70,7 +73,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE3_POWER("plate3PowerStep", "plate3power", DecimalType.class, false), PLATE3_POWER("plate3PowerStep", "plate3power", DecimalType.class, false),
PLATE3_HEAT("plate3RemainingHeat", "plate3heat", DecimalType.class, false) { PLATE3_HEAT("plate3RemainingHeat", "plate3heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
@ -78,7 +82,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE4_POWER("plate4PowerStep", "plate4power", DecimalType.class, false), PLATE4_POWER("plate4PowerStep", "plate4power", DecimalType.class, false),
PLATE4_HEAT("plate4RemainingHeat", "plate4heat", DecimalType.class, false) { PLATE4_HEAT("plate4RemainingHeat", "plate4heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
@ -86,7 +91,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE5_POWER("plate5PowerStep", "plate5power", DecimalType.class, false), PLATE5_POWER("plate5PowerStep", "plate5power", DecimalType.class, false),
PLATE5_HEAT("plate5RemainingHeat", "plate5heat", DecimalType.class, false) { PLATE5_HEAT("plate5RemainingHeat", "plate5heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
@ -94,7 +100,8 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
PLATE6_POWER("plate6PowerStep", "plate6power", DecimalType.class, false), PLATE6_POWER("plate6PowerStep", "plate6power", DecimalType.class, false),
PLATE6_HEAT("plate6RemainingHeat", "plate6heat", DecimalType.class, false) { PLATE6_HEAT("plate6RemainingHeat", "plate6heat", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
@ -140,12 +147,13 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -172,6 +180,6 @@ public enum HobChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
} }

View File

@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal.handler;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOB; import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOB;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -27,6 +28,7 @@ import org.openhab.core.types.Command;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - fixed handling of REFRESH commands * @author Kai Kreuzer - fixed handling of REFRESH commands
*/ */
@NonNullByDefault
public class HobHandler extends MieleApplianceHandler<HobChannelSelector> { public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) { public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {

View File

@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
@ -34,26 +36,25 @@ import org.slf4j.LoggerFactory;
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Added raw channels * @author Jacob Laursen - Added raw channels
*/ */
@NonNullByDefault
public enum HoodChannelSelector implements ApplianceChannelSelector { public enum HoodChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
VENTILATION("ventilationPower", "ventilation", DecimalType.class, false), VENTILATION("ventilationPower", "ventilation", DecimalType.class, false),
LIGHT("lightingStatus", "light", OnOffType.class, false) { LIGHT("lightingStatus", "light", OnOffType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("ON"); return getState("ON");
} }
@ -65,7 +66,7 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
STOP(null, "stop", OnOffType.class, false); STOP("", "stop", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(HoodChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(HoodChannelSelector.class);
@ -107,12 +108,13 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -139,6 +141,6 @@ public enum HoodChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
} }

View File

@ -15,6 +15,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.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOOD; import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOOD;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -35,6 +37,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/ **/
@NonNullByDefault
public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> { public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(HoodHandler.class); private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
@ -49,31 +52,37 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
HoodChannelSelector selector = (HoodChannelSelector) getValueSelectorFromChannelID(channelID); HoodChannelSelector selector = (HoodChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
switch (selector) { if (bridgeHandler == null) {
case LIGHT: { logger.warn("Command '{}' failed, missing bridge handler", command);
if (command.equals(OnOffType.ON)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting"); }
} else if (command.equals(OnOffType.OFF)) { switch (selector) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting"); case LIGHT: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting");
} }
case STOP: { break;
if (command.equals(OnOffType.ON)) { }
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); case STOP: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
default: {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
break;
}
default: {
logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID());
} }
} }
// process result // process result
@ -84,6 +93,14 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }
} }

View File

@ -19,17 +19,16 @@ import java.util.IllformedLocaleException;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceClassObject; import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty; import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice; import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -43,11 +42,11 @@ import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
@ -62,23 +61,23 @@ import com.google.gson.JsonParser;
* @author Martin Lepsy - Added check for JsonNull result * @author Martin Lepsy - Added check for JsonNull result
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
implements ApplianceStatusListener { implements ApplianceStatusListener {
private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class); private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN,
.of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB, THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD,
THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE) THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE);
.collect(Collectors.toSet());
protected Gson gson = new Gson(); protected Gson gson = new Gson();
protected String applianceId; protected @Nullable String applianceId;
protected MieleBridgeHandler bridgeHandler; protected @Nullable MieleBridgeHandler bridgeHandler;
protected TranslationProvider i18nProvider; protected TranslationProvider i18nProvider;
protected LocaleProvider localeProvider; protected LocaleProvider localeProvider;
protected MieleTranslationProvider translationProvider; protected @Nullable MieleTranslationProvider translationProvider;
private Class<E> selectorType; private Class<E> selectorType;
protected String modelID; protected String modelID;
@ -101,7 +100,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
String.format("Could not get enum constants for value selector: %s", valueSelectorText)); String.format("Could not get enum constants for value selector: %s", valueSelectorText));
} }
for (ApplianceChannelSelector c : enumConstants) { for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) { if (c.getChannelID().equals(valueSelectorText)) {
return c; return c;
} }
} }
@ -117,7 +116,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
String.format("Could not get enum constants for value selector: %s", valueSelectorText)); String.format("Could not get enum constants for value selector: %s", valueSelectorText));
} }
for (ApplianceChannelSelector c : enumConstants) { for (ApplianceChannelSelector c : enumConstants) {
if (c != null && c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) { if (!c.getMieleID().isEmpty() && c.getMieleID().equals(valueSelectorText)) {
return c; return c;
} }
} }
@ -168,7 +167,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (applianceId != null) { if (applianceId != null) {
MieleBridgeHandler bridgeHandler = getMieleBridgeHandler(); MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
if (bridgeHandler != null) { if (bridgeHandler != null) {
getMieleBridgeHandler().unregisterApplianceStatusListener(this); bridgeHandler.unregisterApplianceStatusListener(this);
} }
applianceId = null; applianceId = null;
} }
@ -186,12 +185,17 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override @Override
public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicationIdentifier,
DeviceClassObject dco) { DeviceClassObject dco) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = this.applianceId;
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) { if (applianceId == null || !applianceId.equals(applicationIdentifier.getApplianceId())) {
return; return;
} }
for (JsonElement prop : dco.Properties.getAsJsonArray()) { JsonArray properties = dco.Properties;
if (properties == null) {
return;
}
for (JsonElement prop : properties.getAsJsonArray()) {
try { try {
DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class); DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class);
if (dp == null) { if (dp == null) {
@ -210,9 +214,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override @Override
public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, DeviceProperty dp) { public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, DeviceProperty dp) {
String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = this.applianceId;
if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) { if (applianceId == null || !applianceId.equals(applicationIdentifier.getApplianceId())) {
return; return;
} }
@ -225,8 +229,8 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (dp.Metadata == null) { if (dp.Metadata == null) {
String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim()); String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim());
if (metadata != null) { if (metadata != null) {
JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata); JsonObject jsonMetadata = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class); dmd = gson.fromJson(jsonMetadata, DeviceMetaData.class);
// only keep the enum, if any - that's all we care for events we receive via multicast // only keep the enum, if any - that's all we care for events we receive via multicast
// all other fields are nulled // all other fields are nulled
if (dmd != null) { if (dmd != null) {
@ -237,8 +241,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
} }
} }
} }
if (dp.Metadata != null) { JsonObject jsonMetadata = dp.Metadata;
String metadata = dp.Metadata.toString().replace("enum", "MieleEnum"); if (jsonMetadata != null) {
String metadata = jsonMetadata.toString().replace("enum", "MieleEnum");
JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata); JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata);
dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class); dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class);
metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata); metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata);
@ -269,19 +274,14 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
if (!selector.isProperty()) { if (!selector.isProperty()) {
ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID()); ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID());
if (dp.Value != null) { State state = selector.getState(dpValue, dmd, this.translationProvider);
State state = selector.getState(dpValue, dmd, this.translationProvider); logger.trace("Update state of {} with getState '{}'", theChannelUID, state);
logger.trace("Update state of {} with getState '{}'", theChannelUID, state); updateState(theChannelUID, state);
updateState(theChannelUID, state); updateRawChannel(dp.Name, dpValue);
updateRawChannel(dp.Name, dpValue);
} else {
updateState(theChannelUID, UnDefType.UNDEF);
}
} else { } else {
logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(), logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(),
getThing().getUID(), selector.getState(dpValue, dmd, this.translationProvider).toString()); getThing().getUID(), selector.getState(dpValue, dmd, this.translationProvider).toString());
@NonNull Map<String, String> properties = editProperties();
Map<@NonNull String, @NonNull String> properties = editProperties();
properties.put(selector.getChannelID(), properties.put(selector.getChannelID(),
selector.getState(dpValue, dmd, this.translationProvider).toString()); selector.getState(dpValue, dmd, this.translationProvider).toString());
updateProperties(properties); updateProperties(properties);
@ -330,31 +330,46 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override @Override
public void onApplianceRemoved(HomeDevice appliance) { public void onApplianceRemoved(HomeDevice appliance) {
String applianceId = this.applianceId;
if (applianceId == null) { if (applianceId == null) {
return; return;
} }
if (applianceId.equals(appliance.getApplianceIdentifier().getApplianceId())) { FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
if (applianceIdentifier == null) {
return;
}
if (applianceId.equals(applianceIdentifier.getApplianceId())) {
updateStatus(ThingStatus.OFFLINE); updateStatus(ThingStatus.OFFLINE);
} }
} }
@Override @Override
public void onApplianceAdded(HomeDevice appliance) { public void onApplianceAdded(HomeDevice appliance) {
String applianceId = this.applianceId;
if (applianceId == null) { if (applianceId == null) {
return; return;
} }
FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier(); FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier();
if (applianceIdentifier == null) {
return;
}
if (applianceId.equals(applianceIdentifier.getApplianceId())) { if (applianceId.equals(applianceIdentifier.getApplianceId())) {
@NonNull Map<String, String> properties = editProperties();
Map<@NonNull String, @NonNull String> properties = editProperties(); String vendor = appliance.Vendor;
properties.put(Thing.PROPERTY_VENDOR, appliance.Vendor); if (vendor != null) {
properties.put(Thing.PROPERTY_VENDOR, vendor);
}
properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel()); properties.put(Thing.PROPERTY_MODEL_ID, appliance.getApplianceModel());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber()); properties.put(Thing.PROPERTY_SERIAL_NUMBER, appliance.getSerialNumber());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion()); properties.put(Thing.PROPERTY_FIRMWARE_VERSION, appliance.getFirmwareVersion());
properties.put(PROPERTY_PROTOCOL_ADAPTER, appliance.ProtocolAdapterName); String protocolAdapterName = appliance.ProtocolAdapterName;
if (protocolAdapterName != null) {
properties.put(PROPERTY_PROTOCOL_ADAPTER, protocolAdapterName);
}
String deviceClass = appliance.getDeviceClass(); String deviceClass = appliance.getDeviceClass();
if (deviceClass != null) { if (deviceClass != null) {
properties.put(PROPERTY_DEVICE_CLASS, deviceClass); properties.put(PROPERTY_DEVICE_CLASS, deviceClass);
@ -372,7 +387,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
} }
} }
private synchronized MieleBridgeHandler getMieleBridgeHandler() { private synchronized @Nullable MieleBridgeHandler getMieleBridgeHandler() {
if (this.bridgeHandler == null) { if (this.bridgeHandler == null) {
Bridge bridge = getBridge(); Bridge bridge = getBridge();
if (bridge == null) { if (bridge == null) {
@ -380,8 +395,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
} }
ThingHandler handler = bridge.getHandler(); ThingHandler handler = bridge.getHandler();
if (handler instanceof MieleBridgeHandler) { if (handler instanceof MieleBridgeHandler) {
this.bridgeHandler = (MieleBridgeHandler) handler; var bridgeHandler = (MieleBridgeHandler) handler;
this.bridgeHandler.registerApplianceStatusListener(this); this.bridgeHandler = bridgeHandler;
bridgeHandler.registerApplianceStatusListener(this);
} else { } else {
return null; return null;
} }
@ -390,9 +406,6 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
} }
protected boolean isResultProcessable(JsonElement result) { protected boolean isResultProcessable(JsonElement result) {
if (result == null) {
throw new IllegalArgumentException("Provided result is null");
}
return !result.isJsonNull(); return !result.isJsonNull();
} }
} }

View File

@ -30,7 +30,6 @@ import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.IllformedLocaleException; import java.util.IllformedLocaleException;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -49,8 +48,13 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
import org.openhab.binding.miele.internal.api.dto.DeviceProperty;
import org.openhab.binding.miele.internal.api.dto.HomeDevice;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.common.NamedThreadFactory; import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -69,6 +73,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
/** /**
@ -80,12 +85,10 @@ import com.google.gson.JsonParser;
* @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice * @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/ **/
@NonNullByDefault
public class MieleBridgeHandler extends BaseBridgeHandler { public class MieleBridgeHandler extends BaseBridgeHandler {
@NonNull public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_XGW3000);
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 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])$"); .compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
@ -102,128 +105,14 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class); private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class);
protected List<ApplianceStatusListener> applianceStatusListeners = new CopyOnWriteArrayList<>(); protected List<ApplianceStatusListener> applianceStatusListeners = new CopyOnWriteArrayList<>();
protected ScheduledFuture<?> pollingJob; protected @Nullable ScheduledFuture<?> pollingJob;
protected ExecutorService executor; protected @Nullable ExecutorService executor;
protected Future<?> eventListenerJob; protected @Nullable Future<?> eventListenerJob;
@NonNull
protected Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<String, HomeDevice>(); protected Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<String, HomeDevice>();
protected Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<String, HomeDevice>(); protected Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<String, HomeDevice>();
protected URL url; protected @Nullable 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;
public String ProtocolAdapterName;
public String Vendor;
public String UID;
public String Type;
public JsonArray DeviceClasses;
public String Version;
public String TimestampAdded;
public JsonObject Error;
public JsonObject Properties;
HomeDevice() {
}
public FullyQualifiedApplianceIdentifier getApplianceIdentifier() {
return new FullyQualifiedApplianceIdentifier(this.UID);
}
@NonNull
public String getSerialNumber() {
return Properties.get("serial.number").getAsString();
}
@NonNull
public String getFirmwareVersion() {
return Properties.get("firmware.version").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();
}
public String getConnectionBaudRate() {
JsonElement baudRate = Properties.get("connection.baud.rate");
if (baudRate == null) {
return null;
}
return baudRate.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 {
public String DeviceClassType;
public JsonArray Operations;
public String DeviceClass;
public JsonArray Properties;
DeviceClassObject() {
}
}
public class DeviceOperation {
public String Name;
public String Arguments;
public JsonObject Metadata;
DeviceOperation() {
}
}
public class DeviceProperty {
public String Name;
public String Value;
public int Polling;
public JsonObject Metadata;
DeviceProperty() {
}
}
public MieleBridgeHandler(Bridge bridge) { public MieleBridgeHandler(Bridge bridge) {
super(bridge); super(bridge);
@ -245,9 +134,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return; return;
} }
// for future usage - no headers to be set for now
headers = new HashMap<>();
onUpdate(); onUpdate();
lastBridgeConnectionState = false; lastBridgeConnectionState = false;
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
@ -328,10 +214,8 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd); cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd);
} }
@NonNull Set<Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
Set<@NonNull Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet(); Iterator<Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
@NonNull
Iterator<@NonNull Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Entry<String, HomeDevice> cachedEntry = iterator.next(); Entry<String, HomeDevice> cachedEntry = iterator.next();
@ -349,11 +233,13 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
for (Thing appliance : getThing().getThings()) { for (Thing appliance : getThing().getThings()) {
if (appliance.getStatus() == ThingStatus.ONLINE) { if (appliance.getStatus() == ThingStatus.ONLINE) {
String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID);
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId( FullyQualifiedApplianceIdentifier applianceIdentifier = null;
applianceId); if (applianceId != null) {
applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId);
}
if (applianceIdentifier == null) { if (applianceIdentifier == null) {
logger.error("The appliance with ID '{}' was not found in appliance list from bridge.", logger.warn("The appliance with ID '{}' was not found in appliance list from bridge.",
applianceId); applianceId);
continue; continue;
} }
@ -363,29 +249,33 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
args[1] = true; args[1] = true;
JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args); JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args);
if (result != null) { for (JsonElement obj : result.getAsJsonArray()) {
for (JsonElement obj : result.getAsJsonArray()) { try {
try { DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class);
// Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl // Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl
if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) { if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) {
continue; continue;
}
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) { } catch (MieleRpcException e) {
logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage()); Throwable cause = e.getCause();
if (cause == null) {
logger.debug("An exception occurred while polling an appliance: '{}'", e.getMessage());
} else {
logger.debug("An exception occurred while polling an appliance: '{}' -> '{}'", e.getMessage(),
cause.getMessage());
}
} }
} }
@ -396,12 +286,9 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
// That's why we do an HTTP access instead // That's why we do an HTTP access instead
// If there is no connection, this line will fail // If there is no connection, this line will fail
JsonElement result = invokeRPC("system.listMethods", null); invokeRPC("system.listMethods", new Object[0]);
if (result == null) { } catch (MieleRpcException e) {
logger.debug("{} is not reachable", ipAddress); logger.debug("{} is not reachable", ipAddress);
return false;
}
} catch (Exception e) {
return false; return false;
} }
@ -421,16 +308,24 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
for (JsonElement obj : result.getAsJsonArray()) { for (JsonElement obj : result.getAsJsonArray()) {
HomeDevice hd = gson.fromJson(obj, HomeDevice.class); HomeDevice hd = gson.fromJson(obj, HomeDevice.class);
devices.add(hd); if (hd != null) {
devices.add(hd);
}
}
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.debug("An exception occurred while getting the home devices: '{}'", e.getMessage());
} else {
logger.debug("An exception occurred while getting the home devices: '{}' -> '{}", e.getMessage(),
cause.getMessage());
} }
} catch (Exception e) {
logger.debug("An exception occurred while getting the home devices :'{}'", e.getMessage());
} }
} }
return devices; return devices;
} }
private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) { private @Nullable FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) {
HomeDevice homeDevice = this.cachedHomeDevicesByApplianceId.get(applianceId); HomeDevice homeDevice = this.cachedHomeDevicesByApplianceId.get(applianceId);
if (homeDevice == null) { if (homeDevice == null) {
return null; return null;
@ -475,19 +370,17 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(), logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(),
packet.getPort()); packet.getPort());
DeviceProperty dp = new DeviceProperty();
String id = null;
String[] parts = event.split("&"); String[] parts = event.split("&");
String id = null, name = null, value = null;
for (String p : parts) { for (String p : parts) {
String[] subparts = p.split("="); String[] subparts = p.split("=");
switch (subparts[0]) { switch (subparts[0]) {
case "property": { case "property": {
dp.Name = subparts[1]; name = subparts[1];
break; break;
} }
case "value": { case "value": {
dp.Value = subparts[1].strip().trim(); value = subparts[1].strip().trim();
break; break;
} }
case "id": { case "id": {
@ -497,7 +390,7 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
} }
if (id == null) { if (id == null || name == null || value == null) {
continue; continue;
} }
@ -514,8 +407,11 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
applianceIdentifier = device.getApplianceIdentifier(); applianceIdentifier = device.getApplianceIdentifier();
} }
var deviceProperty = new DeviceProperty();
deviceProperty.Name = name;
deviceProperty.Value = value;
for (ApplianceStatusListener listener : applianceStatusListeners) { for (ApplianceStatusListener listener : applianceStatusListeners) {
listener.onAppliancePropertyChanged(applianceIdentifier, dp); listener.onAppliancePropertyChanged(applianceIdentifier, deviceProperty);
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
try { try {
@ -549,18 +445,15 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
}; };
public JsonElement invokeOperation(String applianceId, String modelID, String methodName) { public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException {
if (getThing().getStatus() != ThingStatus.ONLINE) { if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("The Bridge is offline - operations can not be invoked."); throw new MieleRpcException("Bridge is offline, operations can not be invoked");
return null;
} }
FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId); FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId);
if (applianceIdentifier == null) { if (applianceIdentifier == null) {
logger.error( throw new MieleRpcException("Appliance with ID" + applianceId
"The appliance with ID '{}' was not found in appliance list from bridge - operations can not be invoked.", + " was not found in appliance list from gateway - operations can not be invoked");
applianceId);
return null;
} }
Object[] args = new Object[4]; Object[] args = new Object[4];
@ -572,65 +465,72 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
return invokeRPC("HDAccess/invokeDCOOperation", args); return invokeRPC("HDAccess/invokeDCOOperation", args);
} }
protected JsonElement invokeRPC(String methodName, Object[] args) { protected JsonElement invokeRPC(String methodName, Object[] args) throws MieleRpcException {
int id = rand.nextInt(Integer.MAX_VALUE); JsonElement result = null;
URL url = this.url;
if (url == null) {
throw new MieleRpcException("URL is not set");
}
JsonObject req = new JsonObject(); JsonObject req = new JsonObject();
int id = rand.nextInt(Integer.MAX_VALUE);
req.addProperty("jsonrpc", "2.0"); req.addProperty("jsonrpc", "2.0");
req.addProperty("id", id); req.addProperty("id", id);
req.addProperty("method", methodName); req.addProperty("method", methodName);
JsonElement result = null;
JsonArray params = new JsonArray(); JsonArray params = new JsonArray();
if (args != null) { for (Object o : args) {
for (Object o : args) { params.add(gson.toJsonTree(o));
params.add(gson.toJsonTree(o));
}
} }
req.add("params", params); req.add("params", params);
String requestData = req.toString(); String requestData = req.toString();
String responseData = null; String responseData = null;
try { try {
responseData = post(url, headers, requestData); responseData = post(url, Collections.emptyMap(), requestData);
} catch (Exception e) { } catch (IOException e) {
logger.debug("An exception occurred while posting data : '{}'", e.getMessage()); throw new MieleRpcException("Exception occurred while posting data", e);
} }
if (responseData != null) { logger.trace("The request '{}' yields '{}'", requestData, responseData);
logger.trace("The request '{}' yields '{}'", requestData, responseData); JsonObject parsedResponse = null;
JsonObject resp = (JsonObject) JsonParser.parseReader(new StringReader(responseData)); try {
parsedResponse = (JsonObject) JsonParser.parseReader(new StringReader(responseData));
} catch (JsonParseException e) {
throw new MieleRpcException("Error parsing JSON response", e);
}
result = resp.get("result"); JsonElement error = parsedResponse.get("error");
JsonElement error = resp.get("error"); if (error != null && !error.isJsonNull()) {
if (error.isJsonPrimitive()) {
if (error != null && !error.isJsonNull()) { throw new MieleRpcException("Remote exception occurred: '" + error.getAsString() + "'");
if (error.isJsonPrimitive()) { } else if (error.isJsonObject()) {
logger.debug("A remote exception occurred: '{}'", error.getAsString()); JsonObject o = error.getAsJsonObject();
} else if (error.isJsonObject()) { Integer code = (o.has("code") ? o.get("code").getAsInt() : null);
JsonObject o = error.getAsJsonObject(); String message = (o.has("message") ? o.get("message").getAsString() : null);
Integer code = (o.has("code") ? o.get("code").getAsInt() : null); String data = (o.has("data")
String message = (o.has("message") ? o.get("message").getAsString() : null); ? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString())
String data = (o.has("data") ? (o.get("data") instanceof JsonObject ? o.get("data").toString() : null);
: o.get("data").getAsString()) : null); throw new MieleRpcException(
logger.debug("A remote exception occurred: '{}':'{}':'{}'", code, message, data); "Remote exception occurred: '" + code + "':'" + message + "':'" + data + "'");
} else { } else {
logger.debug("An unknown remote exception occurred: '{}'", error.toString()); throw new MieleRpcException("Unknown remote exception occurred: '" + error.toString() + "'");
}
} }
} }
result = parsedResponse.get("result");
if (result == null) {
throw new MieleRpcException("Result is missing in response");
}
return result; return result;
} }
protected String post(URL url, Map<String, String> headers, String data) throws IOException { protected String post(URL url, Map<String, String> headers, String data) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (headers != null) { for (Map.Entry<String, String> entry : headers.entrySet()) {
for (Map.Entry<String, String> entry : headers.entrySet()) { connection.addRequestProperty(entry.getKey(), entry.getValue());
connection.addRequestProperty(entry.getKey(), entry.getValue());
}
} }
connection.addRequestProperty("Accept-Encoding", "gzip"); connection.addRequestProperty("Accept-Encoding", "gzip");
@ -688,16 +588,21 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private synchronized void onUpdate() { private synchronized void onUpdate() {
logger.debug("Scheduling the Miele polling job"); logger.debug("Scheduling the Miele polling job");
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob == null || pollingJob.isCancelled()) { if (pollingJob == null || pollingJob.isCancelled()) {
logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD); logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD);
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD, TimeUnit.SECONDS); pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, POLLING_PERIOD, TimeUnit.SECONDS);
this.pollingJob = pollingJob;
logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone()); logger.trace("Scheduling the Miele polling job Job is done ?{}", pollingJob.isDone());
} }
logger.debug("Scheduling the Miele event listener job"); logger.debug("Scheduling the Miele event listener job");
Future<?> eventListenerJob = this.eventListenerJob;
if (eventListenerJob == null || eventListenerJob.isCancelled()) { if (eventListenerJob == null || eventListenerJob.isCancelled()) {
executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("binding-miele")); ExecutorService executor = Executors
eventListenerJob = executor.submit(eventListenerRunnable); .newSingleThreadExecutor(new NamedThreadFactory("binding-" + BINDING_ID));
this.executor = executor;
this.eventListenerJob = executor.submit(eventListenerRunnable);
} }
} }
@ -725,9 +630,6 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
} }
public boolean registerApplianceStatusListener(ApplianceStatusListener applianceStatusListener) { public boolean registerApplianceStatusListener(ApplianceStatusListener applianceStatusListener) {
if (applianceStatusListener == null) {
throw new IllegalArgumentException("It's not allowed to pass a null ApplianceStatusListener.");
}
boolean result = applianceStatusListeners.add(applianceStatusListener); boolean result = applianceStatusListeners.add(applianceStatusListener);
if (result && isInitialized()) { if (result && isInitialized()) {
onUpdate(); onUpdate();
@ -759,17 +661,20 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
ScheduledFuture<?> pollingJob = this.pollingJob;
if (pollingJob != null) { if (pollingJob != null) {
pollingJob.cancel(true); pollingJob.cancel(true);
pollingJob = null; this.pollingJob = null;
} }
Future<?> eventListenerJob = this.eventListenerJob;
if (eventListenerJob != null) { if (eventListenerJob != null) {
eventListenerJob.cancel(true); eventListenerJob.cancel(true);
eventListenerJob = null; this.eventListenerJob = null;
} }
ExecutorService executor = this.executor;
if (executor != null) { if (executor != null) {
executor.shutdownNow(); executor.shutdownNow();
executor = null; this.executor = null;
} }
} }
} }

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -43,39 +45,35 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added UoM for temperatures, raw channels * @author Jacob Laursen - Added UoM for temperatures, raw channels
*/ */
@NonNullByDefault
public enum OvenChannelSelector implements ApplianceChannelSelector { public enum OvenChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false), PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false),
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false), PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false), PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_OVEN_TEXT_PREFIX); MIELE_OVEN_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false), PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) { START_TIME("startTime", "start", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -89,7 +87,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}, },
DURATION("duration", "duration", DateTimeType.class, false) { DURATION("duration", "duration", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -103,7 +102,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}, },
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) { ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -117,7 +117,8 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}, },
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) { FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -131,32 +132,37 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
}, },
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false) { TARGET_TEMP("targetTemperature", "target", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
MEASURED_TEMP("measuredTemperature", "measured", QuantityType.class, false) { MEASURED_TEMP("measuredTemperature", "measured", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
DEVICE_TEMP_ONE("deviceTemperature1", "temp1", QuantityType.class, false) { DEVICE_TEMP_ONE("deviceTemperature1", "temp1", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
DEVICE_TEMP_TWO("deviceTemperature2", "temp2", QuantityType.class, false) { DEVICE_TEMP_TWO("deviceTemperature2", "temp2", QuantityType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false) { DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -168,12 +174,12 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
STOP(null, "stop", OnOffType.class, false), STOP("", "stop", OnOffType.class, false),
SWITCH(null, "switch", OnOffType.class, false); SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class);
private static final 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("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")); entry("10", "defrost"), entry("11", "cooling-down"), entry("12", "energy-save-phase"));
@ -215,12 +221,13 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -247,7 +254,7 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
public State getTemperatureState(String s) { public State getTemperatureState(String s) {

View File

@ -15,6 +15,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.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_OVEN; import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_OVEN;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
*/ */
@NonNullByDefault
public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> { public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(OvenHandler.class); private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
@ -50,32 +53,39 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
OvenChannelSelector selector = (OvenChannelSelector) getValueSelectorFromChannelID(channelID); OvenChannelSelector selector = (OvenChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
switch (selector) { if (bridgeHandler == null) {
case SWITCH: { logger.warn("Command '{}' failed, missing bridge handler", command);
if (command.equals(OnOffType.ON)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); }
} else if (command.equals(OnOffType.OFF)) { switch (selector) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff"); case SWITCH: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn");
} else if (command.equals(OnOffType.OFF)) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff");
} }
case STOP: { break;
if (command.equals(OnOffType.ON)) { }
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); case STOP: {
} if (command.equals(OnOffType.ON)) {
break; result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
} }
default: { break;
if (!(command instanceof RefreshType)) { }
logger.debug("{} is a read-only channel that does not accept commands", default: {
selector.getChannelID()); if (!(command instanceof RefreshType)) {
} logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -87,6 +97,14 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }
} }

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -42,49 +44,42 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added raw channels * @author Jacob Laursen - Added raw channels
*/ */
@NonNullByDefault
public enum TumbleDryerChannelSelector implements ApplianceChannelSelector { public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) { PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_TUMBLE_DRYER_TEXT_PREFIX); MIELE_TUMBLE_DRYER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false), PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false),
PROGRAMTYPE("programType", "type", StringType.class, false), PROGRAMTYPE("programType", "type", StringType.class, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_TUMBLE_DRYER_TEXT_PREFIX); MIELE_TUMBLE_DRYER_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false), PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false),
START_TIME("startTime", "start", DateTimeType.class, false) { START_TIME("startTime", "start", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -98,7 +93,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}, },
DURATION("duration", "duration", DateTimeType.class, false) { DURATION("duration", "duration", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -112,7 +108,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}, },
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) { ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -126,7 +123,8 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}, },
FINISH_TIME("finishTime", "finish", DateTimeType.class, false) { FINISH_TIME("finishTime", "finish", DateTimeType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -140,14 +138,16 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
}, },
DRYING_STEP("dryingStep", "step", DecimalType.class, false) { DRYING_STEP("dryingStep", "step", DecimalType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getState(s); return getState(s);
} }
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false) { DOOR("signalDoor", "door", OpenClosedType.class, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -159,11 +159,11 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
SWITCH(null, "switch", OnOffType.class, false); SWITCH("", "switch", OnOffType.class, false);
private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class);
private static final Map<String, String> programs = Map.ofEntries(entry("10", "automatic-plus"), private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("10", "automatic-plus"),
entry("20", "cottons"), entry("23", "cottons-hygiene"), entry("30", "minimum-iron"), entry("20", "cottons"), entry("23", "cottons-hygiene"), entry("30", "minimum-iron"),
entry("31", "gentle-minimum-iron"), entry("40", "woollens-handcare"), entry("50", "delicates"), 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-eco"), entry("60", "warm-air"), entry("70", "cool-air"), entry("80", "express"), entry("90", "cottons-eco"),
@ -173,7 +173,7 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
entry("190", "standard-pillows"), entry("220", "basket-programme"), entry("240", "smoothing"), entry("190", "standard-pillows"), entry("220", "basket-programme"), entry("240", "smoothing"),
entry("65000", "cottons-auto-load-control"), entry("65001", "minimum-iron-auto-load-control")); entry("65000", "cottons-auto-load-control"), entry("65001", "minimum-iron-auto-load-control"));
private static final 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("2", "drying"), entry("3", "drying-machine-iron"), entry("4", "drying-hand-iron"),
entry("5", "drying-normal"), entry("6", "drying-normal-plus"), entry("7", "cooling-down"), entry("5", "drying-normal"), entry("6", "drying-normal-plus"), entry("7", "cooling-down"),
entry("8", "drying-hand-iron"), entry("10", "finished")); entry("8", "drying-hand-iron"), entry("10", "finished"));
@ -217,12 +217,13 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -249,6 +250,6 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
} }

View File

@ -15,6 +15,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.APPLIANCE_ID;
import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_TUMBLE_DRYER; import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_TUMBLE_DRYER;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -36,6 +38,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
**/ **/
@NonNullByDefault
public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannelSelector> { public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannelSelector> {
private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class); private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
@ -50,26 +53,33 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
TumbleDryerChannelSelector selector = (TumbleDryerChannelSelector) getValueSelectorFromChannelID(channelID); TumbleDryerChannelSelector selector = (TumbleDryerChannelSelector) getValueSelectorFromChannelID(channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { switch (selector) {
switch (selector) { case SWITCH: {
case SWITCH: { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (command.equals(OnOffType.ON)) { if (bridgeHandler == null) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); logger.warn("Command '{}' failed, missing bridge handler", command);
} else if (command.equals(OnOffType.OFF)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
} }
default: { if (command.equals(OnOffType.ON)) {
if (!(command instanceof RefreshType)) { result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
logger.debug("{} is a read-only channel that does not accept commands", } else if (command.equals(OnOffType.OFF)) {
selector.getChannelID()); result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
} }
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -81,6 +91,14 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }
} }

View File

@ -21,9 +21,11 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.openhab.binding.miele.internal.DeviceMetaData; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.miele.internal.DeviceUtil; import org.openhab.binding.miele.internal.DeviceUtil;
import org.openhab.binding.miele.internal.MieleTranslationProvider; import org.openhab.binding.miele.internal.MieleTranslationProvider;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -43,49 +45,42 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Kai Kreuzer - Changed START_TIME to DateTimeType
* @author Jacob Laursen - Added power/water consumption channels, UoM for temperatures, raw channels * @author Jacob Laursen - Added power/water consumption channels, UoM for temperatures, raw channels
*/ */
@NonNullByDefault
public enum WashingMachineChannelSelector implements ApplianceChannelSelector { public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false), PRODUCT_TYPE("productTypeId", "productType", StringType.class, true, false),
DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true, false),
STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) { STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); @Nullable MieleTranslationProvider translationProvider) {
if (state != null) { return DeviceUtil.getStateTextState(s, dmd, translationProvider);
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
STATE(null, STATE_CHANNEL_ID, DecimalType.class, false, false), STATE("", STATE_CHANNEL_ID, DecimalType.class, false, false),
PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) { PROGRAM_TEXT(PROGRAM_ID_PROPERTY_NAME, PROGRAM_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_TEXT_PREFIX,
MIELE_WASHING_MACHINE_TEXT_PREFIX); MIELE_WASHING_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM(null, PROGRAM_CHANNEL_ID, DecimalType.class, false, false), PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false, false),
PROGRAMTYPE("programType", "type", StringType.class, false, false), PROGRAMTYPE("programType", "type", StringType.class, false, false),
PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) { PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, @Nullable MieleTranslationProvider translationProvider) {
return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_TEXT_PREFIX,
MIELE_WASHING_MACHINE_TEXT_PREFIX); MIELE_WASHING_MACHINE_TEXT_PREFIX);
if (state != null) {
return state;
}
return super.getState(s, dmd, translationProvider);
} }
}, },
PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false), PROGRAM_PHASE(RAW_PHASE_PROPERTY_NAME, PHASE_CHANNEL_ID, DecimalType.class, false, false),
START_TIME("startTime", "start", DateTimeType.class, false, false) { START_TIME("startTime", "start", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -99,7 +94,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}, },
DURATION("duration", "duration", DateTimeType.class, false, false) { DURATION("duration", "duration", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -113,7 +109,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}, },
ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) { ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -127,7 +124,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}, },
FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) { FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
Date date = new Date(); Date date = new Date();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
@ -141,13 +139,15 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}, },
TARGET_TEMP("targetTemperature", "target", QuantityType.class, false, false) { TARGET_TEMP("targetTemperature", "target", QuantityType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return getTemperatureState(s); return getTemperatureState(s);
} }
}, },
SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false, false) { SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false, false) {
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
if ("0".equals(s)) { if ("0".equals(s)) {
return getState("Without spinning"); return getState("Without spinning");
} }
@ -159,8 +159,8 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
}, },
DOOR("signalDoor", "door", OpenClosedType.class, false, false) { DOOR("signalDoor", "door", OpenClosedType.class, false, false) {
@Override @Override
public State getState(String s, @Nullable DeviceMetaData dmd,
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { @Nullable MieleTranslationProvider translationProvider) {
if ("true".equals(s)) { if ("true".equals(s)) {
return getState("OPEN"); return getState("OPEN");
} }
@ -172,7 +172,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
return UnDefType.UNDEF; return UnDefType.UNDEF;
} }
}, },
SWITCH(null, "switch", OnOffType.class, false, false), SWITCH("", "switch", OnOffType.class, false, false),
POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, POWER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, POWER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
true), true),
WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false,
@ -180,7 +180,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class); private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
private static final 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("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("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("24", "denim"), entry("27", "proofing"), entry("29", "sportswear"), entry("31", "automatic-plus"),
@ -189,7 +189,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
entry("95", "down-duvets"), entry("122", "express-20"), entry("129", "down-filled-items"), entry("95", "down-duvets"), entry("122", "express-20"), entry("129", "down-filled-items"),
entry("133", "cottons-eco"), entry("146", "quickpowerwash"), entry("65532", "mix")); entry("133", "cottons-eco"), entry("146", "quickpowerwash"), entry("65532", "mix"));
private static final 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("5", "rinses"), entry("7", "clean"), entry("9", "drain"), entry("10", "spin"),
entry("11", "anti-crease"), entry("12", "finished")); entry("11", "anti-crease"), entry("12", "finished"));
@ -234,12 +234,13 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { public State getState(String s, @Nullable DeviceMetaData dmd,
@Nullable MieleTranslationProvider translationProvider) {
return this.getState(s, dmd); return this.getState(s, dmd);
} }
@Override @Override
public State getState(String s, DeviceMetaData dmd) { public State getState(String s, @Nullable DeviceMetaData dmd) {
if (dmd != null) { if (dmd != null) {
String localizedValue = dmd.getMieleEnum(s); String localizedValue = dmd.getMieleEnum(s);
if (localizedValue == null) { if (localizedValue == null) {
@ -266,7 +267,7 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
logger.error("An exception occurred while converting '{}' into a State", s); logger.error("An exception occurred while converting '{}' into a State", s);
} }
return null; return UnDefType.UNDEF;
} }
public State getTemperatureState(String s) { public State getTemperatureState(String s) {

View File

@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
import java.math.BigDecimal; import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -42,6 +44,7 @@ import com.google.gson.JsonElement;
* @author Martin Lepsy - fixed handling of empty JSON results * @author Martin Lepsy - fixed handling of empty JSON results
* @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels
**/ **/
@NonNullByDefault
public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector> public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineChannelSelector>
implements ExtendedDeviceStateListener { implements ExtendedDeviceStateListener {
@ -62,27 +65,34 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
String channelID = channelUID.getId(); String channelID = channelUID.getId();
String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null) {
logger.warn("Command '{}' failed, appliance id is unknown", command);
return;
}
WashingMachineChannelSelector selector = (WashingMachineChannelSelector) getValueSelectorFromChannelID( WashingMachineChannelSelector selector = (WashingMachineChannelSelector) getValueSelectorFromChannelID(
channelID); channelID);
JsonElement result = null; JsonElement result = null;
try { try {
if (selector != null) { switch (selector) {
switch (selector) { case SWITCH: {
case SWITCH: { MieleBridgeHandler bridgeHandler = this.bridgeHandler;
if (command.equals(OnOffType.ON)) { if (bridgeHandler == null) {
result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); logger.warn("Command '{}' failed, missing bridge handler", command);
} else if (command.equals(OnOffType.OFF)) { return;
result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
}
break;
} }
default: { if (command.equals(OnOffType.ON)) {
if (!(command instanceof RefreshType)) { result = bridgeHandler.invokeOperation(applianceId, modelID, "start");
logger.debug("{} is a read-only channel that does not accept commands", } else if (command.equals(OnOffType.OFF)) {
selector.getChannelID()); result = bridgeHandler.invokeOperation(applianceId, modelID, "stop");
} }
break;
}
default: {
if (!(command instanceof RefreshType)) {
logger.debug("{} is a read-only channel that does not accept commands",
selector.getChannelID());
} }
} }
} }
@ -94,6 +104,14 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
logger.warn( logger.warn(
"An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
channelID, command.toString()); channelID, command.toString());
} catch (MieleRpcException e) {
Throwable cause = e.getCause();
if (cause == null) {
logger.warn("An error occurred while trying to invoke operation: {}", e.getMessage());
} else {
logger.warn("An error occurred while trying to invoke operation: {} -> {}", e.getMessage(),
cause.getMessage());
}
} }
} }

View File

@ -13,10 +13,15 @@
package org.openhab.binding.miele.internal; package org.openhab.binding.miele.internal;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.binding.miele.internal.api.dto.DeviceMetaData;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.test.java.JavaTest; import org.openhab.core.test.java.JavaTest;
@ -28,9 +33,12 @@ import org.openhab.core.types.UnDefType;
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
public class DeviceUtilTest extends JavaTest { public class DeviceUtilTest extends JavaTest {
private @NonNullByDefault({}) @Mock MieleTranslationProvider translationProvider;
@Test @Test
public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() { public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() {
String actual = DeviceUtil.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 });
@ -70,11 +78,6 @@ public class DeviceUtilTest extends JavaTest {
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A")); assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A"));
} }
@Test
public void getTemperatureStateNullValueThrowsNumberFormatException() {
assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState(null));
}
@Test @Test
public void getStateTextStateProviderHasPrecedence() { public void getStateTextStateProviderHasPrecedence() {
assertEquals("I brug", this.getStateTextState("5", "Running", "miele.state.running", "I brug")); assertEquals("I brug", this.getStateTextState("5", "Running", "miele.state.running", "I brug"));
@ -82,19 +85,22 @@ public class DeviceUtilTest extends JavaTest {
@Test @Test
public void getStateTextStateGatewayTextIsReturnedWhenKeyIsUnknown() { public void getStateTextStateGatewayTextIsReturnedWhenKeyIsUnknown() {
assertEquals("Running", this.getStateTextState("-1", "Running", "key.unknown", "I brug")); assertEquals("Running", this.getStateTextState("-1", "Running"));
} }
@Test @Test
public void getStateTextStateKeyIsReturnedWhenUnknownByGatewayAndProvider() { public void getStateTextStateKeyIsReturnedWhenUnknownByGatewayAndProvider() {
assertEquals("state.99", this.getStateTextState("99", null, "key.unknown", "I brug")); assertEquals("state.99", this.getStateTextState("99", null));
} }
private String getStateTextState(String value, String localizedValue, String mockedKey, String mockedValue) { private String getStateTextState(String value, String localizedValue, String mockedKey, String mockedValue) {
when(translationProvider.getText(mockedKey, localizedValue)).thenReturn(mockedValue);
return getStateTextState(value, localizedValue);
}
private String getStateTextState(String value, @Nullable String localizedValue) {
var metaData = new DeviceMetaData(); var metaData = new DeviceMetaData();
metaData.LocalizedValue = localizedValue; metaData.LocalizedValue = localizedValue;
var translationProvider = mock(MieleTranslationProvider.class);
when(translationProvider.getText(mockedKey, metaData.LocalizedValue)).thenReturn(mockedValue);
return DeviceUtil.getStateTextState(value, metaData, translationProvider).toString(); return DeviceUtil.getStateTextState(value, metaData, translationProvider).toString();
} }

View File

@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openhab.core.test.java.JavaTest; import org.openhab.core.test.java.JavaTest;
@ -23,6 +24,7 @@ import org.openhab.core.test.java.JavaTest;
* *
* @author Jacob Laursen - Initial contribution * @author Jacob Laursen - Initial contribution
*/ */
@NonNullByDefault
public class FullyQualifiedApplianceIdentifierTest extends JavaTest { public class FullyQualifiedApplianceIdentifierTest extends JavaTest {
@Test @Test