mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[shelly] Fix Plus/Pro Auth support (#15284)
* Reimplement Auth Digist to return response as http header field instead of being part of post data; avoid sending Basic Auth for Gen2 and /shelly Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
parent
91c008a06c
commit
7f6502d769
@ -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) |
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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<ShellyRollerStatus> 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<String, String> map, @Nullable String key) {
|
||||
String value;
|
||||
boolean known = key != null && !key.isEmpty() && map.containsKey(key);
|
||||
|
@ -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",
|
||||
|
@ -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<Object> 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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String, String> 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());
|
||||
|
@ -35,10 +35,18 @@
|
||||
<label>ShellyPlus 2 Relay</label>
|
||||
<description>@text/thing-type.shelly.shellyplus2-relay.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel"/>
|
||||
<channel-group id="meter1" typeId="meter"/>
|
||||
<channel-group id="relay2" typeId="relayChannel"/>
|
||||
<channel-group id="meter2" typeId="meter"/>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user