]> git.basschouten.com Git - openhab-addons.git/commitdiff
[shelly] Fix thing re-init after power cycle for firmware update (#17163)
authorMarkus Michels <markus7017@gmail.com>
Fri, 2 Aug 2024 21:14:38 +0000 (23:14 +0200)
committerGitHub <noreply@github.com>
Fri, 2 Aug 2024 21:14:38 +0000 (23:14 +0200)
* Fixes thing re-init during/after firmware update. API will not be closed
on ota_success message.
* Don't disconnect from device during firmware update, this ensures to get
further events throughout the upgrade process
* suspress COMMUNICATION_ERROR on expected restart after fw upgrade

Signed-off-by: Markus Michels <markus7017@gmail.com>
bundles/org.openhab.binding.shelly/README.md
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingInterface.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties

index 9b0bfdf2f5f1c21fb81febf3aae7292d80898217..e9f5b8da25b76869445c9c60c4ef61b3edc600c4 100644 (file)
@@ -285,14 +285,19 @@ Values 1-4 are selecting the corresponding favorite id in the Shelly App, 0 mean
 
 The binding sets the following Thing status depending on the device status:
 
-| Status         | Description                                                                                                                                                                                                                                                                                                  |
-| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| INITIALIZING   | This is the default status while initializing the Thing. Once the initialization is triggered the Thing switches to Status UNKNOWN.                                                                                                                                                                          |
-| UNKNOWN        | Indicates that the status is currently unknown, which must not show a problem. Usually the Thing stays in this status when the device is in sleep mode. Once the device is reachable and was initialized the Thing switches to status ONLINE.                                                                |
-| ONLINE         | ONLINE indicates that the device can be accessed and is responding properly. Battery powered devices also stay ONLINE when in sleep mode. The binding has an integrated watchdog timer supervising the device, see below. The Thing switches to status OFFLINE when some type of communication error occurs. |
-| OFFLINE        | Communication with the device failed. Check the Thing status in the UI and openHAB's log for an indication of the error. Try restarting OH or deleting and re-discovering the Thing. You could also post to the community thread if the problem persists.                                                    |
-| CONFIG PENDING | The thing has been initialized, but device initialization is in progress or pending (e.g. waiting for device wake-up)                                                                                                                                                                                        |
-| ERROR: COMM    | Communication with the device has reported an error, check detailed status.                                                                                                                                                                                                                                  |
+| Status                | Description                                                                                                                  |
+| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| INITIALIZING          | This is the default status while initializing the Thing. Once the initialization is triggered the Thing switches to Status ONLINE.CONFIGURATION_PENDING. |
+| UNKNOWN               | Indicates that the status is currently unknown, which must not show a problem. Once the device is reachable and was initialized the Thing switches to status ONLINE. |
+| CONFIGURATION_PENDING | The Thing has been initialized, but device initialization is in progress or pending (e.g. waiting for device wake-up).       |
+| ONLINE                | ONLINE indicates that the device can be accessed and is responding properly. Once initialized battery powered devices also stay ONLINE when in sleep mode. The binding has an integrated watchdog timer supervising the device, see below. The Thing switches to status OFFLINE when some type of communication error occurs.        |
+| OFFLINE               | Communication with the device failed. Check the Thing status in the UI and openHAB's log for an indication of the error.     |
+| COMMUNICATION_ERROR   | Communication with the device has reported an error, check detailed status. If the problem persists make sure to have stable WiFi, set the correct password etc. Try restarting OH or deleting and re-discovering the Thing. |
+| FIRMWARE_UPDATING     | Device firmware is updating, just wait. The device should come back to ONLINE within 2 minutes.                              |
+| DUTY_CYCLE            | The device is re-initializing and reported a restart event, e.g. after a firmware update or manual reboot.                   |
+
+`Note:`
+For more details see  [Thing Concept](https://www.openhab.org/docs/concepts/things.html#status-details) in openHAB documentation.
 
 `Battery powered devices:`
 If the device is in sleep mode and can't be reached by the binding, the Thing will change into CONFIG_PENDING.
index 48bcc0ff215bb60733b1db43d6989b71be5a8ab6..e131f0348e54a436599b5997e00dc32c1a362432 100644 (file)
@@ -135,6 +135,7 @@ public class Shelly2ApiJsonDTO {
     public static final String SHELLY2_EVENT_OTASTART = "ota_begin";
     public static final String SHELLY2_EVENT_OTAPROGRESS = "ota_progress";
     public static final String SHELLY2_EVENT_OTADONE = "ota_success";
+    public static final String SHELLY2_EVENT_RESTART = "scheduled_restart";
     public static final String SHELLY2_EVENT_WIFICONNFAILED = "sta_connect_fail";
     public static final String SHELLY2_EVENT_WIFIDISCONNECTED = "sta_disconnected";
 
index 09cd64ca4f7fa39eac39ee0418d61fe9dd04f468..b78257d1ef015bbdfab38590d53d2b7335599aa7 100644 (file)
@@ -85,6 +85,7 @@ import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
 import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
 import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
 import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -171,7 +172,6 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
         }
 
         Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class);
-        profile.isGen2 = true;
         profile.settingsJson = gson.toJson(dc);
         profile.thingName = thingName;
         profile.settings.name = profile.status.name = dc.sys.device.name;
@@ -684,19 +684,22 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
 
                     case SHELLY2_EVENT_OTASTART:
                         logger.debug("{}: Firmware update started: {}", thingName, getString(e.msg));
-                        getThing().postEvent(e.event, true);
-                        getThing().setThingOffline(ThingStatusDetail.FIRMWARE_UPDATING,
+                        getThing().setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.FIRMWARE_UPDATING,
                                 "offline.status-error-fwupgrade");
                         break;
                     case SHELLY2_EVENT_OTAPROGRESS:
                         logger.debug("{}: Firmware update in progress: {}", thingName, getString(e.msg));
-                        getThing().postEvent(e.event, false);
                         break;
                     case SHELLY2_EVENT_OTADONE:
-                        logger.debug("{}: Firmware update completed: {}", thingName, getString(e.msg));
-                        getThing().setThingOffline(ThingStatusDetail.CONFIGURATION_PENDING,
+                        logger.debug("{}: Firmware update completed with status {}", thingName, getString(e.msg));
+                        getThing().setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE,
+                                "message.offline.status-error-fwcompleted");
+                        break;
+                    case SHELLY2_EVENT_RESTART:
+                        logger.debug("{}: Device was restarted: {}", thingName, getString(e.msg));
+                        getThing().setThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.DUTY_CYCLE,
                                 "offline.status-error-restarted");
-                        getThing().requestUpdates(1, true); // refresh config
+                        getThing().postEvent(ALARM_TYPE_RESTARTED, true);
                         break;
                     case SHELLY2_EVENT_SLEEP:
                         logger.debug("{}: Connection terminated, e.g. device in sleep mode", thingName);
@@ -732,10 +735,12 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
             String reason = getString(description);
             logger.debug("{}: WebSocket connection closed, status = {}/{}", thingName, statusCode, reason);
             if ("Bye".equalsIgnoreCase(reason)) {
-                logger.debug("{}: Device went to sleep mode", thingName);
+                logger.debug("{}: Device went to sleep mode or was restarted", thingName);
             } else if (statusCode == StatusCode.ABNORMAL && !discovery && getProfile().alwaysOn) {
                 // e.g. device rebooted
-                thingOffline("WebSocket connection closed abnormal");
+                if (getThing().getThingStatusDetail() != ThingStatusDetail.DUTY_CYCLE) {
+                    thingOffline("WebSocket connection closed abnormally");
+                }
             }
         } catch (ShellyApiException e) {
             logger.debug("{}: Exception on onClose()", thingName, e);
@@ -753,8 +758,8 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac
 
     private void thingOffline(String reason) {
         if (thing != null) { // do not reinit of battery powered devices with sleep mode
-            thing.setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-unexpected-error",
-                    reason);
+            thing.setThingOfflineAndDisconnect(ThingStatusDetail.COMMUNICATION_ERROR,
+                    "offline.status-error-unexpected-error", reason);
         }
     }
 
index f395142067a2f176a1b477c5a446b08d099c3fb1..a45f119492779908f00c6c3ebe066005e5f62f87 100755 (executable)
@@ -231,7 +231,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
         }
 
         if (!status.isEmpty()) {
-            setThingOffline(errorCode, status, e.toString());
+            setThingOfflineAndDisconnect(errorCode, status, e.toString());
         } else {
             logger.debug("{}: Unable to initialize: {}, retrying later", thingName, e.toString());
         }
@@ -299,13 +299,17 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
                 thingName, getThing().getLabel(), thingType, config.deviceAddress.toUpperCase(), gen2, profile.isBlu,
                 profile.alwaysOn, profile.hasBattery, config.eventsCoIoT);
         if (config.deviceAddress.isEmpty()) {
-            setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-address");
+            setThingOfflineAndDisconnect(ThingStatusDetail.CONFIGURATION_ERROR,
+                    "config-status.error.missing-device-address");
             return false;
         }
 
         if (profile.alwaysOn || !profile.isInitialized()) {
-            updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
-                    messages.get("status.unknown.initializing"));
+            ThingStatusDetail detail = getThingStatusDetail();
+            if (detail != ThingStatusDetail.DUTY_CYCLE) {
+                updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                        messages.get("status.config_pending"));
+            }
         }
 
         // Gen 1 only: Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing
@@ -319,7 +323,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
         api.initialize();
         ShellySettingsDevice device = profile.device = api.getDeviceInfo();
         if (getBool(device.auth) && config.password.isEmpty()) {
-            setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
+            setThingOfflineAndDisconnect(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
             return false;
         }
         if (config.serviceName.isEmpty()) {
@@ -336,7 +340,8 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
         // Validate device mode
         String reqMode = thingType.contains("-") ? substringAfter(thingType, "-") : "";
         if (!reqMode.isEmpty() && !mode.equals(reqMode)) {
-            setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", mode, reqMode);
+            setThingOfflineAndDisconnect(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", mode,
+                    reqMode);
             return false;
         }
         if (!getString(tmpPrf.device.coiot).isEmpty()) {
@@ -543,7 +548,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
             ThingStatus thingStatus = getThing().getStatus();
             if (refreshSettings || (scheduledUpdates > 0) || (skipUpdate % skipCount == 0)) {
                 if (!profile.isInitialized() || ((thingStatus == ThingStatus.OFFLINE))
-                        || (thingStatus == ThingStatus.UNKNOWN)) {
+                        || (getThingStatusDetail() == ThingStatusDetail.CONFIGURATION_PENDING)) {
                     logger.debug("{}: Status update triggered thing initialization", thingName);
                     initializeThing(); // may fire an exception if initialization failed
                 }
@@ -671,7 +676,8 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
 
     @Override
     public boolean isThingOnline() {
-        return getThingStatus() == ThingStatus.ONLINE;
+        return getThingStatus() == ThingStatus.ONLINE
+                && getThingStatusDetail() != ThingStatusDetail.CONFIGURATION_PENDING;
     }
 
     public boolean isThingOffline() {
@@ -692,13 +698,18 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
     }
 
     @Override
-    public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments) {
+    public void setThingOfflineAndDisconnect(ThingStatusDetail detail, String messageKey, Object... arguments) {
         if (!isThingOffline()) {
             updateStatus(ThingStatus.OFFLINE, detail, messages.get(messageKey, arguments));
-            api.close(); // Gen2: disconnect WS/close http sessions
-            watchdog = 0;
-            channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
         }
+        api.close(); // Gen2: disconnect WS/close http sessions
+        watchdog = 0;
+        channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
+    }
+
+    @Override
+    public void setThingStatus(ThingStatus status, ThingStatusDetail detail, String messageKey, Object... arguments) {
+        updateStatus(status, detail, messages.get(messageKey, arguments));
     }
 
     @Override
@@ -724,7 +735,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
             logger.debug("{}: Handler is shutting down, ignore", thingName);
             return;
         }
-        updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
+        updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
                 messages.get("offline.status-error-restarted"));
         requestUpdates(0, true);
     }
@@ -1025,7 +1036,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
         config.localIp = bindingConfig.localIP;
         config.localPort = String.valueOf(bindingConfig.httpPort);
         if (config.localIp.startsWith("169.254")) {
-            setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "config-status.error.network-config",
+            setThingOfflineAndDisconnect(ThingStatusDetail.COMMUNICATION_ERROR, "config-status.error.network-config",
                     config.localIp);
             return false;
         }
@@ -1159,7 +1170,8 @@ public abstract class ShellyBaseHandler extends BaseThingHandler
             changeThingType(thingTypeUID, getConfig());
         } else {
             logger.debug("{}:  to {}", thingName, thingType);
-            setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Unable to change thing type to " + thingType);
+            setThingOfflineAndDisconnect(ThingStatusDetail.CONFIGURATION_ERROR,
+                    "Unable to change thing type to " + thingType);
         }
     }
 
index 3b761855f6569ee9bd6110bff8ab5d8137cb9157..f4d99a1ba1b6f4c07a6482c784392930552bbc63 100644 (file)
@@ -46,7 +46,7 @@ public interface ShellyManagerInterface {
 
     public void setThingOnline();
 
-    public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
+    public void setThingOfflineAndDisconnect(ThingStatusDetail detail, String messageKey, Object... arguments);
 
     public boolean requestUpdates(int requestCount, boolean refreshSettings);
 
index 68758c1249271e64513f907a5dc46043c018f315..38f7f8054215835c088674a47974ea750a70d5ef 100644 (file)
@@ -52,7 +52,9 @@ public interface ShellyThingInterface {
 
     void setThingOnline();
 
-    void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
+    void setThingOfflineAndDisconnect(ThingStatusDetail detail, String messageKey, Object... arguments);
+
+    void setThingStatus(ThingStatus status, ThingStatusDetail detail, String messageKey, Object... arguments);
 
     boolean isStopping();
 
index ee1429c08d0306cc41c506c3a40abc56a205f843..1b6d8e0cb2f9ae4e6b88901322205817bdf271d6 100644 (file)
@@ -442,7 +442,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
     }
 
     private void setRestarted(ShellyManagerInterface th, String uid) {
-        th.setThingOffline(ThingStatusDetail.GONE, "offline.status-error-restarted");
+        th.setThingOfflineAndDisconnect(ThingStatusDetail.GONE, "offline.status-error-restarted");
         scheduleUpdate(th, uid + "_upgrade", 25); // wait 25s before refresh
     }
 }
index 901e546936c9b2b5bb40c6fdbb77a2ea7a22b584..eb759411a5f60ba760c391efb7f1c3e35d0b4425 100644 (file)
@@ -118,7 +118,7 @@ public class ShellyManagerOtaPage extends ShellyManagerPage {
 
             if ("yes".equalsIgnoreCase(update)) {
                 // do the update
-                th.setThingOffline(ThingStatusDetail.FIRMWARE_UPDATING, "offline.status-error-fwupgrade");
+                th.setThingOfflineAndDisconnect(ThingStatusDetail.FIRMWARE_UPDATING, "offline.status-error-fwupgrade");
                 html += loadHTML(FWUPDATE2_HTML, properties);
 
                 new Thread(() -> { // schedule asynchronous reboot
index 5be941643391adf20cc638e8670dd991961549b5..5871cdcfcec26c9ab79ef0fb0f06f7d396125fec 100644 (file)
@@ -263,7 +263,7 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
         ShellyThingConfiguration config = thing.getConfiguration().as(ShellyThingConfiguration.class);
         TreeMap<String, String> result = new TreeMap<>();
 
-        if ((status != ThingStatus.ONLINE) && (status != ThingStatus.UNKNOWN)) {
+        if (status != ThingStatus.ONLINE && status != ThingStatus.UNKNOWN) {
             result.put("Thing Status", status.toString());
         }
         State wifiSignal = handler.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI);
index 66e676ee6362e42b53e1378b0a3afad21a244e20..ff97262c0f7b3d25de3db2182dffb47cc5a1d2cf 100644 (file)
@@ -25,7 +25,8 @@ message.offline.status-error-unexpected-error = Unexpected error: {0}
 message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
 message.offline.status-error-watchdog = Device is not responding, seems to be unavailable.
 message.offline.status-error-restarted = The device has restarted and will be re-initialized.
-message.offline.status-error-fwupgrade = Firmware upgrade in progress
+message.offline.status-error-fwupgrade = Firmware upgrade in progress.
+message.offline.status-error-fwcompleted = Firmware upgrade completed, device is restarting.
 
 # General messages
 message.versioncheck.failed = Unable to check firmware version: {0}
@@ -36,7 +37,7 @@ message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum vers
 message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
 message.command.failed = ERROR: Unable to process command {0} for channel {1}
 message.command.init = Thing not yet initialized, command {0} triggered initialization
-message.status.unknown.initializing = Initializing or device in sleep mode.
+message.status.config_pending = Device is initializing or in sleep mode.
 message.statusupdate.failed = Unable to update status
 message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager
 message.event.triggered = Event triggered: {0}