[dlinksmarthome] Reboot device daily following shutdown of cloud service (#14479)

* Reboot device

---------

Signed-off-by: Mike Major <mike_j_major@hotmail.com>
This commit is contained in:
Mike Major 2023-03-11 09:49:30 +00:00 committed by GitHub
parent 38915f5009
commit 53bbda267f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 26 deletions

View 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.

View 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<>();

View 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;

View 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;
}
}
}
}
}

View 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;
}

View 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.

View File

@ -26,6 +26,12 @@
<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>