]> git.basschouten.com Git - openhab-addons.git/commitdiff
[miele] Add null annotations and improve error handling robustness (#12497)
authorJacob Laursen <jacob-github@vindvejr.dk>
Mon, 21 Mar 2022 15:49:03 +0000 (16:49 +0100)
committerGitHub <noreply@github.com>
Mon, 21 Mar 2022 15:49:03 +0000 (16:49 +0100)
* Add null annotations and improve error handling robustness
* Fix compliancy with rule ConstantNameCheck

Fixes #12496

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
36 files changed:
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceMetaData.java [deleted file]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/DeviceUtil.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifier.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleBindingConstants.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/MieleHandlerFactory.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceClassObject.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceMetaData.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceProperty.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/HomeDevice.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/discovery/MieleApplianceDiscoveryService.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/exceptions/MieleRpcException.java [new file with mode: 0644]
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ApplianceStatusListener.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/CoffeeMachineHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishWasherHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/DishwasherChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/ExtendedDeviceStateListener.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeFreezerHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/FridgeHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HobHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/HoodHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleApplianceHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/MieleBridgeHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/OvenHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/TumbleDryerHandler.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineChannelSelector.java
bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/handler/WashingMachineHandler.java
bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/DeviceUtilTest.java
bundles/org.openhab.binding.miele/src/test/java/org/openhab/binding/miele/internal/FullyQualifiedApplianceIdentifierTest.java

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/DeviceMetaData.java
deleted file mode 100644 (file)
index 77c1a46..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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;
-
-import java.util.Map.Entry;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-/**
- * The {@link DeviceMetaData} class represents the Metadata node in the response JSON.
- *
- * @author Jacob Laursen - Initial contribution
- */
-public class DeviceMetaData {
-    public String Filter;
-    public String description;
-    public String LocalizedID;
-    public String LocalizedValue;
-    public JsonObject MieleEnum;
-    public String access;
-
-    public String getMieleEnum(String s) {
-        if (this.MieleEnum == null) {
-            return null;
-        }
-
-        for (Entry<String, JsonElement> enumEntry : this.MieleEnum.entrySet()) {
-            if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
-                return enumEntry.getKey();
-            }
-        }
-
-        return null;
-    }
-}
index 0fc9a514ef2f178940c7d2f7183dcc3fd781b4b2..311ac6088e2a96a5dd3e27036bd7f021d2073ea0 100644 (file)
@@ -17,6 +17,9 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
 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 @@ import org.openhab.core.types.UnDefType;
  *
  * @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<String, String> states = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"),
+    private static final Map<String, String> STATES = Map.ofEntries(Map.entry("1", "off"), Map.entry("2", "stand-by"),
             Map.entry("3", "programmed"), Map.entry("4", "waiting-to-start"), Map.entry("5", "running"),
             Map.entry("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 class DeviceUtil {
      * 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 class DeviceUtil {
      * @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<String, String> valueMap, String propertyPrefix, String appliancePrefix) {
+    public static State getTextState(String s, @Nullable DeviceMetaData dmd,
+            @Nullable MieleTranslationProvider translationProvider, Map<String, String> valueMap, String propertyPrefix,
+            String appliancePrefix) {
         if ("0".equals(s)) {
             return UnDefType.UNDEF;
         }
@@ -116,7 +122,7 @@ public class DeviceUtil {
         }
 
         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));
index bed883699468a58c60baeb789acd1de21371a921..98d3865be5dcb6a4e50e4e9d5ca4942e2807a125 100644 (file)
  */
 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 class FullyQualifiedApplianceIdentifier {
     }
 
     /**
-     * @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:"")
+     * @return Protocol prefix of fully qualified appliance identifier (e.g. "hdmi:ZigBee:")
      */
     public String getProtocol() {
         return this.protocol;
index e624be46516f02e7f043b9e158955473b479fa16..487cb9a8b36658ce89541f80a7200bdfe6d4667e 100644 (file)
@@ -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";
index 3fa74e738e7b4c5af82bf3e8a77c0b7c399ec3aa..c802398866cb2947bb88a74696cb5eb8d1f65215 100644 (file)
@@ -21,6 +21,8 @@ import java.util.Set;
 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 @@ import org.osgi.service.component.annotations.Reference;
  *
  * @author Karel Goderis - Initial contribution
  */
+@NonNullByDefault
 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele")
 public class MieleHandlerFactory extends BaseThingHandlerFactory {
 
@@ -82,8 +85,8 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
     }
 
     @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 class MieleHandlerFactory extends BaseThingHandlerFactory {
     }
 
     @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 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
         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 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
         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 (file)
index 0000000..a802cc2
--- /dev/null
@@ -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/api/dto/DeviceMetaData.java b/bundles/org.openhab.binding.miele/src/main/java/org/openhab/binding/miele/internal/api/dto/DeviceMetaData.java
new file mode 100644 (file)
index 0000000..c706315
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * 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 java.util.Map.Entry;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link DeviceMetaData} class represents the Metadata node in the response JSON.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+public class DeviceMetaData {
+    public String Filter;
+    public String description;
+    public String LocalizedID;
+    public String LocalizedValue;
+    public JsonObject MieleEnum;
+    public String access;
+
+    public String getMieleEnum(String s) {
+        if (this.MieleEnum == null) {
+            return null;
+        }
+
+        for (Entry<String, JsonElement> enumEntry : this.MieleEnum.entrySet()) {
+            if (enumEntry.getValue().getAsString().trim().equals(s.trim())) {
+                return enumEntry.getKey();
+            }
+        }
+
+        return null;
+    }
+}
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 (file)
index 0000000..dac5060
--- /dev/null
@@ -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 (file)
index 0000000..aa9ba7f
--- /dev/null
@@ -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;
+    }
+}
index f1a22d63de2646df812f70847c1fbdc75de7dc25..cf9bd30cfe921ac91a6a87438795a92c5d6337af 100644 (file)
@@ -20,13 +20,15 @@ import java.util.List;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
     @Override
     public void startScan() {
         List<HomeDevice> appliances = mieleBridgeHandler.getHomeDevices();
-        if (appliances != null) {
-            for (HomeDevice l : appliances) {
-                onApplianceAddedInternal(l);
-            }
+        for (HomeDevice l : appliances) {
+            onApplianceAddedInternal(l);
         }
     }
 
@@ -100,11 +101,17 @@ public class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
             Map<String, Object> 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 class MieleApplianceDiscoveryService extends AbstractDiscoveryService imp
         // 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 (file)
index 0000000..f443af3
--- /dev/null
@@ -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);
+    }
+}
index 845f6422dabbce48187b4cf39df7dc6694fa96f3..0bbed76aabacb173eb97ab546ceaad080844d489 100644 (file)
  */
 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 @@ import org.openhab.core.types.State;
  * @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
index 8c23bbde101a6f1a7000a3ce0aefb2184a88caf0..7b81fcb38351f85c43365f56f593357b9cec7388 100644 (file)
  */
 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 @@ import org.openhab.binding.miele.internal.handler.MieleBridgeHandler.HomeDevice;
  * @author Karel Goderis - Initial contribution
  * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
  */
+@NonNullByDefault
 public interface ApplianceStatusListener {
 
     /**
index 3b1e2bd312b71044fd7a8f88295ee77f52df4932..b65b6a1c3fbc489ee305178a236e5db28681199e 100644 (file)
@@ -15,12 +15,13 @@ package org.openhab.binding.miele.internal.handler;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
             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<String, String> programs = Collections.<String, String> emptyMap();
+    private static final Map<String, String> PROGRAMS = Map.of();
 
-    private static final Map<String, String> phases = Collections.<String, String> emptyMap();
+    private static final Map<String, String> PHASES = Map.of();
 
     private final String mieleID;
     private final String channelID;
@@ -139,12 +133,13 @@ public enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum CoffeeMachineChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 }
index 3e7d128c154a9b40d68f8a0bfe02a6d1d3029e7a..4aa51acb588af54d235171dfe0e5efc333631a08 100644 (file)
@@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.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 @@ import com.google.gson.JsonElement;
  * @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<CoffeeMachineChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(CoffeeMachineHandler.class);
@@ -50,26 +53,33 @@ public class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
 
         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 class CoffeeMachineHandler extends MieleApplianceHandler<CoffeeMachineCha
             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());
+            }
         }
     }
 }
index 844d797e0e435d191035b098b5eb3ce1ce1878e4..c5933f88ff49eb68e2c5e0ffdf6297c95a68dda9 100644 (file)
@@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
 
 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 @@ import com.google.gson.JsonElement;
  * @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<DishwasherChannelSelector>
         implements ExtendedDeviceStateListener {
 
@@ -61,26 +64,33 @@ public class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
 
         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 class DishWasherHandler extends MieleApplianceHandler<DishwasherChannelSe
             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());
+            }
         }
     }
 
index bde826e8e95e58de8efbbe288245879e351167d8..6f1f61b6ebe8d4c6b631a0913d54a99e8c4287c2 100644 (file)
@@ -21,9 +21,11 @@ import java.util.Date;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
     },
     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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
     },
     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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
     },
     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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
     },
     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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
             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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
 
     private final Logger logger = LoggerFactory.getLogger(DishwasherChannelSelector.class);
 
-    private static final Map<String, String> programs = Map.ofEntries(entry("26", "intensive"),
+    private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("26", "intensive"),
             entry("27", "maintenance-programme"), entry("28", "eco"), entry("30", "normal"), entry("32", "automatic"),
             entry("34", "solarsave"), entry("35", "gentle"), entry("36", "extra-quiet"), entry("37", "hygiene"),
             entry("38", "quickpowerwash"), entry("42", "tall-items"));
 
-    private static final Map<String, String> phases = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"),
+    private static final Map<String, String> PHASES = Map.ofEntries(entry("2", "pre-wash"), entry("3", "main-wash"),
             entry("4", "rinses"), entry("6", "final-rinse"), entry("7", "drying"), entry("8", "finished"));
 
     private final String mieleID;
@@ -209,12 +208,13 @@ public enum DishwasherChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum DishwasherChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 }
index fb8e08d42a42a9901cf887cf083cf0b06c82e63f..1567a13d4db5bc4ca43f634a564277ed1f5b7873 100644 (file)
  */
 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);
 }
index ea692af2abed98cf8dc4eb7d2ee63edf892e0d3b..46b46084042372278f22b94ec5f19b434e85e6f9 100644 (file)
@@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
 
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum FridgeChannelSelector implements ApplianceChannelSelector {
             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 enum FridgeChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum FridgeChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 
     public State getTemperatureState(String s) {
index ab4a741be56c5e30eeeb1487dcff7988fa45623c..a08ef29aa8b38729ac9a4f55599473e70deac551 100644 (file)
@@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
 
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
             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 enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum FridgeFreezerChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 
     public State getTemperatureState(String s) {
index 89e55618e390c338c9fd4985130f1f7e611cad6d..3bfcd07974575eb92330fe5ede78b09006e9e98a 100644 (file)
@@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
 
 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 @@ import com.google.gson.JsonElement;
  * @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<FridgeFreezerChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(FridgeFreezerHandler.class);
@@ -50,33 +53,39 @@ public class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
 
         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 class FridgeFreezerHandler extends MieleApplianceHandler<FridgeFreezerCha
             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());
+            }
         }
     }
 
index 08a759a33d6628b3c9afe45b7875844752f47365..da8dea107a46b5e504507f9b72425fba425b0580 100644 (file)
@@ -14,7 +14,9 @@ package org.openhab.binding.miele.internal.handler;
 
 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 @@ import com.google.gson.JsonElement;
  * @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<FridgeChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(FridgeHandler.class);
@@ -50,32 +53,39 @@ public class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
 
         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 class FridgeHandler extends MieleApplianceHandler<FridgeChannelSelector>
             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());
+            }
         }
     }
 
index a03f7e638205af95122ee67a43e6f1f6f7a365b0..4059931fc0d437a9a05c2717f7527b9942abb487 100644 (file)
@@ -16,13 +16,16 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
 
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum HobChannelSelector implements ApplianceChannelSelector {
     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 enum HobChannelSelector implements ApplianceChannelSelector {
     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 enum HobChannelSelector implements ApplianceChannelSelector {
     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 enum HobChannelSelector implements ApplianceChannelSelector {
     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 enum HobChannelSelector implements ApplianceChannelSelector {
     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 enum HobChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum HobChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 }
index 816ee5265ffacb33b63a86bfe1e068076c635f5d..86e62104759dd55fd6fce58e87c10a86a23d9994 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal.handler;
 
 import static org.openhab.binding.miele.internal.MieleBindingConstants.MIELE_DEVICE_CLASS_HOB;
 
+import 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 @@ import org.openhab.core.types.Command;
  * @author Karel Goderis - Initial contribution
  * @author Kai Kreuzer - fixed handling of REFRESH commands
  */
+@NonNullByDefault
 public class HobHandler extends MieleApplianceHandler<HobChannelSelector> {
 
     public HobHandler(Thing thing, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
index 4b2db76aeb8fa63e2f42737fd2d98440256c9000..f84eebc91941db4f227c280b1943d5c7067fe505 100644 (file)
@@ -16,9 +16,11 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
 
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum HoodChannelSelector implements ApplianceChannelSelector {
             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 enum HoodChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum HoodChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 }
index 8de0614c47036a02936388e1fe4d8f10862f409e..83cb84ec7fafc292fe6545c3a8f29fbe7f43fc19 100644 (file)
@@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.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 @@ import com.google.gson.JsonElement;
  * @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<HoodChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(HoodHandler.class);
@@ -49,31 +52,37 @@ public class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
 
         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 class HoodHandler extends MieleApplianceHandler<HoodChannelSelector> {
             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());
+            }
         }
     }
 }
index fa655398341dd17c48bbaedf7b4c12f0d558f61c..85aac472c97701089646c3951d42544e5b3a3fdf 100644 (file)
@@ -19,17 +19,16 @@ import java.util.IllformedLocaleException;
 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.thing.binding.ThingHandler;
 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 @@ import com.google.gson.JsonParser;
  * @author Martin Lepsy - Added check for JsonNull result
  * @author Jacob Laursen - Fixed multicast and protocol support (ZigBee/LAN)
  */
+@NonNullByDefault
 public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannelSelector> extends BaseThingHandler
         implements ApplianceStatusListener {
 
     private final Logger logger = LoggerFactory.getLogger(MieleApplianceHandler.class);
 
-    public static final Set<ThingTypeUID> 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<ThingTypeUID> 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<E> selectorType;
     protected String modelID;
 
@@ -101,7 +100,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                     String.format("Could not get enum constants for value selector: %s", valueSelectorText));
         }
         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 abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                     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 abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
         if (applianceId != null) {
             MieleBridgeHandler bridgeHandler = getMieleBridgeHandler();
             if (bridgeHandler != null) {
-                getMieleBridgeHandler().unregisterApplianceStatusListener(this);
+                bridgeHandler.unregisterApplianceStatusListener(this);
             }
             applianceId = null;
         }
@@ -186,12 +185,17 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
     @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 abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
 
     @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 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
             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 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                     }
                 }
             }
-            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 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
                 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<String, String> properties = editProperties();
                     properties.put(selector.getChannelID(),
                             selector.getState(dpValue, dmd, this.translationProvider).toString());
                     updateProperties(properties);
@@ -330,31 +330,46 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
 
     @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<String, String> 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 abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
         }
     }
 
-    private synchronized MieleBridgeHandler getMieleBridgeHandler() {
+    private synchronized @Nullable MieleBridgeHandler getMieleBridgeHandler() {
         if (this.bridgeHandler == null) {
             Bridge bridge = getBridge();
             if (bridge == null) {
@@ -380,8 +395,9 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
             }
             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 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
     }
 
     protected boolean isResultProcessable(JsonElement result) {
-        if (result == null) {
-            throw new IllegalArgumentException("Provided result is null");
-        }
         return !result.isJsonNull();
     }
 }
index 464a43398dff3aee8a6678f75503046631390fd7..e8a460bbfca7ce5f061fb81ecb889b7b1a1182a1 100644 (file)
@@ -30,7 +30,6 @@ import java.net.URL;
 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.concurrent.TimeUnit;
 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.Gson;
 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 @@ import com.google.gson.JsonParser;
  * @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<ThingTypeUID> 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<ApplianceStatusListener> 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<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<String, HomeDevice>();
     protected Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<String, HomeDevice>();
 
-    protected URL url;
-    protected Map<String, String> headers;
-
-    // Data structures to de-JSONify whatever Miele appliances are sending us
-    public class HomeDevice {
-
-        private static final String MIELE_APPLIANCE_CLASS = "com.miele.xgw3000.gateway.hdm.deviceclasses.MieleAppliance";
-
-        public String Name;
-        public String Status;
-        public String ParentUID;
-        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 class MieleBridgeHandler extends BaseBridgeHandler {
             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 class MieleBridgeHandler extends BaseBridgeHandler {
                     cachedHomeDevicesByRemoteUid.put(hd.getRemoteUid(), hd);
                 }
 
-                @NonNull
-                Set<@NonNull Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
-                @NonNull
-                Iterator<@NonNull Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
+                Set<Entry<String, HomeDevice>> cachedEntries = cachedHomeDevicesByApplianceId.entrySet();
+                Iterator<Entry<String, HomeDevice>> iterator = cachedEntries.iterator();
 
                 while (iterator.hasNext()) {
                     Entry<String, HomeDevice> cachedEntry = iterator.next();
@@ -349,11 +233,13 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
                 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 class MieleBridgeHandler extends BaseBridgeHandler {
                         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 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
                 // 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 class MieleBridgeHandler extends BaseBridgeHandler {
 
                 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 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
                                 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 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
                                     }
                                 }
 
-                                if (id == null) {
+                                if (id == null || name == null || value == null) {
                                     continue;
                                 }
 
@@ -514,8 +407,11 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
                                     }
                                     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 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
         }
     };
 
-    public JsonElement invokeOperation(String applianceId, String modelID, String methodName) {
+    public JsonElement invokeOperation(String applianceId, String modelID, String methodName) throws MieleRpcException {
         if (getThing().getStatus() != ThingStatus.ONLINE) {
-            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 class MieleBridgeHandler extends BaseBridgeHandler {
         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<String, String> headers, String data) throws IOException {
         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 
-        if (headers != null) {
-            for (Map.Entry<String, String> entry : headers.entrySet()) {
-                connection.addRequestProperty(entry.getKey(), entry.getValue());
-            }
+        for (Map.Entry<String, String> entry : headers.entrySet()) {
+            connection.addRequestProperty(entry.getKey(), entry.getValue());
         }
 
         connection.addRequestProperty("Accept-Encoding", "gzip");
@@ -688,16 +588,21 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
 
     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 class MieleBridgeHandler extends BaseBridgeHandler {
     }
 
     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 class MieleBridgeHandler extends BaseBridgeHandler {
     @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;
         }
     }
 }
index 43e374df356cd2115f1539fc16888a7c17e59241..d1d64bfde3c501b094d224d8fd6b999daac0d0ec 100644 (file)
@@ -21,9 +21,11 @@ import java.util.Date;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum OvenChannelSelector implements ApplianceChannelSelector {
     },
     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 enum OvenChannelSelector implements ApplianceChannelSelector {
     },
     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 enum OvenChannelSelector implements ApplianceChannelSelector {
     },
     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 enum OvenChannelSelector implements ApplianceChannelSelector {
     },
     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 enum OvenChannelSelector implements ApplianceChannelSelector {
             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<String, String> phases = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"),
+    private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "heating"), entry("2", "temp-hold"),
             entry("3", "door-open"), entry("4", "pyrolysis"), entry("7", "lighting"), entry("8", "searing-phase"),
             entry("10", "defrost"), entry("11", "cooling-down"), entry("12", "energy-save-phase"));
 
@@ -215,12 +221,13 @@ public enum OvenChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum OvenChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 
     public State getTemperatureState(String s) {
index ce0dd87fd2ee4cabc25d74250479140b1a3d2ad9..a1681033a8b37e6eec6ee9a375eb381d8f7c391a 100644 (file)
@@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.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 @@ import com.google.gson.JsonElement;
  * @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<OvenChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(OvenHandler.class);
@@ -50,32 +53,39 @@ public class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
 
         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 class OvenHandler extends MieleApplianceHandler<OvenChannelSelector> {
             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());
+            }
         }
     }
 }
index 0c364815689581c6c558ff88a555e0f37b4c3f19..ebb355f30eac4b69d9a2df6308c6757fd654829a 100644 (file)
@@ -21,9 +21,11 @@ import java.util.Date;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
     },
     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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
     },
     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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
     },
     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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
     },
     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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
             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<String, String> programs = Map.ofEntries(entry("10", "automatic-plus"),
+    private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("10", "automatic-plus"),
             entry("20", "cottons"), entry("23", "cottons-hygiene"), entry("30", "minimum-iron"),
             entry("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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
             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<String, String> phases = Map.ofEntries(entry("1", "programme-running"),
+    private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "programme-running"),
             entry("2", "drying"), entry("3", "drying-machine-iron"), entry("4", "drying-hand-iron"),
             entry("5", "drying-normal"), entry("6", "drying-normal-plus"), entry("7", "cooling-down"),
             entry("8", "drying-hand-iron"), entry("10", "finished"));
@@ -217,12 +217,13 @@ public enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum TumbleDryerChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 }
index 9fa424d727ae1837f87d9f76199c1d094840b1eb..0c459414328215c32bf1f9c7eaa3b8ffd46f6771 100644 (file)
@@ -15,6 +15,8 @@ package org.openhab.binding.miele.internal.handler;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.APPLIANCE_ID;
 import static org.openhab.binding.miele.internal.MieleBindingConstants.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 @@ import com.google.gson.JsonElement;
  * @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<TumbleDryerChannelSelector> {
 
     private final Logger logger = LoggerFactory.getLogger(TumbleDryerHandler.class);
@@ -50,26 +53,33 @@ public class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
 
         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 class TumbleDryerHandler extends MieleApplianceHandler<TumbleDryerChannel
             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());
+            }
         }
     }
 }
index 4e8da7013ba878b84631e329ac074fee501d55d3..bd22781dcbfe066ea1a6c2c4effa9e20982cfe59 100644 (file)
@@ -21,9 +21,11 @@ import java.util.Date;
 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 @@ import org.slf4j.LoggerFactory;
  * @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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     },
     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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     },
     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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     },
     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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     },
     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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     },
     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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
 
     private final Logger logger = LoggerFactory.getLogger(WashingMachineChannelSelector.class);
 
-    private static final Map<String, String> programs = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"),
+    private static final Map<String, String> PROGRAMS = Map.ofEntries(entry("1", "cottons"), entry("3", "minimum-iron"),
             entry("4", "delicates"), entry("8", "woollens"), entry("9", "silks"), entry("17", "starch"),
             entry("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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             entry("95", "down-duvets"), entry("122", "express-20"), entry("129", "down-filled-items"),
             entry("133", "cottons-eco"), entry("146", "quickpowerwash"), entry("65532", "mix"));
 
-    private static final Map<String, String> phases = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"),
+    private static final Map<String, String> PHASES = Map.ofEntries(entry("1", "pre-wash"), entry("4", "washing"),
             entry("5", "rinses"), entry("7", "clean"), entry("9", "drain"), entry("10", "spin"),
             entry("11", "anti-crease"), entry("12", "finished"));
 
@@ -234,12 +234,13 @@ public enum WashingMachineChannelSelector implements ApplianceChannelSelector {
     }
 
     @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 enum WashingMachineChannelSelector implements ApplianceChannelSelector {
             logger.error("An exception occurred while converting '{}' into a State", s);
         }
 
-        return null;
+        return UnDefType.UNDEF;
     }
 
     public State getTemperatureState(String s) {
index d90bb3bebd2d36741f18fda92c28829f4174b600..736a1b6b791004e3d304f7817ff435221a4bdfd1 100644 (file)
@@ -19,6 +19,8 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.WATER_CON
 
 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 @@ import com.google.gson.JsonElement;
  * @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<WashingMachineChannelSelector>
         implements ExtendedDeviceStateListener {
 
@@ -62,27 +65,34 @@ public class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
 
         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 class WashingMachineHandler extends MieleApplianceHandler<WashingMachineC
             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());
+            }
         }
     }
 
index a009ed38a71ad0eff0e5251037cb691e2bf9bc90..03555d49fb0ec38f805cdf38cad61c59ead37412 100644 (file)
 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 @@ import org.openhab.core.types.UnDefType;
  *
  * @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 class DeviceUtilTest extends JavaTest {
         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 class DeviceUtilTest extends JavaTest {
 
     @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();
     }
index 993935bcba8df6e5019ae4b93b63e09cf847e8e4..4e28d6aa7415be199ba802d6b3dd5abece5401ce 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.miele.internal;
 
 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 @@ import org.openhab.core.test.java.JavaTest;
  *
  * @author Jacob Laursen - Initial contribution
  */
+@NonNullByDefault
 public class FullyQualifiedApplianceIdentifierTest extends JavaTest {
 
     @Test