diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceUtil.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceUtil.java index 0fc9a514ef2f1..311ac6088e2a9 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceUtil.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceUtil.java @@ -17,6 +17,9 @@ import java.nio.charset.StandardCharsets; 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.StringType; import org.openhab.core.library.unit.SIUnits; @@ -29,13 +32,14 @@ * * @author Jacob Laursen - Initial contribution */ +@NonNullByDefault public class DeviceUtil { private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); private static final String TEMPERATURE_UNDEFINED = "32768"; private static final String TEMPERATURE_COLD = "-32760"; private static final String TEXT_PREFIX = "miele."; - private static final Map states = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"), + private static final Map 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("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"), @@ -84,8 +88,9 @@ public static State getTemperatureState(String s) throws NumberFormatException { * Get state text for provided string taking into consideration {@link DeviceMetaData} * as well as built-in/translated strings. */ - public static State getStateTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - return getTextState(s, dmd, translationProvider, states, MISSING_STATE_TEXT_PREFIX, ""); + public static State getStateTextState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return getTextState(s, dmd, translationProvider, STATES, MISSING_STATE_TEXT_PREFIX, ""); } /** @@ -100,8 +105,9 @@ public static State getStateTextState(String s, DeviceMetaData dmd, MieleTransla * @param appliancePrefix Appliance prefix appended to text key (including dot) * @return Text string as State */ - public static State getTextState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider, - Map valueMap, String propertyPrefix, String appliancePrefix) { + public static State getTextState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider, Map valueMap, String propertyPrefix, + String appliancePrefix) { if ("0".equals(s)) { return UnDefType.UNDEF; } @@ -116,7 +122,7 @@ public static State getTextState(String s, DeviceMetaData dmd, MieleTranslationP } String value = valueMap.get(s); - if (value != null) { + if (value != null && translationProvider != null) { String key = TEXT_PREFIX + propertyPrefix + appliancePrefix + value; return new StringType( translationProvider.getText(key, gatewayText != null ? gatewayText : propertyPrefix + s)); diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifier.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifier.java index bed883699468a..98d3865be5dcb 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifier.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifier.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.miele.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link FullyQualifiedApplianceIdentifier} class represents a fully qualified appliance identifier. * Example: "hdm:ZigBee:0123456789abcdef#210" * * @author Jacob Laursen - Initial contribution */ +@NonNullByDefault public class FullyQualifiedApplianceIdentifier { private String uid; private String protocol; @@ -56,7 +59,7 @@ public String getId() { } /** - * @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() { return this.protocol; diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java index e624be46516f0..487cb9a8b3665 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java @@ -28,6 +28,7 @@ public class MieleBindingConstants { public static final String BINDING_ID = "miele"; public static final String APPLIANCE_ID = "uid"; + public static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele"; // Properties public static final String PROPERTY_DEVICE_CLASS = "deviceClass"; diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleHandlerFactory.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleHandlerFactory.java index 3fa74e738e7b4..c802398866cb2 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleHandlerFactory.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleHandlerFactory.java @@ -21,6 +21,8 @@ import java.util.stream.Collectors; 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.handler.CoffeeMachineHandler; import org.openhab.binding.miele.internal.handler.DishWasherHandler; @@ -56,6 +58,7 @@ * * @author Karel Goderis - Initial contribution */ +@NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele") public class MieleHandlerFactory extends BaseThingHandlerFactory { @@ -82,8 +85,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } @Override - public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID, - ThingUID bridgeUID) { + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { ThingUID mieleBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration); return super.createThing(thingTypeUID, configuration, mieleBridgeUID, null); @@ -97,7 +100,7 @@ public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, } @Override - protected ThingHandler createHandler(Thing thing) { + protected @Nullable ThingHandler createHandler(Thing thing) { if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing); registerApplianceDiscoveryService(handler); @@ -135,7 +138,8 @@ protected ThingHandler createHandler(Thing thing) { return null; } - private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) { + private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, + Configuration configuration) { if (thingUID == null) { String hostID = (String) configuration.get(HOST); thingUID = new ThingUID(thingTypeUID, hostID); @@ -143,12 +147,16 @@ private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, return thingUID; } - private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration, - ThingUID bridgeUID) { + private ThingUID getApplianceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, + Configuration configuration, @Nullable ThingUID bridgeUID) { String applianceId = (String) configuration.get(APPLIANCE_ID); 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; } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceClassObject.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceClassObject.java new file mode 100644 index 0000000000000..a802cc23a7276 --- /dev/null +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceClassObject.java @@ -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() { + } +} diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceMetaData.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceMetaData.java similarity index 95% rename from bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceMetaData.java rename to bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceMetaData.java index 77c1a46e5001c..c7063151cb0dc 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceMetaData.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceMetaData.java @@ -10,7 +10,7 @@ * * 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; diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceProperty.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceProperty.java new file mode 100644 index 0000000000000..dac506075040f --- /dev/null +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceProperty.java @@ -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() { + } +} diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/HomeDevice.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/HomeDevice.java new file mode 100644 index 0000000000000..aa9ba7f55a183 --- /dev/null +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/HomeDevice.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/discovery/MieleApplianceDiscoveryService.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/discovery/MieleApplianceDiscoveryService.java index f1a22d63de264..cf9bd30cfe921 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/discovery/MieleApplianceDiscoveryService.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/discovery/MieleApplianceDiscoveryService.java @@ -20,13 +20,15 @@ import java.util.Map; 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.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.MieleApplianceHandler; 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.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -44,6 +46,7 @@ * @author Martin Lepsy - Added protocol information in order so support WiFi devices * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public class MieleApplianceDiscoveryService extends AbstractDiscoveryService implements ApplianceStatusListener { private final Logger logger = LoggerFactory.getLogger(MieleApplianceDiscoveryService.class); @@ -75,10 +78,8 @@ public Set getSupportedThingTypes() { @Override public void startScan() { List appliances = mieleBridgeHandler.getHomeDevices(); - if (appliances != null) { - for (HomeDevice l : appliances) { - onApplianceAddedInternal(l); - } + for (HomeDevice l : appliances) { + onApplianceAddedInternal(l); } } @@ -100,11 +101,17 @@ private void onApplianceAddedInternal(HomeDevice appliance) { Map properties = new HashMap<>(9); 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_SERIAL_NUMBER, appliance.getSerialNumber()); 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()); String deviceClass = appliance.getDeviceClass(); if (deviceClass != null) { @@ -149,7 +156,7 @@ public void onAppliancePropertyChanged(FullyQualifiedApplianceIdentifier applian // nothing to do } - private ThingUID getThingUID(HomeDevice appliance) { + private @Nullable ThingUID getThingUID(HomeDevice appliance) { ThingUID bridgeUID = mieleBridgeHandler.getThing().getUID(); String modelId = appliance.getDeviceClass(); diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/exceptions/MieleRpcException.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/exceptions/MieleRpcException.java new file mode 100644 index 0000000000000..f443af3808c5f --- /dev/null +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/exceptions/MieleRpcException.java @@ -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); + } +} diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java index 845f6422dabbc..0bbed76aabacb 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java @@ -12,8 +12,10 @@ */ 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.api.dto.DeviceMetaData; import org.openhab.core.types.State; /** @@ -25,6 +27,7 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Added power/water consumption channels */ +@NonNullByDefault public interface ApplianceChannelSelector { @Override @@ -61,7 +64,7 @@ public interface ApplianceChannelSelector { * @param dmd - the device meta data * @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 @@ -71,7 +74,7 @@ public interface ApplianceChannelSelector { * @param s - the value to be used to instantiate the State * @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 diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceStatusListener.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceStatusListener.java index 8c23bbde101a6..7b81fcb38351f 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceStatusListener.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceStatusListener.java @@ -12,10 +12,11 @@ */ 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.handler.MieleBridgeHandler.DeviceClassObject; -import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.DeviceProperty; -import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice; +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; /** * @@ -25,6 +26,7 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public interface ApplianceStatusListener { /** diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java index 3b1e2bd312b71..b65b6a1c3fbc4 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java @@ -15,12 +15,13 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*; import java.lang.reflect.Method; -import java.util.Collections; 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; @@ -37,50 +38,43 @@ * @author Stephan Esch - Initial contribution * @author Jacob Laursen - Added raw channels */ +@NonNullByDefault public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_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), PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_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), // lightingStatus signalFailure signalInfo DOOR("signalDoor", "door", OpenClosedType.class, false) { @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)) { return getState("OPEN"); } @@ -92,13 +86,13 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - SWITCH(null, "switch", OnOffType.class, false); + SWITCH("", "switch", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(CoffeeMachineChannelSelector.class); - private static final Map programs = Collections. emptyMap(); + private static final Map PROGRAMS = Map.of(); - private static final Map phases = Collections. emptyMap(); + private static final Map PHASES = Map.of(); private final String mieleID; private final String channelID; @@ -139,12 +133,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -171,6 +166,6 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineHandler.java index 3e7d128c154a9..4aa51acb588af 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineHandler.java @@ -15,6 +15,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID; 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -35,6 +37,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public class CoffeeMachineHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class); @@ -50,26 +53,33 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SWITCH: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff"); - } - break; + switch (selector) { + case SWITCH: { + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff"); } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + 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 void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java index 844d797e0e435..c5933f88ff49e 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java @@ -19,6 +19,8 @@ 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -42,6 +44,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels */ +@NonNullByDefault public class DishWasherHandler extends MieleApplianceHandler implements ExtendedDeviceStateListener { @@ -61,26 +64,33 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SWITCH: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); - } - break; + switch (selector) { + case SWITCH: { + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); + } else if (command.equals(OnOffType.OFF)) { + 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 void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java index bde826e8e95e5..6f1f61b6ebe8d 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java @@ -21,9 +21,11 @@ import java.util.Map; 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -43,48 +45,41 @@ * @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Jacob Laursen - Added power/water consumption channels, raw channels */ +@NonNullByDefault public enum DishwasherChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", 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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_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), START_TIME("startTime", "start", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -98,7 +93,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DURATION("duration", "duration", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -112,7 +108,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -126,7 +123,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -140,7 +138,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DOOR("signalDoor", "door", OpenClosedType.class, false, false) { @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)) { return getState("OPEN"); } @@ -152,7 +151,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra 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, true), WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, @@ -160,12 +159,12 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class); - private static final Map programs = Map.ofEntries(entry("26", "intensive"), + private static final Map PROGRAMS = Map.ofEntries(entry("26", "intensive"), 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("38", "quickpowerwash"), entry("42", "tall-items")); - private static final Map phases = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"), + private static final Map PHASES = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"), entry("4", "rinses"), entry("6", "final-rinse"), entry("7", "drying"), entry("8", "finished")); private final String mieleID; @@ -209,12 +208,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -241,6 +241,6 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java index fb8e08d42a42a..1567a13d4db5b 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.miele.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Appliance handlers can implement the {@link ExtendedDeviceStateListener} interface * to extract additional information from the ExtendedDeviceState property. * * @author Jacob Laursen - Initial contribution */ +@NonNullByDefault public interface ExtendedDeviceStateListener { void onApplianceExtendedStateChanged(byte[] extendedDeviceState); } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java index ea692af2abed9..46b4608404237 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java @@ -16,9 +16,11 @@ 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; @@ -36,38 +38,39 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Added UoM for temperatures, raw channels */ +@NonNullByDefault public enum FridgeChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(s, dmd, translationProvider); } }, - STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), - SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false), + STATE("", STATE_CHANNEL_ID, DecimalType.class, false), + SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false), FRIDGECURRENTTEMP("currentTemperature", "current", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, FRIDGETARGETTEMP("targetTemperature", "target", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, DOOR("signalDoor", "door", OpenClosedType.class, false) { @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)) { return getState("OPEN"); } @@ -79,7 +82,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - START(null, "start", OnOffType.class, false); + START("", "start", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(FridgeChannelSelector.class); @@ -121,12 +124,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -153,7 +157,7 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } public State getTemperatureState(String s) { diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java index ab4a741be56c5..a08ef29aa8b38 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java @@ -16,9 +16,11 @@ 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; @@ -37,53 +39,55 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Added UoM for temperatures, raw channels */ +@NonNullByDefault public enum FridgeFreezerChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(s, dmd, translationProvider); } }, - STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), + STATE("", STATE_CHANNEL_ID, DecimalType.class, false), FREEZERSTATE("freezerState", "freezerstate", StringType.class, false), FRIDGESTATE("fridgeState", "fridgestate", StringType.class, false), - SUPERCOOL(null, SUPERCOOL_CHANNEL_ID, OnOffType.class, false), - SUPERFREEZE(null, SUPERFREEZE_CHANNEL_ID, OnOffType.class, false), + SUPERCOOL("", SUPERCOOL_CHANNEL_ID, OnOffType.class, false), + SUPERFREEZE("", SUPERFREEZE_CHANNEL_ID, OnOffType.class, false), FREEZERCURRENTTEMP("freezerCurrentTemperature", "freezercurrent", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, FREEZERTARGETTEMP("freezerTargetTemperature", "freezertarget", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, FRIDGECURRENTTEMP("fridgeCurrentTemperature", "fridgecurrent", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, FRIDGETARGETTEMP("fridgeTargetTemperature", "fridgetarget", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, DOOR("signalDoor", "door", OpenClosedType.class, false) { @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)) { return getState("OPEN"); } @@ -95,7 +99,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - START(null, "start", OnOffType.class, false); + START("", "start", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(FridgeFreezerChannelSelector.class); @@ -138,12 +142,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -170,7 +175,7 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } public State getTemperatureState(String s) { diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerHandler.java index 89e55618e390c..3bfcd07974575 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerHandler.java @@ -14,7 +14,9 @@ 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -35,6 +37,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public class FridgeFreezerHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class); @@ -50,33 +53,39 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SUPERCOOL: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); - } - break; - } - case SUPERFREEZE: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing"); - } - break; + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + switch (selector) { + case SUPERCOOL: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); } - default: { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); + break; + } + case SUPERFREEZE: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperFreezing"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperFreezing"); } + break; + } + default: { + logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID()); } } // process result @@ -87,6 +96,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeHandler.java index 08a759a33d662..da8dea107a46b 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeHandler.java @@ -14,7 +14,9 @@ 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -36,6 +38,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public class FridgeHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class); @@ -50,32 +53,39 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SUPERCOOL: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); - } - break; + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + switch (selector) { + case SUPERCOOL: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "startSuperCooling"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stopSuperCooling"); } - case START: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); - } - break; + break; + } + case START: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + break; + } + default: { + if (!(command instanceof RefreshType)) { + logger.debug("{} is a read-only channel that does not accept commands", + selector.getChannelID()); } } } @@ -87,6 +97,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java index a03f7e638205a..4059931fc0d43 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java @@ -16,13 +16,16 @@ 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.State; import org.openhab.core.types.Type; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,26 +35,25 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Added raw channels */ +@NonNullByDefault public enum HobChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(s, dmd, translationProvider); } }, - STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), + STATE("", STATE_CHANNEL_ID, DecimalType.class, false), PLATES("plateNumbers", "plates", DecimalType.class, true), PLATE1_POWER("plate1PowerStep", "plate1power", DecimalType.class, false), PLATE1_HEAT("plate1RemainingHeat", "plate1heat", DecimalType.class, false) { @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 // converted into a DecimalType. We therefore ignore the metadata and return the device property value as a // State @@ -62,7 +64,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra PLATE2_POWER("plate2PowerStep", "plate2power", DecimalType.class, false), PLATE2_HEAT("plate2RemainingHeat", "plate2heat", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, @@ -70,7 +73,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra PLATE3_POWER("plate3PowerStep", "plate3power", DecimalType.class, false), PLATE3_HEAT("plate3RemainingHeat", "plate3heat", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, @@ -78,7 +82,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra PLATE4_POWER("plate4PowerStep", "plate4power", DecimalType.class, false), PLATE4_HEAT("plate4RemainingHeat", "plate4heat", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, @@ -86,7 +91,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra PLATE5_POWER("plate5PowerStep", "plate5power", DecimalType.class, false), PLATE5_HEAT("plate5RemainingHeat", "plate5heat", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, @@ -94,7 +100,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra PLATE6_POWER("plate6PowerStep", "plate6power", DecimalType.class, false), PLATE6_HEAT("plate6RemainingHeat", "plate6heat", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, @@ -140,12 +147,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -172,6 +180,6 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobHandler.java index 816ee5265ffac..86e62104759dd 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobHandler.java @@ -14,6 +14,7 @@ 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.TranslationProvider; import org.openhab.core.thing.ChannelUID; @@ -27,6 +28,7 @@ * @author Karel Goderis - Initial contribution * @author Kai Kreuzer - fixed handling of REFRESH commands */ +@NonNullByDefault public class HobHandler extends MieleApplianceHandler { public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) { diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java index 4b2db76aeb8fa..f84eebc91941d 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java @@ -16,9 +16,11 @@ 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; @@ -34,26 +36,25 @@ * @author Karel Goderis - Initial contribution * @author Jacob Laursen - Added raw channels */ +@NonNullByDefault public enum HoodChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(s, dmd, translationProvider); } }, - STATE(null, STATE_CHANNEL_ID, DecimalType.class, false), + STATE("", STATE_CHANNEL_ID, DecimalType.class, false), VENTILATION("ventilationPower", "ventilation", DecimalType.class, false), LIGHT("lightingStatus", "light", OnOffType.class, false) { @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)) { return getState("ON"); } @@ -65,7 +66,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - STOP(null, "stop", OnOffType.class, false); + STOP("", "stop", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(HoodChannelSelector.class); @@ -107,12 +108,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -139,6 +141,6 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodHandler.java index 8de0614c47036..83cb84ec7fafc 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodHandler.java @@ -15,6 +15,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID; 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -35,6 +37,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) **/ +@NonNullByDefault public class HoodHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(HoodHandler.class); @@ -49,31 +52,37 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case LIGHT: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting"); - } - break; - } - case STOP: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); - } - break; + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + switch (selector) { + case LIGHT: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "startLighting"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stopLighting"); } - default: { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); + break; + } + case STOP: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); } + break; + } + default: { + logger.debug("{} is a read-only channel that does not accept commands", selector.getChannelID()); } } // process result @@ -84,6 +93,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java index fa655398341dd..85aac472c9770 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java @@ -19,17 +19,16 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.jdt.annotation.NonNull; -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.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.MieleTranslationProvider; -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.binding.miele.internal.api.dto.DeviceClassObject; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; +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.TranslationProvider; import org.openhab.core.thing.Bridge; @@ -43,11 +42,11 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -62,23 +61,23 @@ * @author Martin Lepsy - Added check for JsonNull result * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public abstract class MieleApplianceHandler & ApplianceChannelSelector> extends BaseThingHandler implements ApplianceStatusListener { private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class); - public static final Set SUPPORTED_THING_TYPES = Stream - .of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB, - THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE) - .collect(Collectors.toSet()); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DISHWASHER, THING_TYPE_OVEN, + THING_TYPE_FRIDGE, THING_TYPE_DRYER, THING_TYPE_HOB, THING_TYPE_FRIDGEFREEZER, THING_TYPE_HOOD, + THING_TYPE_WASHINGMACHINE, THING_TYPE_COFFEEMACHINE); protected Gson gson = new Gson(); - protected String applianceId; - protected MieleBridgeHandler bridgeHandler; + protected @Nullable String applianceId; + protected @Nullable MieleBridgeHandler bridgeHandler; protected TranslationProvider i18nProvider; protected LocaleProvider localeProvider; - protected MieleTranslationProvider translationProvider; + protected @Nullable MieleTranslationProvider translationProvider; private Class selectorType; protected String modelID; @@ -101,7 +100,7 @@ public ApplianceChannelSelector getValueSelectorFromChannelID(String valueSelect String.format("Could not get enum constants for value selector: %s", valueSelectorText)); } for (ApplianceChannelSelector c : enumConstants) { - if (c != null && c.getChannelID() != null && c.getChannelID().equals(valueSelectorText)) { + if (c.getChannelID().equals(valueSelectorText)) { return c; } } @@ -117,7 +116,7 @@ public ApplianceChannelSelector getValueSelectorFromMieleID(String valueSelector String.format("Could not get enum constants for value selector: %s", valueSelectorText)); } for (ApplianceChannelSelector c : enumConstants) { - if (c != null && c.getMieleID() != null && c.getMieleID().equals(valueSelectorText)) { + if (!c.getMieleID().isEmpty() && c.getMieleID().equals(valueSelectorText)) { return c; } } @@ -168,7 +167,7 @@ public void dispose() { if (applianceId != null) { MieleBridgeHandler bridgeHandler = getMieleBridgeHandler(); if (bridgeHandler != null) { - getMieleBridgeHandler().unregisterApplianceStatusListener(this); + bridgeHandler.unregisterApplianceStatusListener(this); } applianceId = null; } @@ -186,12 +185,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicationIdentifier, DeviceClassObject dco) { - String myApplianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); - if (myApplianceId == null || !myApplianceId.equals(applicationIdentifier.getApplianceId())) { + String applianceId = this.applianceId; + if (applianceId == null || !applianceId.equals(applicationIdentifier.getApplianceId())) { return; } - for (JsonElement prop : dco.Properties.getAsJsonArray()) { + JsonArray properties = dco.Properties; + if (properties == null) { + return; + } + + for (JsonElement prop : properties.getAsJsonArray()) { try { DeviceProperty dp = gson.fromJson(prop, DeviceProperty.class); if (dp == null) { @@ -210,9 +214,9 @@ public void onApplianceStateChanged(FullyQualifiedApplianceIdentifier applicatio @Override 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; } @@ -225,8 +229,8 @@ protected void onAppliancePropertyChanged(DeviceProperty dp) { if (dp.Metadata == null) { String metadata = metaDataCache.get(new StringBuilder().append(dp.Name).toString().trim()); if (metadata != null) { - JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata); - dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class); + JsonObject jsonMetadata = (JsonObject) JsonParser.parseString(metadata); + dmd = gson.fromJson(jsonMetadata, DeviceMetaData.class); // only keep the enum, if any - that's all we care for events we receive via multicast // all other fields are nulled if (dmd != null) { @@ -237,8 +241,9 @@ protected void onAppliancePropertyChanged(DeviceProperty dp) { } } } - if (dp.Metadata != null) { - String metadata = dp.Metadata.toString().replace("enum", "MieleEnum"); + JsonObject jsonMetadata = dp.Metadata; + if (jsonMetadata != null) { + String metadata = jsonMetadata.toString().replace("enum", "MieleEnum"); JsonObject jsonMetaData = (JsonObject) JsonParser.parseString(metadata); dmd = gson.fromJson(jsonMetaData, DeviceMetaData.class); metaDataCache.put(new StringBuilder().append(dp.Name).toString().trim(), metadata); @@ -269,19 +274,14 @@ protected void onAppliancePropertyChanged(DeviceProperty dp) { if (!selector.isProperty()) { ChannelUID theChannelUID = new ChannelUID(getThing().getUID(), selector.getChannelID()); - if (dp.Value != null) { - State state = selector.getState(dpValue, dmd, this.translationProvider); - logger.trace("Update state of {} with getState '{}'", theChannelUID, state); - updateState(theChannelUID, state); - updateRawChannel(dp.Name, dpValue); - } else { - updateState(theChannelUID, UnDefType.UNDEF); - } + State state = selector.getState(dpValue, dmd, this.translationProvider); + logger.trace("Update state of {} with getState '{}'", theChannelUID, state); + updateState(theChannelUID, state); + updateRawChannel(dp.Name, dpValue); } else { logger.debug("Updating the property '{}' of '{}' to '{}'", selector.getChannelID(), getThing().getUID(), selector.getState(dpValue, dmd, this.translationProvider).toString()); - @NonNull - Map<@NonNull String, @NonNull String> properties = editProperties(); + Map properties = editProperties(); properties.put(selector.getChannelID(), selector.getState(dpValue, dmd, this.translationProvider).toString()); updateProperties(properties); @@ -330,31 +330,46 @@ private void updateRawChannel(String propertyName, String value) { @Override public void onApplianceRemoved(HomeDevice appliance) { + String applianceId = this.applianceId; if (applianceId == null) { return; } - if (applianceId.equals(appliance.getApplianceIdentifier().getApplianceId())) { + FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier(); + if (applianceIdentifier == null) { + return; + } + + if (applianceId.equals(applianceIdentifier.getApplianceId())) { updateStatus(ThingStatus.OFFLINE); } } @Override public void onApplianceAdded(HomeDevice appliance) { + String applianceId = this.applianceId; if (applianceId == null) { return; } FullyQualifiedApplianceIdentifier applianceIdentifier = appliance.getApplianceIdentifier(); + if (applianceIdentifier == null) { + return; + } if (applianceId.equals(applianceIdentifier.getApplianceId())) { - @NonNull - Map<@NonNull String, @NonNull String> properties = editProperties(); - properties.put(Thing.PROPERTY_VENDOR, appliance.Vendor); + Map properties = editProperties(); + 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_SERIAL_NUMBER, appliance.getSerialNumber()); 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(); if (deviceClass != null) { properties.put(PROPERTY_DEVICE_CLASS, deviceClass); @@ -372,7 +387,7 @@ public void onApplianceAdded(HomeDevice appliance) { } } - private synchronized MieleBridgeHandler getMieleBridgeHandler() { + private synchronized @Nullable MieleBridgeHandler getMieleBridgeHandler() { if (this.bridgeHandler == null) { Bridge bridge = getBridge(); if (bridge == null) { @@ -380,8 +395,9 @@ private synchronized MieleBridgeHandler getMieleBridgeHandler() { } ThingHandler handler = bridge.getHandler(); if (handler instanceof MieleBridgeHandler) { - this.bridgeHandler = (MieleBridgeHandler) handler; - this.bridgeHandler.registerApplianceStatusListener(this); + var bridgeHandler = (MieleBridgeHandler) handler; + this.bridgeHandler = bridgeHandler; + bridgeHandler.registerApplianceStatusListener(this); } else { return null; } @@ -390,9 +406,6 @@ private synchronized MieleBridgeHandler getMieleBridgeHandler() { } protected boolean isResultProcessable(JsonElement result) { - if (result == null) { - throw new IllegalArgumentException("Provided result is null"); - } return !result.isJsonNull(); } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleBridgeHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleBridgeHandler.java index 464a43398dff3..e8a460bbfca7c 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleBridgeHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleBridgeHandler.java @@ -30,7 +30,6 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.IllformedLocaleException; import java.util.Iterator; import java.util.List; @@ -49,8 +48,13 @@ import java.util.regex.Pattern; 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.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.config.core.Configuration; import org.openhab.core.thing.Bridge; @@ -69,6 +73,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; /** @@ -80,12 +85,10 @@ * @author Martin Lepsy - Added protocol information to support WiFi devices & some refactoring for HomeDevice * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) **/ +@NonNullByDefault public class MieleBridgeHandler extends BaseBridgeHandler { - @NonNull - public static final Set<@NonNull ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_XGW3000); - - private static final String MIELE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.Miele"; + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_XGW3000); 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])$"); @@ -102,128 +105,14 @@ public class MieleBridgeHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(MieleBridgeHandler.class); protected List applianceStatusListeners = new CopyOnWriteArrayList<>(); - protected ScheduledFuture pollingJob; - protected ExecutorService executor; - protected Future eventListenerJob; + protected @Nullable ScheduledFuture pollingJob; + protected @Nullable ExecutorService executor; + protected @Nullable Future eventListenerJob; - @NonNull protected Map cachedHomeDevicesByApplianceId = new ConcurrentHashMap(); protected Map cachedHomeDevicesByRemoteUid = new ConcurrentHashMap(); - protected URL url; - protected Map 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() { - } - } + protected @Nullable URL url; public MieleBridgeHandler(Bridge bridge) { super(bridge); @@ -245,9 +134,6 @@ public void initialize() { return; } - // for future usage - no headers to be set for now - headers = new HashMap<>(); - onUpdate(); lastBridgeConnectionState = false; updateStatus(ThingStatus.UNKNOWN); @@ -328,10 +214,8 @@ public void run() { cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd); } - @NonNull - Set<@NonNull Entry> cachedEntries = cachedHomeDevicesByApplianceId.entrySet(); - @NonNull - Iterator<@NonNull Entry> iterator = cachedEntries.iterator(); + Set> cachedEntries = cachedHomeDevicesByApplianceId.entrySet(); + Iterator> iterator = cachedEntries.iterator(); while (iterator.hasNext()) { Entry cachedEntry = iterator.next(); @@ -349,11 +233,13 @@ public void run() { for (Thing appliance : getThing().getThings()) { if (appliance.getStatus() == ThingStatus.ONLINE) { String applianceId = (String) appliance.getConfiguration().getProperties().get(APPLIANCE_ID); - FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId( - applianceId); + FullyQualifiedApplianceIdentifier applianceIdentifier = null; + if (applianceId != null) { + applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId); + } 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); continue; } @@ -363,29 +249,33 @@ public void run() { args[1] = true; JsonElement result = invokeRPC("HDAccess/getDeviceClassObjects", args); - if (result != null) { - for (JsonElement obj : result.getAsJsonArray()) { - try { - DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class); + for (JsonElement obj : result.getAsJsonArray()) { + try { + DeviceClassObject dco = gson.fromJson(obj, DeviceClassObject.class); - // Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl - if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) { - continue; - } + // Skip com.prosyst.mbs.services.zigbee.hdm.deviceclasses.ReportingControl + if (dco == null || !dco.DeviceClass.startsWith(MIELE_CLASS)) { + 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) { - logger.debug("An exception occurred while polling an appliance :'{}'", e.getMessage()); + } catch (MieleRpcException e) { + 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 @@ private boolean isReachable(String ipAddress) { // That's why we do an HTTP access instead // If there is no connection, this line will fail - JsonElement result = invokeRPC("system.listMethods", null); - if (result == null) { - logger.debug("{} is not reachable", ipAddress); - return false; - } - } catch (Exception e) { + invokeRPC("system.listMethods", new Object[0]); + } catch (MieleRpcException e) { + logger.debug("{} is not reachable", ipAddress); return false; } @@ -421,16 +308,24 @@ public List getHomeDevices() { for (JsonElement obj : result.getAsJsonArray()) { 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; } - private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) { + private @Nullable FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId(String applianceId) { HomeDevice homeDevice = this.cachedHomeDevicesByApplianceId.get(applianceId); if (homeDevice == null) { return null; @@ -475,19 +370,17 @@ private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId( logger.debug("Received a multicast event '{}' from '{}:{}'", event, packet.getAddress(), packet.getPort()); - DeviceProperty dp = new DeviceProperty(); - String id = null; - String[] parts = event.split("&"); + String id = null, name = null, value = null; for (String p : parts) { String[] subparts = p.split("="); switch (subparts[0]) { case "property": { - dp.Name = subparts[1]; + name = subparts[1]; break; } case "value": { - dp.Value = subparts[1].strip().trim(); + value = subparts[1].strip().trim(); break; } case "id": { @@ -497,7 +390,7 @@ private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId( } } - if (id == null) { + if (id == null || name == null || value == null) { continue; } @@ -514,8 +407,11 @@ private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId( } applianceIdentifier = device.getApplianceIdentifier(); } + var deviceProperty = new DeviceProperty(); + deviceProperty.Name = name; + deviceProperty.Value = value; for (ApplianceStatusListener listener : applianceStatusListeners) { - listener.onAppliancePropertyChanged(applianceIdentifier, dp); + listener.onAppliancePropertyChanged(applianceIdentifier, deviceProperty); } } catch (SocketTimeoutException e) { try { @@ -549,18 +445,15 @@ private FullyQualifiedApplianceIdentifier getApplianceIdentifierFromApplianceId( } }; - 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) { - logger.debug("The Bridge is offline - operations can not be invoked."); - return null; + throw new MieleRpcException("Bridge is offline, operations can not be invoked"); } FullyQualifiedApplianceIdentifier applianceIdentifier = getApplianceIdentifierFromApplianceId(applianceId); if (applianceIdentifier == null) { - logger.error( - "The appliance with ID '{}' was not found in appliance list from bridge - operations can not be invoked.", - applianceId); - return null; + throw new MieleRpcException("Appliance with ID" + applianceId + + " was not found in appliance list from gateway - operations can not be invoked"); } Object[] args = new Object[4]; @@ -572,65 +465,72 @@ public JsonElement invokeOperation(String applianceId, String modelID, String me return invokeRPC("HDAccess/invokeDCOOperation", args); } - protected JsonElement invokeRPC(String methodName, Object[] args) { - int id = rand.nextInt(Integer.MAX_VALUE); + protected JsonElement invokeRPC(String methodName, Object[] args) throws MieleRpcException { + JsonElement result = null; + URL url = this.url; + if (url == null) { + throw new MieleRpcException("URL is not set"); + } JsonObject req = new JsonObject(); + int id = rand.nextInt(Integer.MAX_VALUE); req.addProperty("jsonrpc", "2.0"); req.addProperty("id", id); req.addProperty("method", methodName); - JsonElement result = null; - JsonArray params = new JsonArray(); - if (args != null) { - for (Object o : args) { - params.add(gson.toJsonTree(o)); - } + for (Object o : args) { + params.add(gson.toJsonTree(o)); } req.add("params", params); String requestData = req.toString(); String responseData = null; try { - responseData = post(url, headers, requestData); - } catch (Exception e) { - logger.debug("An exception occurred while posting data : '{}'", e.getMessage()); - } - - if (responseData != null) { - logger.trace("The request '{}' yields '{}'", requestData, responseData); - JsonObject resp = (JsonObject) JsonParser.parseReader(new StringReader(responseData)); - - result = resp.get("result"); - JsonElement error = resp.get("error"); - - if (error != null && !error.isJsonNull()) { - if (error.isJsonPrimitive()) { - logger.debug("A remote exception occurred: '{}'", error.getAsString()); - } else if (error.isJsonObject()) { - JsonObject o = error.getAsJsonObject(); - Integer code = (o.has("code") ? o.get("code").getAsInt() : null); - String message = (o.has("message") ? o.get("message").getAsString() : null); - String data = (o.has("data") ? (o.get("data") instanceof JsonObject ? o.get("data").toString() - : o.get("data").getAsString()) : null); - logger.debug("A remote exception occurred: '{}':'{}':'{}'", code, message, data); - } else { - logger.debug("An unknown remote exception occurred: '{}'", error.toString()); - } + responseData = post(url, Collections.emptyMap(), requestData); + } catch (IOException e) { + throw new MieleRpcException("Exception occurred while posting data", e); + } + + logger.trace("The request '{}' yields '{}'", requestData, responseData); + JsonObject parsedResponse = null; + try { + parsedResponse = (JsonObject) JsonParser.parseReader(new StringReader(responseData)); + } catch (JsonParseException e) { + throw new MieleRpcException("Error parsing JSON response", e); + } + + JsonElement error = parsedResponse.get("error"); + if (error != null && !error.isJsonNull()) { + if (error.isJsonPrimitive()) { + throw new MieleRpcException("Remote exception occurred: '" + error.getAsString() + "'"); + } else if (error.isJsonObject()) { + JsonObject o = error.getAsJsonObject(); + Integer code = (o.has("code") ? o.get("code").getAsInt() : null); + String message = (o.has("message") ? o.get("message").getAsString() : null); + String data = (o.has("data") + ? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString()) + : null); + throw new MieleRpcException( + "Remote exception occurred: '" + code + "':'" + message + "':'" + data + "'"); + } else { + 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; } protected String post(URL url, Map headers, String data) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - if (headers != null) { - for (Map.Entry entry : headers.entrySet()) { - connection.addRequestProperty(entry.getKey(), entry.getValue()); - } + for (Map.Entry entry : headers.entrySet()) { + connection.addRequestProperty(entry.getKey(), entry.getValue()); } connection.addRequestProperty("Accept-Encoding", "gzip"); @@ -688,16 +588,21 @@ protected String post(URL url, Map headers, String data) throws private synchronized void onUpdate() { logger.debug("Scheduling the Miele polling job"); + ScheduledFuture pollingJob = this.pollingJob; if (pollingJob == null || pollingJob.isCancelled()) { logger.trace("Scheduling the Miele polling job period is {}", POLLING_PERIOD); 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.debug("Scheduling the Miele event listener job"); + Future eventListenerJob = this.eventListenerJob; if (eventListenerJob == null || eventListenerJob.isCancelled()) { - executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("binding-miele")); - eventListenerJob = executor.submit(eventListenerRunnable); + ExecutorService executor = Executors + .newSingleThreadExecutor(new NamedThreadFactory("binding-" + BINDING_ID)); + this.executor = executor; + this.eventListenerJob = executor.submit(eventListenerRunnable); } } @@ -725,9 +630,6 @@ public void onConnectionResumed() { } 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); if (result && isInitialized()) { onUpdate(); @@ -759,17 +661,20 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void dispose() { super.dispose(); + ScheduledFuture pollingJob = this.pollingJob; if (pollingJob != null) { pollingJob.cancel(true); - pollingJob = null; + this.pollingJob = null; } + Future eventListenerJob = this.eventListenerJob; if (eventListenerJob != null) { eventListenerJob.cancel(true); - eventListenerJob = null; + this.eventListenerJob = null; } + ExecutorService executor = this.executor; if (executor != null) { executor.shutdownNow(); - executor = null; + this.executor = null; } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java index 43e374df356cd..d1d64bfde3c50 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java @@ -21,9 +21,11 @@ import java.util.Map; 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -43,39 +45,35 @@ * @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Jacob Laursen - Added UoM for temperatures, raw channels */ +@NonNullByDefault public enum OvenChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(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(null, PROGRAM_CHANNEL_ID, DecimalType.class, false), + PROGRAM("", PROGRAM_CHANNEL_ID, DecimalType.class, false), PROGRAMTYPE("programType", "type", StringType.class, false), PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_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), START_TIME("startTime", "start", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -89,7 +87,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DURATION("duration", "duration", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -103,7 +102,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -117,7 +117,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, FINISH_TIME("finishTime", "finish", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -131,32 +132,37 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, TARGET_TEMP("targetTemperature", "target", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, MEASURED_TEMP("measuredTemperature", "measured", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, DEVICE_TEMP_ONE("deviceTemperature1", "temp1", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, DEVICE_TEMP_TWO("deviceTemperature2", "temp2", QuantityType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, DOOR("signalDoor", "door", OpenClosedType.class, false) { @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)) { return getState("OPEN"); } @@ -168,12 +174,12 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - STOP(null, "stop", OnOffType.class, false), - SWITCH(null, "switch", OnOffType.class, false); + STOP("", "stop", OnOffType.class, false), + SWITCH("", "switch", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(OvenChannelSelector.class); - private static final Map phases = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"), + private static final Map PHASES = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"), entry("3", "door-open"), entry("4", "pyrolysis"), entry("7", "lighting"), entry("8", "searing-phase"), entry("10", "defrost"), entry("11", "cooling-down"), entry("12", "energy-save-phase")); @@ -215,12 +221,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -247,7 +254,7 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } public State getTemperatureState(String s) { diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenHandler.java index ce0dd87fd2ee4..a1681033a8b37 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenHandler.java @@ -15,6 +15,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID; 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -36,6 +38,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) */ +@NonNullByDefault public class OvenHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(OvenHandler.class); @@ -50,32 +53,39 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SWITCH: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff"); - } - break; + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + switch (selector) { + case SWITCH: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOn"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "switchOff"); } - case STOP: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); - } - break; + break; + } + case STOP: { + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + break; + } + default: { + if (!(command instanceof RefreshType)) { + logger.debug("{} is a read-only channel that does not accept commands", + selector.getChannelID()); } } } @@ -87,6 +97,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java index 0c36481568958..ebb355f30eac4 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java @@ -21,9 +21,11 @@ import java.util.Map; 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -42,49 +44,42 @@ * @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Jacob Laursen - Added raw channels */ +@NonNullByDefault public enum TumbleDryerChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", StringType.class, true), DEVICE_TYPE("mieleDeviceType", "deviceType", StringType.class, true), STATE_TEXT(STATE_PROPERTY_NAME, STATE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_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), PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_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), START_TIME("startTime", "start", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -98,7 +93,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DURATION("duration", "duration", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -112,7 +108,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -126,7 +123,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, FINISH_TIME("finishTime", "finish", DateTimeType.class, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -140,14 +138,16 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DRYING_STEP("dryingStep", "step", DecimalType.class, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getState(s); } }, DOOR("signalDoor", "door", OpenClosedType.class, false) { @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)) { return getState("OPEN"); } @@ -159,11 +159,11 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra return UnDefType.UNDEF; } }, - SWITCH(null, "switch", OnOffType.class, false); + SWITCH("", "switch", OnOffType.class, false); private final Logger logger = LoggerFactory.getLogger(TumbleDryerChannelSelector.class); - private static final Map programs = Map.ofEntries(entry("10", "automatic-plus"), + private static final Map PROGRAMS = Map.ofEntries(entry("10", "automatic-plus"), entry("20", "cottons"), entry("23", "cottons-hygiene"), entry("30", "minimum-iron"), entry("31", "gentle-minimum-iron"), entry("40", "woollens-handcare"), entry("50", "delicates"), entry("60", "warm-air"), entry("70", "cool-air"), entry("80", "express"), entry("90", "cottons-eco"), @@ -173,7 +173,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra entry("190", "standard-pillows"), entry("220", "basket-programme"), entry("240", "smoothing"), entry("65000", "cottons-auto-load-control"), entry("65001", "minimum-iron-auto-load-control")); - private static final Map phases = Map.ofEntries(entry("1", "programme-running"), + private static final Map PHASES = Map.ofEntries(entry("1", "programme-running"), entry("2", "drying"), entry("3", "drying-machine-iron"), entry("4", "drying-hand-iron"), entry("5", "drying-normal"), entry("6", "drying-normal-plus"), entry("7", "cooling-down"), entry("8", "drying-hand-iron"), entry("10", "finished")); @@ -217,12 +217,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -249,6 +250,6 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerHandler.java index 9fa424d727ae1..0c45941432821 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerHandler.java @@ -15,6 +15,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID; 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -36,6 +38,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN) **/ +@NonNullByDefault public class TumbleDryerHandler extends MieleApplianceHandler { private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class); @@ -50,26 +53,33 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SWITCH: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); - } - break; + switch (selector) { + case SWITCH: { + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; + } + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); + } else if (command.equals(OnOffType.OFF)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + 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 void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } } diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java index 4e8da7013ba87..bd22781dcbfe0 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java @@ -21,9 +21,11 @@ import java.util.Map; 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.MieleTranslationProvider; +import org.openhab.binding.miele.internal.api.dto.DeviceMetaData; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -43,49 +45,42 @@ * @author Kai Kreuzer - Changed START_TIME to DateTimeType * @author Jacob Laursen - Added power/water consumption channels, UoM for temperatures, raw channels */ +@NonNullByDefault public enum WashingMachineChannelSelector implements ApplianceChannelSelector { PRODUCT_TYPE("productTypeId", "productType", 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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getStateTextState(s, dmd, translationProvider); - if (state != null) { - return state; - } - return super.getState(s, dmd, translationProvider); + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getStateTextState(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) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, programs, MISSING_PROGRAM_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PROGRAMS, MISSING_PROGRAM_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), PROGRAM_PHASE_TEXT(PHASE_PROPERTY_NAME, PHASE_TEXT_CHANNEL_ID, StringType.class, false, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { - State state = DeviceUtil.getTextState(s, dmd, translationProvider, phases, MISSING_PHASE_TEXT_PREFIX, + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { + return DeviceUtil.getTextState(s, dmd, translationProvider, PHASES, MISSING_PHASE_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), START_TIME("startTime", "start", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -99,7 +94,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DURATION("duration", "duration", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -113,7 +109,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, ELAPSED_TIME("elapsedTime", "elapsed", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -127,7 +124,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, FINISH_TIME("finishTime", "finish", DateTimeType.class, false, false) { @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(); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0")); @@ -141,13 +139,15 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, TARGET_TEMP("targetTemperature", "target", QuantityType.class, false, false) { @Override - public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider translationProvider) { + public State getState(String s, @Nullable DeviceMetaData dmd, + @Nullable MieleTranslationProvider translationProvider) { return getTemperatureState(s); } }, SPINNING_SPEED("spinningSpeed", "spinningspeed", StringType.class, false, false) { @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)) { return getState("Without spinning"); } @@ -159,8 +159,8 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra }, DOOR("signalDoor", "door", OpenClosedType.class, false, false) { @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)) { return getState("OPEN"); } @@ -172,7 +172,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra 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, true), WATER_CONSUMPTION(EXTENDED_DEVICE_STATE_PROPERTY_NAME, WATER_CONSUMPTION_CHANNEL_ID, QuantityType.class, false, @@ -180,7 +180,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class); - private static final Map programs = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"), + private static final Map PROGRAMS = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"), entry("4", "delicates"), entry("8", "woollens"), entry("9", "silks"), entry("17", "starch"), entry("18", "rinse"), entry("21", "drain-spin"), entry("22", "curtains"), entry("23", "shirts"), entry("24", "denim"), entry("27", "proofing"), entry("29", "sportswear"), entry("31", "automatic-plus"), @@ -189,7 +189,7 @@ public State getState(String s, DeviceMetaData dmd, MieleTranslationProvider tra entry("95", "down-duvets"), entry("122", "express-20"), entry("129", "down-filled-items"), entry("133", "cottons-eco"), entry("146", "quickpowerwash"), entry("65532", "mix")); - private static final Map phases = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"), + private static final Map PHASES = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"), entry("5", "rinses"), entry("7", "clean"), entry("9", "drain"), entry("10", "spin"), entry("11", "anti-crease"), entry("12", "finished")); @@ -234,12 +234,13 @@ public boolean isExtendedState() { } @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); } @Override - public State getState(String s, DeviceMetaData dmd) { + public State getState(String s, @Nullable DeviceMetaData dmd) { if (dmd != null) { String localizedValue = dmd.getMieleEnum(s); if (localizedValue == null) { @@ -266,7 +267,7 @@ public State getState(String s) { logger.error("An exception occurred while converting '{}' into a State", s); } - return null; + return UnDefType.UNDEF; } public State getTemperatureState(String s) { diff --git a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java index d90bb3bebd2d3..736a1b6b79100 100644 --- a/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java +++ b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java @@ -19,6 +19,8 @@ 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.TranslationProvider; import org.openhab.core.library.types.OnOffType; @@ -42,6 +44,7 @@ * @author Martin Lepsy - fixed handling of empty JSON results * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN), added power/water consumption channels **/ +@NonNullByDefault public class WashingMachineHandler extends MieleApplianceHandler implements ExtendedDeviceStateListener { @@ -62,27 +65,34 @@ public void handleCommand(ChannelUID channelUID, Command command) { String channelID = channelUID.getId(); 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( channelID); JsonElement result = null; try { - if (selector != null) { - switch (selector) { - case SWITCH: { - if (command.equals(OnOffType.ON)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); - } else if (command.equals(OnOffType.OFF)) { - result = bridgeHandler.invokeOperation(applianceId, modelID, "stop"); - } - break; + switch (selector) { + case SWITCH: { + MieleBridgeHandler bridgeHandler = this.bridgeHandler; + if (bridgeHandler == null) { + logger.warn("Command '{}' failed, missing bridge handler", command); + return; } - default: { - if (!(command instanceof RefreshType)) { - logger.debug("{} is a read-only channel that does not accept commands", - selector.getChannelID()); - } + if (command.equals(OnOffType.ON)) { + result = bridgeHandler.invokeOperation(applianceId, modelID, "start"); + } else if (command.equals(OnOffType.OFF)) { + 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 void handleCommand(ChannelUID channelUID, Command command) { logger.warn( "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", 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()); + } } } diff --git a/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/DeviceUtilTest.java b/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/DeviceUtilTest.java index a009ed38a71ad..03555d49fb0ec 100644 --- a/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/DeviceUtilTest.java +++ b/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/DeviceUtilTest.java @@ -13,10 +13,15 @@ package org.openhab.binding.miele.internal; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; 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.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.unit.SIUnits; import org.openhab.core.test.java.JavaTest; @@ -28,9 +33,12 @@ * * @author Jacob Laursen - Initial contribution */ - +@NonNullByDefault +@ExtendWith(MockitoExtension.class) public class DeviceUtilTest extends JavaTest { + private @NonNullByDefault({}) @Mock MieleTranslationProvider translationProvider; + @Test public void bytesToHexWhenTopBitIsUsedReturnsCorrectString() { String actual = DeviceUtil.bytesToHex(new byte[] { (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef }); @@ -70,11 +78,6 @@ public void getTemperatureStateNonNumericValueThrowsNumberFormatException() { assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState("A")); } - @Test - public void getTemperatureStateNullValueThrowsNumberFormatException() { - assertThrows(NumberFormatException.class, () -> DeviceUtil.getTemperatureState(null)); - } - @Test public void getStateTextStateProviderHasPrecedence() { assertEquals("I brug", this.getStateTextState("5", "Running", "miele.state.running", "I brug")); @@ -82,19 +85,22 @@ public void getStateTextStateProviderHasPrecedence() { @Test public void getStateTextStateGatewayTextIsReturnedWhenKeyIsUnknown() { - assertEquals("Running", this.getStateTextState("-1", "Running", "key.unknown", "I brug")); + assertEquals("Running", this.getStateTextState("-1", "Running")); } @Test 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) { + when(translationProvider.getText(mockedKey, localizedValue)).thenReturn(mockedValue); + return getStateTextState(value, localizedValue); + } + + private String getStateTextState(String value, @Nullable String localizedValue) { var metaData = new DeviceMetaData(); metaData.LocalizedValue = localizedValue; - var translationProvider = mock(MieleTranslationProvider.class); - when(translationProvider.getText(mockedKey, metaData.LocalizedValue)).thenReturn(mockedValue); return DeviceUtil.getStateTextState(value, metaData, translationProvider).toString(); } diff --git a/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifierTest.java b/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifierTest.java index 993935bcba8df..4e28d6aa7415b 100644 --- a/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifierTest.java +++ b/bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifierTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.*; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.core.test.java.JavaTest; @@ -23,6 +24,7 @@ * * @author Jacob Laursen - Initial contribution */ +@NonNullByDefault public class FullyQualifiedApplianceIdentifierTest extends JavaTest { @Test