]> git.basschouten.com Git - openhab-addons.git/commitdiff
[dlinksmarthome] Reboot device daily following shutdown of cloud service (#14479)
authorMike Major <mike_j_major@hotmail.com>
Sat, 11 Mar 2023 09:49:30 +0000 (09:49 +0000)
committerGitHub <noreply@github.com>
Sat, 11 Mar 2023 09:49:30 +0000 (10:49 +0100)
* Reboot device

---------

Signed-off-by: Mike Major <mike_j_major@hotmail.com>
bundles/org.openhab.binding.dlinksmarthome/README.md
bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/DLinkSmartHomeDiscoveryParticipant.java
bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/handler/DLinkMotionSensorHandler.java
bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorCommunication.java
bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorConfig.java
bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/i18n/dlinksmarthome.properties
bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/thing/thing-types.xml

index 2e8e8905d2c3ec0e2e40813fd76935aa27ce46e7..5f3755c0af5ed232b2cfb2ac164a73f2a2f74a78 100644 (file)
@@ -6,11 +6,12 @@ A binding for D-Link Smart Home devices.
 
 ### DCH-S150 (WiFi motion sensor)
 
-The binding has been tested with hardware revisions A1 and A2 running firmware version 1.22.
+The binding has been tested with hardware revisions A1 and A2 running firmware version 1.22. 
+The mydlink Home service is now end of life and the device requires a daily reboot (performed by the binding) to keep it responsive.
 
 ## Discovery
 
-The binding can automatically discover devices that have already been added to the Wifi network. Please refer to your mydlink Home app for instructions on how to add your device to your Wifi network.
+The binding can automatically discover devices that have already been added to the Wifi network.
 
 ## Binding Configuration
 
@@ -25,6 +26,7 @@ Once added the configuration must be updated to specify the PIN code located on
 
 - **ipAddress** - Hostname or IP of the device
 - **pin** - PIN code from the back of the device
+- **rebootHour** - Hour (24h) of the day that the device will be rebooted to ensure that it remains responsive (default is 3).
 
 To manually configure a DCH-S150 Thing you must specify its IP address and PIN code.
 
index cb373b5a9845f6a20a1c979c3ed0fac9d3a784e1..e87dc0890c3f93fc4b0f10b7c0e811bbd9c87a87 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.dlinksmarthome.internal;
 import static org.openhab.binding.dlinksmarthome.internal.DLinkSmartHomeBindingConstants.*;
 import static org.openhab.binding.dlinksmarthome.internal.motionsensor.DLinkMotionSensorConfig.IP_ADDRESS;
 
+import java.net.Inet4Address;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -96,7 +97,11 @@ public class DLinkSmartHomeDiscoveryParticipant implements MDNSDiscoveryParticip
 
     private DiscoveryResult createMotionSensor(final ThingUID thingUID, final ThingTypeUID thingType,
             final ServiceInfo serviceInfo) {
-        final String host = serviceInfo.getHostAddresses()[0];
+        final Inet4Address[] addresses = serviceInfo.getInet4Addresses();
+        if (addresses.length != 1) {
+            return null;
+        }
+        final String host = addresses[0].getHostAddress();
         final String mac = serviceInfo.getPropertyString("mac");
 
         final Map<String, Object> properties = new HashMap<>();
index 7c25d9f3de93d207e9143d6624627b3517a36149..43c3ba8a52d1f54092a1bde2bf9bf13bc6302e29 100644 (file)
@@ -64,6 +64,9 @@ public class DLinkMotionSensorHandler extends BaseThingHandler implements DLinkM
             case ONLINE:
                 updateStatus(ThingStatus.ONLINE);
                 break;
+            case REBOOTING:
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE, "Device rebooting");
+                break;
             case COMMUNICATION_ERROR:
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
                 break;
index ca90a66523fb3052382bea448417dbf554b58f5a..e446b86751315991cc6b5e8392740236beed1382 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.dlinksmarthome.internal.motionsensor;
 
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -43,10 +45,14 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
 
     // SOAP actions
     private static final String DETECTION_ACTION = "\"http://purenetworks.com/HNAP1/GetLatestDetection\"";
+    private static final String REBOOT_ACTION = "\"http://purenetworks.com/HNAP1/Reboot\"";
 
     private static final int DETECT_TIMEOUT_MS = 5000;
     private static final int DETECT_POLL_S = 1;
 
+    private static final int REBOOT_TIMEOUT_MS = 60000;
+    private static final int REBOOT_WAIT_S = 35;
+
     /**
      * Indicates the device status
      *
@@ -64,6 +70,10 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
          * Problem communicating with device
          */
         COMMUNICATION_ERROR,
+        /**
+         * Device is being rebooted
+         */
+        REBOOTING,
         /**
          * Internal error
          */
@@ -84,18 +94,24 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
     private final Logger logger = LoggerFactory.getLogger(DLinkMotionSensorCommunication.class);
 
     private final DLinkMotionSensorListener listener;
+    private final ScheduledExecutorService scheduler;
+
+    private int rebootHour;
 
     private SOAPMessage detectionAction;
+    private SOAPMessage rebootAction;
 
     private boolean loginSuccess;
     private boolean detectSuccess;
+    private boolean rebootSuccess;
 
     private long prevDetection;
     private long lastDetection;
 
-    private final ScheduledFuture<?> detectFuture;
+    private ScheduledFuture<?> detectFuture;
+    private ScheduledFuture<?> rebootFuture;
 
-    private boolean online = true;
+    private boolean rebootRequired = false;
     private DeviceStatus status = DeviceStatus.INITIALISING;
 
     /**
@@ -104,35 +120,46 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
     private final Runnable detect = new Runnable() {
         @Override
         public void run() {
-            boolean updateStatus = false;
+            final DeviceStatus currentStatus = status;
+            final boolean tryReboot = rebootRequired;
 
             switch (status) {
                 case INITIALISING:
-                    online = false;
-                    updateStatus = true;
+                case REBOOTING:
+                    loginSuccess = false;
                     // FALL-THROUGH
                 case COMMUNICATION_ERROR:
                 case ONLINE:
-                    if (!loginSuccess) {
-                        login(detectionAction, DETECT_TIMEOUT_MS);
-                    }
+                    if (!tryReboot) {
+                        if (!loginSuccess) {
+                            login(detectionAction, DETECT_TIMEOUT_MS);
+                        }
 
-                    if (!getLastDetection(false)) {
-                        // Try login again in case the session has timed out
-                        login(detectionAction, DETECT_TIMEOUT_MS);
-                        getLastDetection(true);
+                        if (!getLastDetection(false)) {
+                            // Try login again in case the session has timed out
+                            login(detectionAction, DETECT_TIMEOUT_MS);
+                            getLastDetection(true);
+                        }
+                    } else {
+                        login(rebootAction, REBOOT_TIMEOUT_MS);
+                        reboot();
                     }
                     break;
                 default:
                     break;
             }
 
-            if (loginSuccess && detectSuccess) {
+            if (tryReboot) {
+                if (rebootSuccess) {
+                    rebootRequired = false;
+                    status = DeviceStatus.REBOOTING;
+                    detectFuture.cancel(false);
+                    detectFuture = scheduler.scheduleWithFixedDelay(detect, REBOOT_WAIT_S, DETECT_POLL_S,
+                            TimeUnit.SECONDS);
+                }
+            } else if (loginSuccess && detectSuccess) {
                 status = DeviceStatus.ONLINE;
-                if (!online) {
-                    online = true;
-                    listener.sensorStatus(status);
-
+                if (currentStatus != DeviceStatus.ONLINE) {
                     // Ignore old detections
                     prevDetection = lastDetection;
                 }
@@ -140,12 +167,22 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
                 if (lastDetection != prevDetection) {
                     listener.motionDetected();
                 }
-            } else {
-                if (online || updateStatus) {
-                    online = false;
-                    listener.sensorStatus(status);
-                }
             }
+
+            if (currentStatus != status) {
+                listener.sensorStatus(status);
+            }
+        }
+    };
+
+    /**
+     * Reboot the device
+     */
+    private final Runnable reboot = new Runnable() {
+        @Override
+        public void run() {
+            rebootRequired = true;
+            rebootFuture = scheduler.schedule(reboot, getNextRebootTime(), TimeUnit.MILLISECONDS);
         }
     };
 
@@ -153,6 +190,8 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
             final DLinkMotionSensorListener listener, final ScheduledExecutorService scheduler) {
         super(config.ipAddress, config.pin);
         this.listener = listener;
+        this.scheduler = scheduler;
+        this.rebootHour = config.rebootHour;
 
         if (getHNAPStatus() == HNAPStatus.INTERNAL_ERROR) {
             status = DeviceStatus.INTERNAL_ERROR;
@@ -161,8 +200,10 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
         try {
             final MessageFactory messageFactory = MessageFactory.newInstance();
             detectionAction = messageFactory.createMessage();
+            rebootAction = messageFactory.createMessage();
 
             buildDetectionAction();
+            buildRebootAction();
 
         } catch (final SOAPException e) {
             logger.debug("DLinkMotionSensorCommunication - Internal error", e);
@@ -170,6 +211,7 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
         }
 
         detectFuture = scheduler.scheduleWithFixedDelay(detect, 0, DETECT_POLL_S, TimeUnit.SECONDS);
+        rebootFuture = scheduler.schedule(reboot, getNextRebootTime(), TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -178,6 +220,7 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
     @Override
     public void dispose() {
         detectFuture.cancel(true);
+        rebootFuture.cancel(true);
         super.dispose();
     }
 
@@ -198,6 +241,40 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
         headers.addHeader(SOAPACTION, DETECTION_ACTION);
     }
 
+    /**
+     * This is the SOAP message used to reboot the device. This message will
+     * only receive a successful response after the login process has been completed and the
+     * authentication data has been set. Device needs rebooting as it eventually becomes
+     * unresponsive due to cloud services being shutdown.
+     *
+     * @throws SOAPException
+     */
+    private void buildRebootAction() throws SOAPException {
+        rebootAction.getSOAPHeader().detachNode();
+        final SOAPBody soapBody = rebootAction.getSOAPBody();
+        soapBody.addChildElement("Reboot", "", HNAP_XMLNS);
+
+        final MimeHeaders headers = rebootAction.getMimeHeaders();
+        headers.addHeader(SOAPACTION, REBOOT_ACTION);
+    }
+
+    /**
+     * Get the number of milliseconds to the next reboot time
+     *
+     * @return Time in ms to next reboot
+     */
+    private long getNextRebootTime() {
+        final LocalDateTime now = LocalDateTime.now();
+        LocalDateTime nextReboot = LocalDateTime.of(now.getYear(), now.getMonth(), now.getDayOfMonth(), rebootHour, 0,
+                0);
+
+        if (!nextReboot.isAfter(now)) {
+            nextReboot = nextReboot.plusDays(1);
+        }
+
+        return now.until(nextReboot, ChronoUnit.MILLIS);
+    }
+
     /**
      * Output unexpected responses to the debug log and sets the FIRMWARE error.
      *
@@ -292,4 +369,32 @@ public class DLinkMotionSensorCommunication extends DLinkHNAPCommunication {
 
         return detectSuccess;
     }
+
+    /**
+     * Sends the reboot message
+     *
+     */
+    private void reboot() {
+        rebootSuccess = false;
+
+        if (loginSuccess) {
+            try {
+                final Document soapResponse = sendReceive(rebootAction, REBOOT_TIMEOUT_MS);
+
+                final Node result = soapResponse.getElementsByTagName("RebootResult").item(0);
+
+                if (result != null && OK.equals(result.getTextContent())) {
+                    rebootSuccess = true;
+                } else {
+                    unexpectedResult("reboot - Unexpected response", soapResponse);
+                }
+            } catch (final Exception e) {
+                // Assume there has been some problem trying to send one of the messages
+                if (status != DeviceStatus.COMMUNICATION_ERROR) {
+                    logger.debug("getLastDetection - Communication error", e);
+                    status = DeviceStatus.COMMUNICATION_ERROR;
+                }
+            }
+        }
+    }
 }
index b16fea4237b61e75cb210c43d705f1f86ac67545..6f151bd10ff8b575a0d86ffe896e5ae9a6fb8f09 100644 (file)
@@ -23,7 +23,6 @@ public class DLinkMotionSensorConfig {
      * Constants representing the configuration strings
      */
     public static final String IP_ADDRESS = "ipAddress";
-    public static final String PIN = "pin";
 
     /**
      * The IP address of the device
@@ -34,4 +33,9 @@ public class DLinkMotionSensorConfig {
      * The pin code of the device
      */
     public String pin;
+
+    /**
+     * The hour to reboot the device
+     */
+    public int rebootHour;
 }
index ca706f56ca268d1d91fd33f25f8fc002d7da649d..7291d0c4fe97a1869ed87e7ec1d51229313ae859 100644 (file)
@@ -16,3 +16,5 @@ thing-type.config.dlinksmarthome.DCH-S150.ipAddress.label = Hostname or IP
 thing-type.config.dlinksmarthome.DCH-S150.ipAddress.description = Hostname or IP of the device.
 thing-type.config.dlinksmarthome.DCH-S150.pin.label = PIN Code
 thing-type.config.dlinksmarthome.DCH-S150.pin.description = PIN code from the back of the device.
+thing-type.config.dlinksmarthome.DCH-S150.rebootHour.label = Reboot Hour
+thing-type.config.dlinksmarthome.DCH-S150.rebootHour.description = Hour (24h) of the day that the device will be rebooted to ensure that it remains responsive.
index f3948ffb08dd80f034b73be308bc9a378a1fcd1c..8cbee61c17e6d7a6e95a6ca0cb23da64eb5ca011 100644 (file)
                                <label>PIN Code</label>
                                <description>PIN code from the back of the device.</description>
                        </parameter>
+                       <parameter name="rebootHour" type="integer" min="0" max="23" required="false">
+                               <default>3</default>
+                               <advanced>true</advanced>
+                               <label>Reboot Hour</label>
+                               <description>Hour (24h) of the day that the device will be rebooted to ensure that it remains responsive.</description>
+                       </parameter>
                </config-description>
        </thing-type>
 </thing:thing-descriptions>