diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 982544d7a30..f7daae283a2 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -997,7 +997,6 @@ You should calibrate the valve using the device Web UI or Shelly App before star | ------- | --------------- | -------- | --------- | ------------------------------------------------------------------- | | sensors | temperature | Number | yes | Current Temperature in °C | | | state | Contact | yes | Valve status: OPEN or CLOSED (position = 0) | -| | open | Contact | yes | ON: "window is open" was detected, OFF: window is closed | | | lastUpdate | DateTime | yes | Timestamp of the last update (any sensor value changed) | | control | targetTemp | Number | no | Temperature in °C: 4=Low/Min; 5..30=target temperature;31=Hi/Max | | | position | Dimmer | no | Set valve to manual mode (0..100%) disables auto-temp) | 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 3a45219765f..e3300b7766b 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 @@ -291,7 +291,7 @@ public class ShellyBindingConstants { public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+ public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+ public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature - public static final String SHELLY2_API_MIN_FWVERSION = "v0.10.2"; // Gen 2 minimum FW + public static final String SHELLY2_API_MIN_FWVERSION = "v0.10.1"; // Gen 2 minimum FW // Alarm types/messages public static final String ALARM_TYPE_NONE = "NONE"; @@ -327,7 +327,7 @@ public class ShellyBindingConstants { public static final int DIGITS_LUX = 0; public static final int DIGITS_PERCENT = 1; - public static final int SHELLY_API_TIMEOUT_MS = 15000; + public static final int SHELLY_API_TIMEOUT_MS = 10000; public static final int UPDATE_STATUS_INTERVAL_SECONDS = 3; // check for updates every x sec public static final int UPDATE_SKIP_COUNT = 20; // update every x triggers or when a key was pressed public static final int UPDATE_MIN_DELAY = 15;// update every x triggers or when a key was pressed diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java index f6b7dbd209f..1413cf9c608 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java @@ -33,7 +33,7 @@ public class ShellyApiResult { public String response = ""; public int httpCode = -1; public String httpReason = ""; - public String authResponse = ""; + public String authChallenge = ""; public ShellyApiResult() { } 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 8b97f9ae363..e82dc8bee23 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 @@ -14,16 +14,21 @@ package org.openhab.binding.shelly.internal.api; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.SHELLY_API_TIMEOUT_MS; 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.util.ShellyUtils.*; import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; import java.util.Base64; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.ws.rs.core.HttpHeaders; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; @@ -32,6 +37,8 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthChallenge; +import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRsp; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; @@ -49,8 +56,9 @@ import com.google.gson.Gson; public class ShellyHttpClient { private final Logger logger = LoggerFactory.getLogger(ShellyHttpClient.class); - public static final String HTTP_HEADER_AUTH = "Authorization"; + public static final String HTTP_HEADER_AUTH = HttpHeaders.AUTHORIZATION; public static final String HTTP_AUTH_TYPE_BASIC = "Basic"; + public static final String HTTP_AUTH_TYPE_DIGEST = "Digest"; public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; public static final String CONTENT_TYPE_FORM_URLENC = "application/x-www-form-urlencoded"; @@ -72,6 +80,7 @@ public class ShellyHttpClient { this.thingName = thingName; setConfig(thingName, config); this.httpClient = httpClient; + this.httpClient.setConnectTimeout(SHELLY_API_TIMEOUT_MS); } public void initialize() throws ShellyApiException { @@ -103,7 +112,7 @@ public class ShellyHttpClient { boolean timeout = false; while (retries > 0) { try { - apiResult = innerRequest(HttpMethod.GET, uri, ""); + apiResult = innerRequest(HttpMethod.GET, uri, null, ""); if (timeout) { logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered, apiResult.getUrl()); @@ -128,10 +137,15 @@ public class ShellyHttpClient { } public String httpPost(String uri, String data) throws ShellyApiException { - return innerRequest(HttpMethod.POST, uri, data).response; + return innerRequest(HttpMethod.POST, uri, null, data).response; } - private ShellyApiResult innerRequest(HttpMethod method, String uri, String data) throws ShellyApiException { + public String httpPost(@Nullable Shelly2AuthChallenge auth, String data) throws ShellyApiException { + return innerRequest(HttpMethod.POST, SHELLYRPC_ENDPOINT, auth, data).response; + } + + private ShellyApiResult innerRequest(HttpMethod method, String uri, @Nullable Shelly2AuthChallenge auth, + String data) throws ShellyApiException { Request request = null; String url = "http://" + config.deviceIp + uri; ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url); @@ -140,10 +154,24 @@ public class ShellyHttpClient { request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS, TimeUnit.MILLISECONDS); - if (!config.password.isEmpty() && !getString(data).contains("\"auth\":{")) { - String value = config.userId + ":" + config.password; - request.header(HTTP_HEADER_AUTH, - HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes())); + if (!uri.equals(SHELLY_URL_DEVINFO) && !config.password.isEmpty()) { // not for /shelly or no password + // configured + // Add Auth info + // Gen 1: Basic Auth + // Gen 2: Digest Auth + String authHeader = ""; + if (auth != null) { // only if we received an Auth challenge + authHeader = formatAuthResponse(uri, + buildAuthResponse(uri, auth, SHELLY2_AUTHDEF_USER, config.password)); + } else { + if (!uri.equals(SHELLYRPC_ENDPOINT)) { + String bearer = config.userId + ":" + config.password; + authHeader = HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(bearer.getBytes()); + } + } + if (!authHeader.isEmpty()) { + request.header(HTTP_HEADER_AUTH, authHeader); + } } fillPostData(request, data); logger.trace("{}: HTTP {} for {} {}\n{}", thingName, method, url, data, request.getHeaders()); @@ -162,14 +190,14 @@ public class ShellyHttpClient { apiResult.httpCode = message.error.code; apiResult.response = message.error.message; if (getInteger(message.error.code) == HttpStatus.UNAUTHORIZED_401) { - apiResult.authResponse = getString(message.error.message).replaceAll("\\\"", "\""); + apiResult.authChallenge = getString(message.error.message).replaceAll("\\\"", "\""); } } } HttpFields headers = contentResponse.getHeaders(); - String auth = headers.get(HttpHeader.WWW_AUTHENTICATE); - if (!getString(auth).isEmpty()) { - apiResult.authResponse = auth; + String authChallenge = headers.get(HttpHeader.WWW_AUTHENTICATE); + if (!getString(authChallenge).isEmpty()) { + apiResult.authChallenge = authChallenge; } // validate response, API errors are reported as Json @@ -191,6 +219,36 @@ public class ShellyHttpClient { return apiResult; } + protected @Nullable Shelly2AuthRsp buildAuthResponse(String uri, @Nullable Shelly2AuthChallenge challenge, + String user, String password) throws ShellyApiException { + if (challenge == null) { + return null; // not required + } + if (!SHELLY2_AUTHTTYPE_DIGEST.equalsIgnoreCase(challenge.authType) + || !SHELLY2_AUTHALG_SHA256.equalsIgnoreCase(challenge.algorithm)) { + throw new IllegalArgumentException("Unsupported Auth type/algorithm requested by device"); + } + Shelly2AuthRsp response = new Shelly2AuthRsp(); + response.username = user; + response.realm = challenge.realm; + response.nonce = challenge.nonce; + response.cnonce = Long.toHexString((long) Math.floor(Math.random() * 10e8)); + response.nc = "00000001"; + response.authType = challenge.authType; + response.algorithm = challenge.algorithm; + String ha1 = sha256(response.username + ":" + response.realm + ":" + password); + String ha2 = sha256(HttpMethod.POST + ":" + uri);// SHELLY2_AUTH_NOISE; + response.response = sha256( + ha1 + ":" + response.nonce + ":" + response.nc + ":" + response.cnonce + ":" + "auth" + ":" + ha2); + return response; + } + + protected String formatAuthResponse(String uri, @Nullable Shelly2AuthRsp rsp) { + return rsp != null ? MessageFormat.format(HTTP_AUTH_TYPE_DIGEST + + " username=\"{0}\", realm=\"{1}\", uri=\"{2}\", nonce=\"{3}\", cnonce=\"{4}\", nc=\"{5}\", qop=\"auth\",response=\"{6}\", algorithm=\"{7}\", ", + rsp.username, rsp.realm, uri, rsp.nonce, rsp.cnonce, rsp.nc, rsp.response, rsp.algorithm) : ""; + } + /** * Fill in POST data, set http headers * 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 5470c8c6b51..afb0482ad5e 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 @@ -52,8 +52,7 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSe import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorLux; -import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRequest; -import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse; +import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRsp; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch; @@ -92,7 +91,7 @@ public class Shelly2ApiClient extends ShellyHttpClient { protected final ShellyStatusSensor sensorData = new ShellyStatusSensor(); protected final ArrayList rollerStatus = new ArrayList<>(); protected @Nullable ShellyThingInterface thing; - protected @Nullable Shelly2AuthRequest authReq; + protected @Nullable Shelly2AuthRsp authReq; public Shelly2ApiClient(String thingName, ShellyThingInterface thing) { super(thingName, thing); @@ -793,23 +792,6 @@ public class Shelly2ApiClient extends ShellyHttpClient { return request; } - protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm, - String password) throws ShellyApiException { - Shelly2AuthRequest authReq = new Shelly2AuthRequest(); - authReq.username = "admin"; - authReq.realm = realm; - authReq.nonce = authParm.nonce; - authReq.cnonce = (long) Math.floor(Math.random() * 10e8); - authReq.nc = authParm.nc != null ? authParm.nc : 1; - authReq.authType = SHELLY2_AUTHTTYPE_DIGEST; - authReq.algorithm = SHELLY2_AUTHALG_SHA256; - String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password); - String ha2 = SHELLY2_AUTH_NOISE; - authReq.response = sha256( - ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2); - return authReq; - } - protected String mapValue(Map map, @Nullable String key) { String value; boolean known = key != null && !key.isEmpty() && map.containsKey(key); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java index 38f9940616c..672f9e3b8cc 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java @@ -27,6 +27,8 @@ import com.google.gson.annotations.SerializedName; * @author Markus Michels - Initial contribution */ public class Shelly2ApiJsonDTO { + public static final String SHELLYRPC_ENDPOINT = "/rpc"; + public static final String SHELLYRPC_METHOD_CLASS_SHELLY = "Shelly"; public static final String SHELLYRPC_METHOD_CLASS_SWITCH = "Switch"; @@ -1004,7 +1006,7 @@ public class Shelly2ApiJsonDTO { public Object params; public String event; public Object result; - public Shelly2AuthRequest auth; + public Shelly2AuthRsp auth; public Shelly2RpcMessageError error; } @@ -1022,17 +1024,27 @@ public class Shelly2ApiJsonDTO { public Shelly2RpcMessageError error; } + public static String SHELLY2_AUTHDEF_USER = "admin"; public static String SHELLY2_AUTHTTYPE_DIGEST = "digest"; public static String SHELLY2_AUTHTTYPE_STRING = "string"; public static String SHELLY2_AUTHALG_SHA256 = "SHA-256"; // = ':auth:'+HexHash("dummy_method:dummy_uri"); public static String SHELLY2_AUTH_NOISE = "6370ec69915103833b5222b368555393393f098bfbfbb59f47e0590af135f062"; - public static class Shelly2AuthRequest { + public static class Shelly2AuthChallenge { // on 401 message contains the auth info + @SerializedName("auth_type") + public String authType; + public String nonce; + public String nc; + public String realm; + public String algorithm; + } + + public static class Shelly2AuthRsp { public String username; - public Long nonce; - public Long cnonce; - public Integer nc; + public String nonce; + public String cnonce; + public String nc; public String realm; public String algorithm; public String response; @@ -1040,15 +1052,6 @@ public class Shelly2ApiJsonDTO { public String authType; } - public static class Shelly2AuthResponse { // on 401 message contains the auth info - @SerializedName("auth_type") - public String authType; - public Long nonce; - public Integer nc; - public String realm; - public String algorithm; - } - // BTHome samples // BLU Button 1 // {"component":"script:2", "id":2, "event":"oh-blu.scan_result", 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 33ca863ee3e..de378fedab8 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 @@ -55,7 +55,7 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortSta import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor; -import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse; +import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthChallenge; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2ConfigParms; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DeviceConfigSta; import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult; @@ -81,6 +81,7 @@ import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptRe import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; 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.ThingStatusDetail; import org.slf4j.Logger; @@ -99,7 +100,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac protected boolean initialized = false; private boolean discovery = false; private Shelly2RpcSocket rpcSocket = new Shelly2RpcSocket(); - private Shelly2AuthResponse authInfo = new Shelly2AuthResponse(); + private @Nullable Shelly2AuthChallenge authInfo; /** * Regular constructor - called by Thing handler @@ -203,6 +204,11 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac ShellySettingsDevice device = getDeviceInfo(); profile.settings.device = device; + if (!getString(device.fw).isEmpty()) { + profile.fwDate = substringBefore(device.fw, "/"); + profile.fwVersion = profile.status.update.oldVersion = "v" + substringAfter(device.fw, "/"); + } + profile.hostname = device.hostname; profile.deviceType = device.type; profile.mac = device.mac; @@ -388,6 +394,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac if (ourId != -1) { startScript(ourId, false); enableScript(script, false); + deleteScript(ourId); logger.debug("{}: Script {} was disabledd, id={}", thingName, script, ourId); } return; @@ -439,8 +446,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac } if (upload && ourId != -1) { // Delete existing script - logger.debug("{}: Delete existing script", thingName); - apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_DELETE).withId(ourId)); + deleteScript(ourId); } if (upload) { @@ -479,10 +485,8 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac if (!running) { running = startScript(ourId, true); - } - if (!discovery) { - logger.info("{}: Script {} {}", thingName, script, - running ? "was successfully (re)started" : "failed to start"); + logger.debug("{}: Script {} {}", thingName, script, + running ? "was successfully started" : "failed to start"); } } catch (ShellyApiException e) { ShellyApiResult res = e.getApiResult(); @@ -515,10 +519,25 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac apiRequest(SHELLYRPC_METHOD_SCRIPT_SETCONFIG, params, String.class); return true; } catch (ShellyApiException e) { + logger.debug("{}: Unable to enable script {}", thingName, script, e); return false; } } + private boolean deleteScript(int id) { + if (id == -1) { + throw new IllegalArgumentException("Invalid Script Id"); + } + try { + logger.debug("{}: Delete existing script with id{}", thingName, id); + apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_DELETE).withId(id)); + return true; + } catch (ShellyApiException e) { + logger.debug("{}: Unable to delete script with id {}", thingName, id); + } + return false; + } + @Override public void onConnect(String deviceIp, boolean connected) { if (thing == null && thingTable != null) { @@ -550,7 +569,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac if (message.error != null) { if (message.error.code == HttpStatus.UNAUTHORIZED_401 && !getString(message.error.message).isEmpty()) { // Save nonce for notification - Shelly2AuthResponse auth = gson.fromJson(message.error.message, Shelly2AuthResponse.class); + Shelly2AuthChallenge auth = gson.fromJson(message.error.message, Shelly2AuthChallenge.class); if (auth != null && auth.realm == null) { logger.debug("{}: Authentication data received: {}", thingName, message.error.message); authInfo = auth; @@ -757,13 +776,17 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac status.update.hasUpdate = ds.sys.availableUpdates.stable != null; if (ds.sys.availableUpdates.stable != null) { status.update.newVersion = "v" + getString(ds.sys.availableUpdates.stable.version); + status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.newVersion) < 0; } if (ds.sys.availableUpdates.beta != null) { status.update.betaVersion = "v" + getString(ds.sys.availableUpdates.beta.version); + status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.betaVersion) < 0; } } - if (ds.sys.wakeUpReason != null && ds.sys.wakeUpReason.boot != null) { + if (ds.sys.wakeUpReason != null && ds.sys.wakeUpReason.boot != null) + + { List values = new ArrayList<>(); String boot = getString(ds.sys.wakeUpReason.boot); String cause = getString(ds.sys.wakeUpReason.cause); @@ -793,7 +816,6 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac return relayStatus; } - @SuppressWarnings("null") @Override public void setRelayTurn(int id, String turnMode) throws ShellyApiException { ShellyDeviceProfile profile = getProfile(); @@ -1128,35 +1150,32 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac Shelly2RpcBaseMessage req = buildRequest(method, params); try { reconnect(); // make sure WS is connected - - if (authInfo.realm != null) { - req.auth = buildAuthRequest(authInfo, config.userId, config.serviceName, config.password); - } json = rpcPost(gson.toJson(req)); } catch (ShellyApiException e) { ShellyApiResult res = e.getApiResult(); - String auth = getString(res.authResponse); + String auth = getString(res.authChallenge); if (res.isHttpAccessUnauthorized() && !auth.isEmpty()) { String[] options = auth.split(","); + authInfo = new Shelly2AuthChallenge(); for (String o : options) { String key = substringBefore(o, "=").stripLeading().trim(); String value = substringAfter(o, "=").replaceAll("\"", "").trim(); switch (key) { case "Digest qop": + authInfo.authType = SHELLY2_AUTHTTYPE_DIGEST; break; case "realm": authInfo.realm = value; break; case "nonce": - authInfo.nonce = Long.parseLong(value, 16); + // authInfo.nonce = Long.parseLong(value, 16); + authInfo.nonce = value; break; case "algorithm": authInfo.algorithm = value; break; } } - authInfo.nc = 1; - req.auth = buildAuthRequest(authInfo, config.userId, authInfo.realm, config.password); json = rpcPost(gson.toJson(req)); } else { throw e; @@ -1187,7 +1206,7 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac } private String rpcPost(String postData) throws ShellyApiException { - return httpPost("/rpc", postData); + return httpPost(authInfo, postData); } private void reconnect() throws ShellyApiException { 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 ecf724ff080..a043772cd9a 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 @@ -114,7 +114,7 @@ public class Shelly2RpcSocket { try { disconnect(); // for safety - URI uri = new URI("ws://" + deviceIp + "/rpc"); + URI uri = new URI("ws://" + deviceIp + SHELLYRPC_ENDPOINT); ClientUpgradeRequest request = new ClientUpgradeRequest(); request.setHeader(HttpHeaders.HOST, deviceIp); request.setHeader("Origin", "http://" + deviceIp); @@ -276,6 +276,8 @@ public class Shelly2RpcSocket { e.event, e.data.name); } } + } else { + handler.onNotifyEvent(fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class)); } } } 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 dd808628826..959c845e014 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 @@ -104,7 +104,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler private final ShellyChannelCache cache; private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS; - private final boolean gen2; + private boolean gen2 = false; private final boolean blu; protected boolean autoCoIoT = false; @@ -1138,11 +1138,12 @@ public abstract class ShellyBaseHandler extends BaseThingHandler * @param mode Device mode (e.g. relay, roller) */ protected void changeThingType(String thingType, String mode) { - ThingTypeUID thingTypeUID = ShellyThingCreator.getThingTypeUID(thingType, "", mode); + String deviceType = substringBefore(thingType, "-"); + ThingTypeUID thingTypeUID = ShellyThingCreator.getThingTypeUID(thingType, deviceType, mode); if (!thingTypeUID.equals(THING_TYPE_SHELLYUNKNOWN)) { logger.debug("{}: Changing thing type to {}", getThing().getLabel(), thingTypeUID); Map properties = editProperties(); - properties.replace(PROPERTY_DEV_TYPE, thingType); + properties.replace(PROPERTY_DEV_TYPE, deviceType); properties.replace(PROPERTY_DEV_MODE, mode); updateProperties(properties); changeThingType(thingTypeUID, getConfig()); diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml index 9e75954a76a..b66b8137fc0 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/shellyGen2_relay.xml @@ -35,10 +35,18 @@ @text/thing-type.shelly.shellyplus2-relay.description - - - - + + + + + + + + + + + +