From a13fd80bfe5cf0b6c55e13756f42abc27b55796b Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sat, 25 Nov 2023 09:46:22 +0100 Subject: [PATCH] [shelly] Misc changes (small fixes, log improvements, hardened leak prevention on (#15922) * Misc changes (same fixes, log improvements, hardened leak prevention on exceptions) --------- Signed-off-by: Markus Michels --- bundles/org.openhab.binding.shelly/README.md | 4 +- bundles/org.openhab.binding.shelly/pom.xml | 9 -- .../internal/ShellyBindingConstants.java | 1 + .../shelly/internal/ShellyHandlerFactory.java | 2 +- .../internal/api/ShellyDeviceProfile.java | 15 +++ .../shelly/internal/api/ShellyHttpClient.java | 11 +- .../internal/api1/Shelly1CoapHandler.java | 12 +- .../internal/api2/Shelly2ApiClient.java | 2 +- .../shelly/internal/api2/Shelly2ApiRpc.java | 18 +-- .../internal/api2/Shelly2RpcSocket.java | 10 +- .../shelly/internal/api2/ShellyBluApi.java | 31 ++--- .../discovery/ShellyDiscoveryParticipant.java | 4 + .../internal/handler/ShellyBaseHandler.java | 113 +++++++----------- .../handler/ShellyBluSensorHandler.java | 6 +- .../internal/handler/ShellyComponents.java | 2 +- .../manager/ShellyManagerOverviewPage.java | 2 +- .../provider/ShellyChannelDefinitions.java | 2 +- .../resources/OH-INF/i18n/shelly.properties | 6 +- 18 files changed, 124 insertions(+), 126 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 406cffc19..fde35de2e 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -1451,8 +1451,6 @@ See notes on discovery of Shelly BLU devices above. | | lowBattery | Switch | yes | Low battery alert (< 20%) | | device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | - - ### Shelly BLU Door/Window Sensor (thing-type: shellybludw) See notes on discovery of Shelly BLU devices above. @@ -1467,7 +1465,7 @@ See notes on discovery of Shelly BLU devices above. | | lowBattery | Switch | yes | Low battery alert (< 20%) | | device | gatewayDevice | String | yes | Shelly forwarded last status update (BLU gateway), could vary from packet to packet | -## Shelly BLU Motion Sensor (thing-type: shellyblumotion) +### Shelly BLU Motion Sensor (thing-type: shellyblumotion) See notes on discovery of Shelly BLU devices above. diff --git a/bundles/org.openhab.binding.shelly/pom.xml b/bundles/org.openhab.binding.shelly/pom.xml index 61c8dbf6c..429dc71bd 100644 --- a/bundles/org.openhab.binding.shelly/pom.xml +++ b/bundles/org.openhab.binding.shelly/pom.xml @@ -14,13 +14,4 @@ org.openhab.binding.shelly openHAB Add-ons :: Bundles :: Shelly Binding Gen1+2 - - - org.eclipse.jetty.websocket - websocket-server - 9.4.46.v20220331 - compile - - - diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 34957a2b4..1a8732b0a 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -120,6 +120,7 @@ public class ShellyBindingConstants { public static final String PROPERTY_DEV_TYPE = "deviceType"; public static final String PROPERTY_DEV_MODE = "deviceMode"; public static final String PROPERTY_DEV_GEN = "deviceGeneration"; + public static final String PROPERTY_DEV_AUTH = "deviceAuth"; public static final String PROPERTY_GW_DEVICE = "gatewayDevice"; public static final String PROPERTY_HWREV = "deviceHwRev"; public static final String PROPERTY_HWBATCH = "deviceHwBatch"; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index f0cf33be8..111f60e4a 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -119,7 +119,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { ShellyBaseHandler handler = null; if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) { - logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(), + logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(), thingTypeUID.toString()); handler = new ShellyProtectedHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient); } else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 23cce5cff..e82314ae9 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -145,6 +145,7 @@ public class ShellyDeviceProfile { device.hostname = device.mac.length() >= 12 ? "shelly-" + device.mac.toUpperCase().substring(6, 11) : "unknown"; } + device.mode = getString(settings.mode).toLowerCase(); name = getString(settings.name); hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : ""; hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : ""; @@ -418,4 +419,18 @@ public class ShellyDeviceProfile { // If device is not yet intialized or the enabled property is missing we assume that CoIoT is enabled return true; } + + public static String buildBluServiceName(String name, String mac) throws IllegalArgumentException { + String model = name.contains("-") ? substringBefore(name, "-") : name; // e.g. SBBT-02C or just SBDW + switch (model) { + case SHELLYDT_BLUBUTTON: + return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase(); + case SHELLYDT_BLUDW: + return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase(); + case SHELLYDT_BLUMOTION: + return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase(); + default: + throw new IllegalArgumentException("Unsupported BLU device model " + model); + } + } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java index 7ed436345..41fb01813 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -120,6 +120,12 @@ public class ShellyHttpClient { } return apiResult.response; // successful } catch (ShellyApiException e) { + if (e.isHttpAccessUnauthorized() && !profile.isGen2 && !basicAuth && !config.password.isEmpty()) { + logger.debug("{}: Access is unauthorized, auto-activate basic auth", thingName); + basicAuth = true; + apiResult = innerRequest(HttpMethod.GET, uri, null, ""); + } + if (e.isConnectionError() || (!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound() || profile.hasBattery || (retries == 0)) { @@ -129,9 +135,10 @@ public class ShellyHttpClient { timeout = true; timeoutErrors++; // count the retries - logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); - retries--; + if (profile.alwaysOn) { + logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); + } } } throw new ShellyApiException("API Timeout or inconsistent result"); // successful diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java index 3de692e22..48eab5fb5 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -49,6 +49,7 @@ import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -241,7 +242,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener { } if (!coiotBound) { thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion); - logger.debug("{}: CoIoT Version {} detected", thingName, iVersion); + logger.debug("{}: CoIoT Version {} detected", thingName, iVersion); if (iVersion == COIOT_VERSION_1) { coiot = new Shelly1CoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); } else if (iVersion == COIOT_VERSION_2) { @@ -265,6 +266,13 @@ public class Shelly1CoapHandler implements Shelly1CoapListener { } } + // Don't change state to online when thing is in status config error + // (e.g. auth failed, but device sends COAP packets via multicast) + if (thingHandler.getThingStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR) { + logger.debug("{}: The device is not configuired correctly, skip Coap packet", thingName); + return; + } + // If we received a CoAP message successful the thing must be online thingHandler.setThingOnline(); @@ -441,7 +449,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener { List sensorUpdates = list.generic; Map updates = new TreeMap(); - logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size()); + logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size()); int failed = 0; ShellyColorUtils col = new ShellyColorUtils(); for (int i = 0; i < sensorUpdates.size(); i++) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java index 7ff8a6992..0e687307b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java @@ -507,7 +507,7 @@ public class Shelly2ApiClient extends ShellyHttpClient { rs.isValid = sm.isValid = emeter.isValid = true; if (cs.state != null) { if (!getString(rs.state).equals(cs.state)) { - logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state, + logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state, mapValue(MAP_ROLLER_STATE, cs.state), updateChannels); } rs.state = mapValue(MAP_ROLLER_STATE, cs.state); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java index 17efc1d84..c37a9c854 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java @@ -131,14 +131,13 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac @Override public void initialize() throws ShellyApiException { - if (!initialized) { - rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp); - rpcSocket.addMessageHandler(this); - initialized = true; - } else { + if (initialized) { logger.debug("{}: Disconnect Rpc Socket on initialize", thingName); disconnect(); } + rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp); + rpcSocket.addMessageHandler(this); + initialized = true; } @Override @@ -1211,6 +1210,9 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac } private void disconnect() { + if (rpcSocket.isConnected()) { + logger.debug("{}: Disconnect Rpc Socket", thingName); + } rpcSocket.disconnect(); } @@ -1220,8 +1222,10 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac @Override public void close() { - logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName, - rpcSocket.isConnected() ? "connected" : "disconnected", discovery); + if (initialized || rpcSocket.isConnected()) { + logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName, + rpcSocket.isConnected() ? "connected" : "disconnected", discovery); + } disconnect(); initialized = false; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java index faff2738e..308ab06ed 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java @@ -206,18 +206,24 @@ public class Shelly2RpcSocket { if (s.isOpen()) { logger.debug("{}: Disconnecting WebSocket ({} -> {})", thingName, s.getLocalAddress(), s.getRemoteAddress()); - s.disconnect(); } + s.disconnect(); s.close(StatusCode.NORMAL, "Socket closed"); session = null; } - client.stop(); } catch (Exception e) { if (e.getCause() instanceof InterruptedException) { logger.debug("{}: Unable to close socket - interrupted", thingName); // e.g. device was rebooted } else { logger.debug("{}: Unable to close socket", thingName, e); } + } finally { + // make sure client is stopped / thread terminates / socket resource is free up + try { + client.stop(); + } catch (Exception e) { + logger.debug("{}: Unable to close Web Socket", thingName, e); + } } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 7869b6fd7..5dbb81609 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -15,7 +15,6 @@ package org.openhab.binding.shelly.internal.api2; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*; -import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import java.util.ArrayList; @@ -112,11 +111,11 @@ public class ShellyBluApi extends Shelly2ApiRpc { public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { ShellySettingsDevice info = new ShellySettingsDevice(); info.hostname = !config.serviceName.isEmpty() ? config.serviceName : ""; - info.fw = "1234"; - info.type = "SBBT"; + info.fw = ""; + info.type = "BLU"; info.mac = config.deviceAddress; info.auth = false; - info.gen = 99; + info.gen = 2; return info; } @@ -136,13 +135,13 @@ public class ShellyBluApi extends Shelly2ApiRpc { profile.gateway = getThing().getProperty(PROPERTY_GW_DEVICE); } - ShellySettingsDevice device = getDeviceInfo(); + profile.device = getDeviceInfo(); if (config.serviceName.isEmpty()) { config.serviceName = getString(profile.device.hostname); } - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); - profile.status.update.oldVersion = profile.fwVersion; + + // for now we have no API to get this information + profile.fwDate = profile.fwVersion = profile.status.update.oldVersion = ""; profile.status.hasUpdate = profile.status.update.hasUpdate = false; if (profile.hasBattery) { @@ -239,7 +238,7 @@ public class ShellyBluApi extends Shelly2ApiRpc { } logger.debug("{}: BLU Device discovered", thingName); if (e.data.name != null) { - profile.settings.name = buildBluServiceName(e.data.name, e.data.addr); + profile.settings.name = ShellyDeviceProfile.buildBluServiceName(e.data.name, e.data.addr); } break; case SHELLY2_EVENT_BLUDATA: @@ -317,18 +316,4 @@ public class ShellyBluApi extends Shelly2ApiRpc { if (updated) { } } - - public static String buildBluServiceName(String name, String mac) throws IllegalArgumentException { - String model = name.contains("-") ? substringBefore(name, "-") : name; // e.g. SBBT-02C or just SBDW - switch (model) { - case SHELLYDT_BLUBUTTON: - return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase(); - case SHELLYDT_BLUDW: - return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase(); - case SHELLYDT_BLUMOTION: - return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase(); - default: - throw new IllegalArgumentException("Unsupported BLU device model " + model); - } - } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 793b710f7..db5da2e1e 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -145,15 +145,18 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { boolean gen2 = "2".equals(service.getPropertyString("gen")); ShellyApiInterface api = null; + boolean auth = false; ShellySettingsDevice devInfo; try { api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient); api.initialize(); devInfo = api.getDeviceInfo(); model = devInfo.type; + auth = devInfo.auth; if (devInfo.name != null) { deviceName = devInfo.name; } + profile = api.getDeviceProfile(thingType, devInfo); api.close(); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); @@ -191,6 +194,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { addProperty(properties, PROPERTY_DEV_TYPE, thingType); addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1"); addProperty(properties, PROPERTY_DEV_MODE, mode); + addProperty(properties, PROPERTY_DEV_AUTH, auth ? "yes" : "no"); logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString()); String thingLabel = deviceName.isEmpty() ? name + " - " + address diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index 2871943ed..0daed7b48 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -187,21 +187,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT); start = initializeThing(); } catch (ShellyApiException e) { - ShellyApiResult res = e.getApiResult(); - String mid = ""; - if (e.isJsonError()) { // invalid JSON format - mid = "offline.status-error-unexpected-error"; - start = false; - } else if (isAuthorizationFailed(res)) { - mid = "offline.conf-error-access-denied"; - start = false; - } else if (profile.alwaysOn && e.isConnectionError()) { - mid = "offline.status-error-connect"; - } - if (!mid.isEmpty()) { - setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, mid, e.toString()); - } - logger.debug("{}: Unable to initialize: {}, retrying later", thingName, e.toString()); + start = handleApiException(e); } catch (IllegalArgumentException e) { logger.debug("{}: Unable to initialize, retrying later", thingName, e); } finally { @@ -215,6 +201,43 @@ public abstract class ShellyBaseHandler extends BaseThingHandler }, 2, TimeUnit.SECONDS); } + private boolean handleApiException(ShellyApiException e) { + ShellyApiResult res = e.getApiResult(); + ThingStatusDetail errorCode = ThingStatusDetail.COMMUNICATION_ERROR; + String status = ""; + boolean retry = true; + if (e.isJsonError()) { // invalid JSON format + logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e); + status = "offline.status-error-unexpected-error"; + errorCode = ThingStatusDetail.CONFIGURATION_ERROR; + retry = false; + } else if (res.isHttpAccessUnauthorized()) { + status = "offline.conf-error-access-denied"; + errorCode = ThingStatusDetail.CONFIGURATION_ERROR; + retry = false; + } else if (isWatchdogExpired()) { + status = "offline.status-error-watchdog"; + } else if (res.httpCode >= 400) { + logger.debug("{}: Unexpected API result: {}/{}", thingName, res.httpCode, res.httpReason, e); + status = "offline.status-error-unexpected-api-result"; + retry = false; + } else if (profile.alwaysOn && (e.isConnectionError() || res.isHttpTimeout())) { + status = "offline.status-error-connect"; + } + + if (!status.isEmpty()) { + setThingOffline(errorCode, status, e.toString()); + } else { + logger.debug("{}: Unable to initialize: {}, retrying later", thingName, e.toString()); + } + + if (!retry) { + api.close(); + } + + return retry; + } + @Override public ShellyThingConfiguration getThingConfig() { return config; @@ -464,10 +487,11 @@ public abstract class ShellyBaseHandler extends BaseThingHandler requestUpdates(1, false); } } catch (ShellyApiException e) { - ShellyApiResult res = e.getApiResult(); - if (isAuthorizationFailed(res)) { + if (!handleApiException(e)) { return; } + + ShellyApiResult res = e.getApiResult(); if (res.isNotCalibrtated()) { logger.warn("{}: {}", thingName, messages.get("roller.calibrating")); } else { @@ -554,35 +578,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler } catch (ShellyApiException e) { // http call failed: go offline except for battery devices, which might be in // sleep mode. Once the next update is successful the device goes back online - String status = ""; - ShellyApiResult res = e.getApiResult(); - if (profile.alwaysOn && e.isConnectionError()) { - status = "offline.status-error-connect"; - } else if (res.isHttpAccessUnauthorized()) { - status = "offline.conf-error-access-denied"; - } else if (isWatchdogStarted()) { - if (!isWatchdogExpired()) { - logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); - if (profile.alwaysOn) { // suppress for battery powered sensors - logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); - } - } - } else if (e.isJSONException()) { - status = "offline.status-error-unexpected-api-result"; - logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e); - } else if (res.isHttpTimeout()) { - // Watchdog not started, e.g. device in sleep mode - if (isThingOnline()) { // ignore when already offline - status = "offline.status-error-watchdog"; - } - } else { - status = "offline.status-error-unexpected-api-result"; - logger.debug("{}: Unexpected API result: {}", thingName, res.response, e); - } - - if (!status.isEmpty()) { - setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, status); - } + handleApiException(e); } catch (NullPointerException | IllegalArgumentException e) { logger.debug("{}: Unable to refresh status: {}", thingName, messages.get("statusupdate.failed"), e); } finally { @@ -631,7 +627,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler } if (prf.isRoller && prf.settings.favorites != null) { String channelId = mkChannelId(CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_FAV); - logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName, + logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName, prf.settings.favorites.size()); channelDefinitions.clearStateOptions(channelId); int fid = 1; @@ -1057,7 +1053,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler String minVersion = !gen2 ? SHELLY_API_MIN_FWVERSION : SHELLY2_API_MIN_FWVERSION; if (version.compare(prf.fwVersion, minVersion) < 0) { logger.warn("{}: {}", prf.device.hostname, - messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); + messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, minVersion)); } } if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) @@ -1120,23 +1116,6 @@ public abstract class ShellyBaseHandler extends BaseThingHandler } } - /** - * Checks the http response for authorization error. - * If the authorization failed the binding can't access the device settings and determine the thing type. In this - * case the thing type shelly-unknown is set. - * - * @param result exception details including the http respone - * @return true if the authorization failed - */ - protected boolean isAuthorizationFailed(ShellyApiResult result) { - if (result.isHttpAccessUnauthorized()) { - // If the device is password protected the API doesn't provide settings to the device settings - setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-access-denied"); - return true; - } - return false; - } - /** * Change type of this thing. * @@ -1363,11 +1342,11 @@ public abstract class ShellyBaseHandler extends BaseThingHandler properties.put(PROPERTY_SERVICE_NAME, config.serviceName); String deviceName = getString(profile.settings.name); properties.put(PROPERTY_SERVICE_NAME, config.serviceName); - properties.put(PROPERTY_DEV_GEN, "1"); + properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2"); + properties.put(PROPERTY_DEV_AUTH, getBool(profile.device.auth) ? "yes" : "no"); if (!deviceName.isEmpty()) { properties.put(PROPERTY_DEV_NAME, deviceName); } - properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2"); // add status properties if (status.wifiSta != null) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java index 0c392678d..8e097bf76 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBluSensorHandler.java @@ -13,7 +13,6 @@ package org.openhab.binding.shelly.internal.handler; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.api2.ShellyBluApi.buildBluServiceName; import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; @@ -23,6 +22,7 @@ import java.util.TreeMap; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent; import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration; @@ -54,7 +54,7 @@ public class ShellyBluSensorHandler extends ShellyBaseHandler { public static void addBluThing(String gateway, Shelly2NotifyEvent e, ShellyThingTable thingTable) { String model = substringBefore(getString(e.data.name), "-").toUpperCase(); - String mac = e.data.addr.replace(":", ""); + String mac = e.data.addr.replaceAll(":", ""); String ttype = ""; logger.debug("{}: Create thing for new BLU device {}: {} / {}", gateway, e.data.name, model, mac); ThingTypeUID tuid; @@ -75,7 +75,7 @@ public class ShellyBluSensorHandler extends ShellyBaseHandler { logger.debug("{}: Unsupported BLU device model {}, MAC={}", gateway, model, mac); return; } - String serviceName = buildBluServiceName(model, mac); + String serviceName = ShellyDeviceProfile.buildBluServiceName(getString(e.data.name), mac); Map properties = new TreeMap<>(); addProperty(properties, PROPERTY_MODEL_ID, model); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index cbac83ffc..fc648d2eb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -78,7 +78,7 @@ public class ShellyComponents { if (status.tmp != null && getBool(status.tmp.isValid) && !thingHandler.getProfile().isSensor && status.tmp.tC != SHELLY_API_INVTEMP) { thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, - toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS)); + toQuantityType(getDouble(status.tmp.tC), DIGITS_TEMP, SIUnits.CELSIUS)); } else if (status.temperature != null && status.temperature != SHELLY_API_INVTEMP) { thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS)); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java index ff4307699..93c8bd1ee 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java @@ -64,7 +64,7 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage { String action = getUrlParm(parameters, URLPARM_ACTION).toLowerCase(); String uidParm = getUrlParm(parameters, URLPARM_UID).toLowerCase(); - logger.debug("Generating overview for {} devices", getThingHandlers().size()); + logger.debug("Generating overview for {} devices", getThingHandlers().size()); String html = ""; Map properties = new HashMap<>(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index 6f03bfc79..7ecaa6232 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -315,7 +315,7 @@ public class ShellyChannelDefinitions { addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME); // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value - boolean accuChannel = profile.numMeters > 1 && !profile.isRoller && !profile.isRGBW2; + boolean accuChannel = profile.hasRelays && profile.numMeters > 1 && !profile.isRoller && !profile.isRGBW2; addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL); addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED); diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index caa4b4243..8ec3b279b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -34,7 +34,7 @@ message.versioncheck.update = INFO: New firmware available: current version: {0} message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT 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.command.init = Thing not yet initialized, command {0} triggered initialization message.status.unknown.initializing = Initializing or device in sleep mode. message.statusupdate.failed = Unable to update status message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager @@ -120,7 +120,7 @@ thing-type.shelly.shellyproem50.description = Shelly Pro EM-50 - 2xPower Meter + thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter # BLU devices -thing-type.shelly.shellyblubutton.description = Shelly BLU Button +thing-type.shelly.shellyblubutton.description = Shelly BLU Button 1 thing-type.shelly.shellybludw.description = Shelly BLU Door/Window Sensor thing-type.shelly.shellyblumotion.description = Shelly BLU Motion Sensor @@ -247,7 +247,7 @@ channel-type.shelly.temperature4.description = Temperature of external Sensor #4 channel-type.shelly.temperature5.label = Temperature 5 channel-type.shelly.temperature6.description = Temperature of external Sensor #5 channel-type.shelly.targetTemp.label = Target Temperature -channel-type.shelly.targetTemp.description = Target Temperature in °C to be reached in auto-temperature mode +channel-type.shelly.targetTemp.description = Target Temperature in °C to be reached in auto-temperature mode channel-type.shelly.humidity.label = Humidity channel-type.shelly.humidity.description = Relative humidity (0..100%) channel-type.shelly.rollerShutter.label = Roller Control (0=open, 100=closed)