diff --git a/bundles/org.openhab.binding.dlinksmarthome/README.md b/bundles/org.openhab.binding.dlinksmarthome/README.md index 2e8e8905d..5f3755c0a 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/README.md +++ b/bundles/org.openhab.binding.dlinksmarthome/README.md @@ -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. diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/DLinkSmartHomeDiscoveryParticipant.java b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/DLinkSmartHomeDiscoveryParticipant.java index cb373b5a9..e87dc0890 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/DLinkSmartHomeDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/DLinkSmartHomeDiscoveryParticipant.java @@ -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 properties = new HashMap<>(); diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/handler/DLinkMotionSensorHandler.java b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/handler/DLinkMotionSensorHandler.java index 7c25d9f3d..43c3ba8a5 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/handler/DLinkMotionSensorHandler.java +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/handler/DLinkMotionSensorHandler.java @@ -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; diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorCommunication.java b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorCommunication.java index ca90a6652..e446b8675 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorCommunication.java +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorCommunication.java @@ -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; + } + } + } + } } diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorConfig.java b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorConfig.java index b16fea423..6f151bd10 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorConfig.java +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/java/org/openhab/binding/dlinksmarthome/internal/motionsensor/DLinkMotionSensorConfig.java @@ -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; } diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/i18n/dlinksmarthome.properties b/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/i18n/dlinksmarthome.properties index ca706f56c..7291d0c4f 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/i18n/dlinksmarthome.properties +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/i18n/dlinksmarthome.properties @@ -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. diff --git a/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/thing/thing-types.xml index f3948ffb0..8cbee61c1 100644 --- a/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.dlinksmarthome/src/main/resources/OH-INF/thing/thing-types.xml @@ -26,6 +26,12 @@ PIN code from the back of the device. + + 3 + true + + Hour (24h) of the day that the device will be rebooted to ensure that it remains responsive. +