2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.shelly.internal.api2;
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
17 import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
18 import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*;
19 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.UncheckedIOException;
26 import java.util.ArrayList;
27 import java.util.List;
29 import java.util.stream.Collectors;
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.jetty.client.HttpClient;
34 import org.eclipse.jetty.http.HttpStatus;
35 import org.eclipse.jetty.websocket.api.StatusCode;
36 import org.openhab.binding.shelly.internal.api.ShellyApiException;
37 import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
38 import org.openhab.binding.shelly.internal.api.ShellyApiResult;
39 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
40 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
41 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
42 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
43 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode;
44 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
45 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer;
46 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
47 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
48 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
49 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
50 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
51 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
52 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsWiFiNetwork;
53 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
54 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
55 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
56 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
57 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
58 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2APClientList;
59 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthChallenge;
60 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2ConfigParms;
61 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DevConfigBle.Shelly2DevConfigBleObserver;
62 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DeviceConfigSta;
63 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
64 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp;
65 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp.Shelly2DeviceConfigApRE;
66 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceSettings;
67 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusLight;
68 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
69 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusSys.Shelly2DeviceStatusSysAvlUpdate;
70 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
71 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
72 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
73 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
74 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus.Shelly2NotifyStatus;
75 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest;
76 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest.Shelly2RpcRequestParams;
77 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse;
78 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse.Shelly2WsConfigResult;
79 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptListResponse;
80 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptListResponse.ShellyScriptListEntry;
81 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptPutCodeParams;
82 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.ShellyScriptResponse;
83 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
84 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
85 import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
86 import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
87 import org.openhab.core.library.unit.SIUnits;
88 import org.openhab.core.thing.ThingStatusDetail;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
93 * {@link Shelly2ApiRpc} implements Gen2 RPC interface
95 * @author Markus Michels - Initial contribution
98 public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterface, Shelly2RpctInterface {
99 private final Logger logger = LoggerFactory.getLogger(Shelly2ApiRpc.class);
100 private final @Nullable ShellyThingTable thingTable;
102 protected boolean initialized = false;
103 private boolean discovery = false;
104 private Shelly2RpcSocket rpcSocket = new Shelly2RpcSocket();
105 private @Nullable Shelly2AuthChallenge authInfo;
108 * Regular constructor - called by Thing handler
110 * @param thingName Symbolic thing name
111 * @param thing Thing Handler (ThingHandlerInterface)
113 public Shelly2ApiRpc(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
114 super(thingName, thing);
115 this.thingName = thingName;
117 this.thingTable = thingTable;
121 * Simple initialization - called by discovery handler
123 * @param thingName Symbolic thing name
124 * @param config Thing Configuration
125 * @param httpClient HTTP Client to be passed to ShellyHttpClient
127 public Shelly2ApiRpc(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
128 super(thingName, config, httpClient);
129 this.thingName = thingName;
130 this.thingTable = null;
131 this.discovery = true;
135 public void initialize() throws ShellyApiException {
137 logger.debug("{}: Disconnect Rpc Socket on initialize", thingName);
140 rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp);
141 rpcSocket.addMessageHandler(this);
146 public boolean isInitialized() {
151 public void startScan() {
153 if (getProfile().isBlu) {
154 installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway);
156 } catch (ShellyApiException e) {
160 @SuppressWarnings("null")
162 public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo)
163 throws ShellyApiException {
164 ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
166 if (devInfo != null) {
167 profile.device = devInfo;
169 if (profile.device.type == null) {
170 profile.device = getDeviceInfo();
173 Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class);
174 profile.isGen2 = true;
175 profile.settingsJson = gson.toJson(dc);
176 profile.thingName = thingName;
177 profile.settings.name = profile.status.name = dc.sys.device.name;
178 profile.name = getString(profile.settings.name);
179 profile.settings.timezone = getString(dc.sys.location.tz);
180 profile.settings.discoverable = getBool(dc.sys.device.discoverable);
181 if (dc.wifi != null && dc.wifi.ap != null && dc.wifi.ap.rangeExtender != null) {
182 profile.settings.wifiAp.rangeExtender = getBool(dc.wifi.ap.rangeExtender.enable);
184 if (dc.cloud != null) {
185 profile.settings.cloud.enabled = getBool(dc.cloud.enable);
187 if (dc.mqtt != null) {
188 profile.settings.mqtt.enable = getBool(dc.mqtt.enable);
190 if (dc.sys.sntp != null) {
191 profile.settings.sntp.server = dc.sys.sntp.server;
194 profile.isRoller = dc.cover0 != null;
195 profile.settings.relays = fillRelaySettings(profile, dc);
196 profile.settings.inputs = fillInputSettings(profile, dc);
197 profile.settings.rollers = fillRollerSettings(profile, dc);
199 profile.isEMeter = true;
200 profile.numInputs = profile.settings.inputs != null ? profile.settings.inputs.size() : 0;
201 profile.numRelays = profile.settings.relays != null ? profile.settings.relays.size() : 0;
202 profile.numRollers = profile.settings.rollers != null ? profile.settings.rollers.size() : 0;
203 profile.hasRelays = profile.numRelays > 0 || profile.numRollers > 0;
204 if (getString(profile.device.mode).isEmpty() && profile.hasRelays) {
205 profile.device.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY;
208 ShellySettingsDevice device = profile.device;
209 if (config.serviceName.isEmpty()) {
210 config.serviceName = getString(profile.device.hostname);
212 profile.settings.fw = device.fw;
213 profile.fwDate = substringBefore(substringBefore(device.fw, "/"), "-");
214 profile.fwVersion = profile.status.update.oldVersion = ShellyDeviceProfile.extractFwVersion(device.fw);
215 profile.status.hasUpdate = profile.status.update.hasUpdate = false;
217 if (dc.eth != null) {
218 profile.settings.ethernet = getBool(dc.eth.enable);
220 if (dc.ble != null) {
221 profile.settings.bluetooth = getBool(dc.ble.enable);
224 profile.settings.wifiSta = new ShellySettingsWiFiNetwork();
225 profile.settings.wifiSta1 = new ShellySettingsWiFiNetwork();
226 fillWiFiSta(dc.wifi.sta, profile.settings.wifiSta);
227 fillWiFiSta(dc.wifi.sta1, profile.settings.wifiSta1);
228 if (dc.wifi.ap != null && dc.wifi.ap.rangeExtender != null) {
229 profile.settings.rangeExtender = getBool(dc.wifi.ap.rangeExtender.enable);
232 profile.numMeters = 0;
233 if (profile.hasRelays) {
234 profile.status.relays = new ArrayList<>();
235 relayStatus.relays = new ArrayList<>();
236 profile.numMeters = profile.isRoller ? profile.numRollers : profile.numRelays;
237 for (int i = 0; i < profile.numRelays; i++) {
238 profile.status.relays.add(new ShellySettingsRelay());
239 relayStatus.relays.add(new ShellyShortStatusRelay());
243 if (profile.numInputs > 0) {
244 profile.status.inputs = new ArrayList<>();
245 relayStatus.inputs = new ArrayList<>();
246 for (int i = 0; i < profile.numInputs; i++) {
247 ShellyInputState input = new ShellyInputState();
250 input.eventCount = 0;
251 profile.status.inputs.add(input);
252 relayStatus.inputs.add(input);
256 // handle special cases, because there is no indicator for a meter in GetConfig
257 // Pro 3EM has 3 meters
258 // Pro 2 has 2 relays, but no meters
259 // Mini PM has 1 meter, but no relay
260 if (thingType.equals(THING_TYPE_SHELLYPRO2_RELAY_STR)) {
261 profile.numMeters = 0;
262 } else if (thingType.equals(THING_TYPE_SHELLYPRO3EM_STR)) {
263 profile.numMeters = 3;
264 } else if (dc.pm10 != null) {
265 profile.numMeters = 1;
266 } else if (dc.em0 != null) {
267 profile.numMeters = 3;
268 } else if (dc.em10 != null) {
269 profile.numMeters = 2;
272 if (profile.numMeters > 0) {
273 profile.status.meters = new ArrayList<>();
274 profile.status.emeters = new ArrayList<>();
275 relayStatus.meters = new ArrayList<>();
277 for (int i = 0; i < profile.numMeters; i++) {
278 profile.status.meters.add(new ShellySettingsMeter());
279 profile.status.emeters.add(new ShellySettingsEMeter());
280 relayStatus.meters.add(new ShellySettingsMeter());
284 if (profile.isRoller) {
285 profile.status.rollers = new ArrayList<>();
286 for (int i = 0; i < profile.numRollers; i++) {
287 ShellyRollerStatus rs = new ShellyRollerStatus();
288 profile.status.rollers.add(rs);
289 rollerStatus.add(rs);
293 if (profile.isDimmer) {
294 profile.settings.dimmers = new ArrayList<>();
295 profile.settings.dimmers.add(new ShellySettingsDimmer());
296 profile.status.dimmers = new ArrayList<>();
297 profile.status.dimmers.add(new ShellyShortLightStatus());
298 fillDimmerSettings(profile, dc);
300 profile.status.lights = profile.isBulb ? new ArrayList<>() : null;
301 profile.status.thermostats = profile.isTRV ? new ArrayList<>() : null;
303 if (profile.hasBattery) {
304 profile.settings.sleepMode = new ShellySensorSleepMode();
305 profile.settings.sleepMode.unit = "m";
306 profile.settings.sleepMode.period = dc.sys.sleep != null ? dc.sys.sleep.wakeupPeriod / 60 : 720;
307 checkSetWsCallback();
310 if (dc.led != null) {
311 profile.settings.ledStatusDisable = !getBool(dc.led.sysLedEnable);
312 profile.settings.ledPowerDisable = "off".equals(getString(dc.led.powerLed));
315 profile.initialized = true;
317 getStatus(); // make sure profile.status is initialized (e.g,. relay/meter status)
318 asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device
321 if (profile.alwaysOn && config.enableBluGateway != null && dc.ble != null) {
322 logger.debug("{}: BLU Gateway support is {} for this device", thingName,
323 config.enableBluGateway ? "enabled" : "disabled");
324 if (config.enableBluGateway) {
325 boolean bluetooth = getBool(dc.ble.enable);
326 boolean observer = dc.ble.observer != null && getBool(dc.ble.observer.enable);
328 logger.warn("{}: Bluetooth will be enabled to activate BLU Gateway mode", thingName);
331 logger.warn("{}: Shelly Cloud Bluetooth Gateway conflicts with openHAB, disabling it",
334 boolean restart = false;
335 if (!bluetooth || observer) {
336 logger.info("{}: Setup openHAB BLU Gateway", thingName);
337 restart = setBluetooth(true);
340 installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway && bluetooth);
343 logger.info("{}: Restart device to activate BLU Gateway", thingName);
345 getThing().reinitializeThing();
349 } catch (ShellyApiException e) {
350 logger.debug("{}: Device config failed", thingName, e);
357 private void fillWiFiSta(@Nullable Shelly2DeviceConfigSta from, ShellySettingsWiFiNetwork to) {
358 to.enabled = from != null && !getString(from.ssid).isEmpty();
362 to.mask = from.netmask;
363 to.dns = from.nameserver;
367 private void checkSetWsCallback() throws ShellyApiException {
368 Shelly2ConfigParms wsConfig = apiRequest(SHELLYRPC_METHOD_WSGETCONFIG, null, Shelly2ConfigParms.class);
369 String url = "ws://" + config.localIp + ":" + config.localPort + "/shelly/wsevent";
370 if (!config.localIp.isEmpty() && !getBool(wsConfig.enable)
371 || !url.equalsIgnoreCase(getString(wsConfig.server))) {
372 logger.debug("{}: A battery device was detected without correct callback, fix it", thingName);
373 wsConfig.enable = true;
374 wsConfig.server = url;
375 Shelly2RpcRequest request = new Shelly2RpcRequest();
377 request.method = SHELLYRPC_METHOD_WSSETCONFIG;
378 request.params.config = wsConfig;
379 Shelly2WsConfigResponse response = apiRequest(SHELLYRPC_METHOD_WSSETCONFIG, request.params,
380 Shelly2WsConfigResponse.class);
381 if (response.result != null && response.result.restartRequired) {
382 logger.info("{}: WebSocket callback was updated, device is restarting", thingName);
383 getThing().getApi().deviceReboot();
384 getThing().reinitializeThing();
389 protected void installScript(String script, boolean install) throws ShellyApiException {
391 ShellyScriptListResponse scriptList = apiRequest(
392 new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_LIST), ShellyScriptListResponse.class);
397 logger.debug("{}: Install or restart script {} on Shelly Device", thingName, script);
399 boolean running = false, upload = false;
400 for (ShellyScriptListEntry s : scriptList.scripts) {
401 if (s.name.startsWith(script)) {
404 logger.debug("{}: Script {} is already installed, id={}", thingName, script, ourId);
411 startScript(ourId, false);
412 enableScript(script, false);
414 logger.debug("{}: Script {} was disabledd, id={}", thingName, script, ourId);
419 // get script code from bundle resources
420 String file = BUNDLE_RESOURCE_SCRIPTS + "/" + script;
421 ClassLoader cl = Shelly2ApiRpc.class.getClassLoader();
423 try (InputStream inputStream = cl.getResourceAsStream(file)) {
424 if (inputStream != null) {
425 code = new BufferedReader(new InputStreamReader(inputStream)).lines()
426 .collect(Collectors.joining("\n"));
428 } catch (IOException | UncheckedIOException e) {
429 logger.debug("{}: Installation of script {} failed: Unable to read {} from bundle resources!",
430 thingName, script, file, e);
434 boolean restart = false;
436 // script not installed -> install it
440 // verify that the same code version is active (avoid unnesesary flash updates)
441 ShellyScriptResponse rsp = apiRequest(
442 new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_GETCODE).withId(ourId),
443 ShellyScriptResponse.class);
444 if (!rsp.data.trim().equals(code.trim())) {
445 logger.debug("{}: A script version was found, update to newest one", thingName);
448 logger.debug("{}: Same script version was found, restart", thingName);
451 } catch (ShellyApiException e) {
452 logger.debug("{}: Unable to read current script code -> force update (deviced returned: {})",
453 thingName, e.getMessage());
458 if (restart || (running && upload)) {
459 // first stop running script
460 startScript(ourId, false);
463 if (upload && ourId != -1) {
464 // Delete existing script
469 logger.debug("{}: Script will be installed...", thingName);
471 // Create new script, get id
472 ShellyScriptResponse rsp = apiRequest(
473 new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_CREATE).withName(script),
474 ShellyScriptResponse.class);
476 logger.debug("{}: Script has been created, id={}", thingName, ourId);
481 // Put script code for generated id
482 ShellyScriptPutCodeParams parms = new ShellyScriptPutCodeParams();
484 parms.append = false;
485 int length = code.length(), processed = 0, chunk = 1;
487 int nextlen = Math.min(1024, length - processed);
488 parms.code = code.substring(processed, processed + nextlen);
489 logger.debug("{}: Uploading chunk {} of script (total {} chars, {} processed)", thingName, chunk,
491 apiRequest(SHELLYRPC_METHOD_SCRIPT_PUTCODE, parms, String.class);
492 processed += nextlen;
495 } while (processed < length);
498 if (enableScript(script, true) && upload) {
499 logger.info("{}: Script {} was {} installed successful", thingName, thingName, script);
503 running = startScript(ourId, true);
504 logger.debug("{}: Script {} {}", thingName, script,
505 running ? "was successfully started" : "failed to start");
507 } catch (ShellyApiException e) {
508 ShellyApiResult res = e.getApiResult();
509 if (res.httpCode == HttpStatus.NOT_FOUND_404) { // Shely 4Pro
510 logger.debug("{}: Script {} was not installed, device doesn't support scripts", thingName, script);
512 logger.debug("{}: Unable to install script {}: {}", thingName, script, res.toString());
517 private boolean startScript(int ourId, boolean start) {
520 apiRequest(new Shelly2RpcRequest()
521 .withMethod(start ? SHELLYRPC_METHOD_SCRIPT_START : SHELLYRPC_METHOD_SCRIPT_STOP)
524 } catch (ShellyApiException e) {
530 private boolean enableScript(String script, boolean enable) {
532 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
533 params.config.name = script;
534 params.config.enable = enable;
535 apiRequest(SHELLYRPC_METHOD_SCRIPT_SETCONFIG, params, String.class);
537 } catch (ShellyApiException e) {
538 logger.debug("{}: Unable to enable script {}", thingName, script, e);
543 private boolean deleteScript(int id) {
545 throw new IllegalArgumentException("Invalid Script Id");
548 logger.debug("{}: Delete existing script with id{}", thingName, id);
549 apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SCRIPT_DELETE).withId(id));
551 } catch (ShellyApiException e) {
552 logger.debug("{}: Unable to delete script with id {}", thingName, id);
558 public void onConnect(String deviceIp, boolean connected) {
559 if (thing == null && thingTable != null) {
560 thing = thingTable.getThing(deviceIp);
561 logger.debug("{}: Get thing from thingTable", thingName);
566 public void onNotifyStatus(Shelly2RpcNotifyStatus message) {
567 logger.debug("{}: NotifyStatus update received: {}", thingName, gson.toJson(message));
569 ShellyThingInterface t = thing;
571 logger.debug("{}: No matching thing on NotifyStatus for {}, ignore (src={}, dst={}, discovery={})",
572 thingName, thingName, message.src, message.dst, discovery);
575 if (t.isStopping()) {
576 logger.debug("{}: Thing is shutting down, ignore WebSocket message", thingName);
579 if (!t.isThingOnline() && t.getThingStatusDetail() != ThingStatusDetail.CONFIGURATION_PENDING) {
580 logger.debug("{}: Thing is not in online state/connectable, ignore NotifyStatus", thingName);
584 getThing().incProtMessages();
585 if (message.error != null) {
586 if (message.error.code == HttpStatus.UNAUTHORIZED_401 && !getString(message.error.message).isEmpty()) {
587 // Save nonce for notification
588 Shelly2AuthChallenge auth = gson.fromJson(message.error.message, Shelly2AuthChallenge.class);
589 if (auth != null && auth.realm == null) {
590 logger.debug("{}: Authentication data received: {}", thingName, message.error.message);
594 logger.debug("{}: Error status received - {} {}", thingName, message.error.code,
595 message.error.message);
600 Shelly2NotifyStatus params = message.params;
601 if (params != null) {
602 if (getThing().getThingStatusDetail() != ThingStatusDetail.FIRMWARE_UPDATING) {
603 getThing().setThingOnline();
606 boolean updated = false;
607 ShellyDeviceProfile profile = getProfile();
608 ShellySettingsStatus status = profile.status;
609 if (params.sys != null) {
610 if (getBool(params.sys.restartRequired)) {
611 logger.warn("{}: Device requires restart to activate changes", thingName);
613 status.uptime = params.sys.uptime;
615 status.temperature = SHELLY_API_INVTEMP; // mark invalid
616 updated |= fillDeviceStatus(status, message.params, true);
617 if (getDouble(status.temperature) == SHELLY_API_INVTEMP) {
618 // no device temp available
619 status.temperature = null;
621 if (status.tmp != null) {
622 updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
623 toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
627 profile.status = status;
629 getThing().restartWatchdog();
632 } catch (ShellyApiException e) {
633 logger.debug("{}: Unable to process status update", thingName, e);
639 public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
641 logger.debug("{}: NotifyEvent received: {}", thingName, gson.toJson(message));
642 ShellyDeviceProfile profile = getProfile();
644 getThing().incProtMessages();
645 getThing().restartWatchdog();
647 for (Shelly2NotifyEvent e : message.params.events) {
649 case SHELLY2_EVENT_BTNUP:
650 case SHELLY2_EVENT_BTNDOWN:
651 String bgroup = getProfile().getInputGroup(e.id);
652 updateChannel(bgroup, CHANNEL_INPUT + profile.getInputSuffix(e.id),
653 getOnOff(SHELLY2_EVENT_BTNDOWN.equals(getString(e.event))));
654 getThing().triggerButton(profile.getInputGroup(e.id), e.id,
655 mapValue(MAP_INPUT_EVENT_ID, e.event));
658 case SHELLY2_EVENT_1PUSH:
659 case SHELLY2_EVENT_2PUSH:
660 case SHELLY2_EVENT_3PUSH:
661 case SHELLY2_EVENT_LPUSH:
662 case SHELLY2_EVENT_SLPUSH:
663 case SHELLY2_EVENT_LSPUSH:
664 if (e.id < profile.numInputs) {
665 ShellyInputState input = relayStatus.inputs.get(e.id);
666 input.event = getString(MAP_INPUT_EVENT_TYPE.get(e.event));
667 input.eventCount = getInteger(input.eventCount) + 1;
668 relayStatus.inputs.set(e.id, input);
669 profile.status.inputs.set(e.id, input);
671 String group = getProfile().getInputGroup(e.id);
672 updateChannel(group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(e.id),
673 getStringType(input.event));
674 updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(e.id),
675 getDecimal(input.eventCount));
676 getThing().triggerButton(profile.getInputGroup(e.id), e.id,
677 mapValue(MAP_INPUT_EVENT_ID, e.event));
680 case SHELLY2_EVENT_CFGCHANGED:
681 logger.debug("{}: Configuration update detected, re-initialize", thingName);
682 getThing().requestUpdates(1, true); // refresh config
685 case SHELLY2_EVENT_OTASTART:
686 logger.debug("{}: Firmware update started: {}", thingName, getString(e.msg));
687 getThing().postEvent(e.event, true);
688 getThing().setThingOffline(ThingStatusDetail.FIRMWARE_UPDATING,
689 "offline.status-error-fwupgrade");
691 case SHELLY2_EVENT_OTAPROGRESS:
692 logger.debug("{}: Firmware update in progress: {}", thingName, getString(e.msg));
693 getThing().postEvent(e.event, false);
695 case SHELLY2_EVENT_OTADONE:
696 logger.debug("{}: Firmware update completed: {}", thingName, getString(e.msg));
697 getThing().setThingOffline(ThingStatusDetail.CONFIGURATION_PENDING,
698 "offline.status-error-restarted");
699 getThing().requestUpdates(1, true); // refresh config
701 case SHELLY2_EVENT_SLEEP:
702 logger.debug("{}: Connection terminated, e.g. device in sleep mode", thingName);
704 case SHELLY2_EVENT_WIFICONNFAILED:
705 logger.debug("{}: WiFi connect failed, check setup, reason {}", thingName,
706 getInteger(e.reason));
707 getThing().postEvent(e.event, false);
709 case SHELLY2_EVENT_WIFIDISCONNECTED:
710 logger.debug("{}: WiFi disconnected, reason {}", thingName, getInteger(e.reason));
711 getThing().postEvent(e.event, false);
714 logger.debug("{}: Event {} was not handled", thingName, e.event);
717 } catch (ShellyApiException e) {
718 logger.debug("{}: Unable to process event", thingName, e);
724 public void onMessage(String message) {
725 logger.debug("{}: Unexpected RPC message received: {}", thingName, message);
730 public void onClose(int statusCode, String description) {
732 String reason = getString(description);
733 logger.debug("{}: WebSocket connection closed, status = {}/{}", thingName, statusCode, reason);
734 if ("Bye".equalsIgnoreCase(reason)) {
735 logger.debug("{}: Device went to sleep mode", thingName);
736 } else if (statusCode == StatusCode.ABNORMAL && !discovery && getProfile().alwaysOn) {
737 // e.g. device rebooted
738 thingOffline("WebSocket connection closed abnormal");
740 } catch (ShellyApiException e) {
741 logger.debug("{}: Exception on onClose()", thingName, e);
747 public void onError(Throwable cause) {
748 logger.debug("{}: WebSocket error: {}", thingName, cause.getMessage());
749 if (thing != null && thing.getProfile().alwaysOn) {
750 thingOffline("WebSocket error");
754 private void thingOffline(String reason) {
755 if (thing != null) { // do not reinit of battery powered devices with sleep mode
756 thing.setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-unexpected-error",
762 public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
763 Shelly2DeviceSettings device = callApi("/shelly", Shelly2DeviceSettings.class);
764 ShellySettingsDevice info = new ShellySettingsDevice();
765 info.hostname = getString(device.id);
766 info.name = getString(device.name);
767 info.fw = getString(device.fw);
768 info.type = getString(device.model);
769 info.mac = getString(device.mac);
770 info.auth = getBool(device.auth);
771 info.gen = getInteger(device.gen);
772 info.mode = mapValue(MAP_PROFILE, device.profile);
777 public ShellySettingsStatus getStatus() throws ShellyApiException {
778 ShellyDeviceProfile profile = getProfile();
779 ShellySettingsStatus status = profile.status;
780 Shelly2DeviceStatusResult ds = apiRequest(SHELLYRPC_METHOD_GETSTATUS, null, Shelly2DeviceStatusResult.class);
781 status.time = ds.sys.time;
782 status.uptime = ds.sys.uptime;
783 status.cloud.connected = getBool(ds.cloud.connected);
784 status.mqtt.connected = getBool(ds.mqtt.connected);
785 status.wifiSta.ssid = getString(ds.wifi.ssid);
786 status.wifiSta.enabled = !status.wifiSta.ssid.isEmpty();
787 status.wifiSta.ip = getString(ds.wifi.staIP);
788 status.wifiSta.rssi = getInteger(ds.wifi.rssi);
789 status.fsFree = ds.sys.fsFree;
790 status.fsSize = ds.sys.fsSize;
791 status.discoverable = getBool(profile.settings.discoverable);
793 if (ds.sys.wakeupPeriod != null) {
794 profile.settings.sleepMode.period = ds.sys.wakeupPeriod / 60;
797 if (ds.sys.availableUpdates != null) {
798 status.update.hasUpdate = ds.sys.availableUpdates.stable != null;
799 if (ds.sys.availableUpdates.stable != null) {
800 status.update.newVersion = ShellyDeviceProfile
801 .extractFwVersion(getString(ds.sys.availableUpdates.stable.version));
802 status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.newVersion) < 0;
804 if (ds.sys.availableUpdates.beta != null) {
805 status.update.betaVersion = ShellyDeviceProfile
806 .extractFwVersion(getString(ds.sys.availableUpdates.beta.version));
807 status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.betaVersion) < 0;
811 if (ds.sys.wakeUpReason != null && ds.sys.wakeUpReason.boot != null)
814 List<Object> values = new ArrayList<>();
815 String boot = getString(ds.sys.wakeUpReason.boot);
816 String cause = getString(ds.sys.wakeUpReason.cause);
818 // Index 0 is aggregated status, 1 boot, 2 cause
819 String reason = boot.equals(SHELLY2_WAKEUPO_BOOT_RESTART) ? ALARM_TYPE_RESTARTED : cause;
821 values.add(ds.sys.wakeUpReason.boot);
822 values.add(ds.sys.wakeUpReason.cause);
823 getThing().updateWakeupReason(values);
826 fillDeviceStatus(status, ds, false);
827 if (getBool(profile.settings.rangeExtender)) {
829 // Get List of AP clients
830 profile.status.rangeExtender = apiRequest(SHELLYRPC_METHOD_WIFILISTAPCLIENTS, null,
831 Shelly2APClientList.class);
832 logger.debug("{}: Range extender is enabled, {} clients connected", thingName,
833 profile.status.rangeExtender.apClients.size());
834 } catch (ShellyApiException e) {
835 logger.debug("{}: Range extender is enabled, but unable to read AP client list", thingName, e);
836 profile.settings.rangeExtender = false;
844 public void setSleepTime(int value) throws ShellyApiException {
848 public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException {
849 if (getProfile().status.wifiSta.ssid == null) {
850 // Update status when not yet initialized
857 public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
858 ShellyDeviceProfile profile = getProfile();
860 if (profile.settings.relays != null) {
861 Integer rid = profile.settings.relays.get(id).id;
866 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
868 params.on = SHELLY_API_ON.equals(turnMode);
869 apiRequest(SHELLYRPC_METHOD_SWITCH_SET, params, String.class);
873 public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException {
874 if (rollerIndex < rollerStatus.size()) {
875 return rollerStatus.get(rollerIndex);
877 throw new IllegalArgumentException("Invalid rollerIndex on getRollerStatus");
881 public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException {
882 String operation = "";
884 case SHELLY_ALWD_ROLLER_TURN_OPEN:
885 operation = SHELLY2_COVER_CMD_OPEN;
887 case SHELLY_ALWD_ROLLER_TURN_CLOSE:
888 operation = SHELLY2_COVER_CMD_CLOSE;
890 case SHELLY_ALWD_ROLLER_TURN_STOP:
891 operation = SHELLY2_COVER_CMD_STOP;
895 apiRequest(new Shelly2RpcRequest().withMethod("Cover." + operation).withId(relayIndex));
899 public void setRollerPos(int relayIndex, int position) throws ShellyApiException {
901 new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_COVER_SETPOS).withId(relayIndex).withPos(position));
905 public ShellyStatusLight getLightStatus() throws ShellyApiException {
906 throw new ShellyApiException("API call not implemented");
910 public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException {
911 ShellyShortLightStatus status = new ShellyShortLightStatus();
912 Shelly2DeviceStatusLight ls = apiRequest(
913 new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_LIGHT_STATUS).withId(index),
914 Shelly2DeviceStatusLight.class);
915 status.ison = ls.output;
916 status.hasTimer = ls.timerStartedAt != null;
917 status.timerDuration = getDuration(ls.timerStartedAt, ls.timerDuration);
918 if (ls.brightness != null) {
919 status.brightness = ls.brightness.intValue();
925 public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException {
926 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
928 params.brightness = brightness;
929 params.on = brightness > 0;
930 apiRequest(SHELLYRPC_METHOD_LIGHT_SET, params, String.class);
934 public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException {
935 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
937 params.on = turnMode.equals(SHELLY_API_ON);
938 apiRequest(SHELLYRPC_METHOD_LIGHT_SET, params, String.class);
939 return getLightStatus(id);
943 public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
948 public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
949 ShellyDeviceProfile profile = getProfile();
950 boolean isLight = profile.isLight || profile.isDimmer;
951 String method = isLight ? SHELLYRPC_METHOD_LIGHT_SETCONFIG : SHELLYRPC_METHOD_SWITCH_SETCONFIG;
952 String component = isLight ? "Light" : "Switch";
953 Shelly2RpcRequest req = new Shelly2RpcRequest().withMethod(method).withId(index);
954 req.params.withConfig();
955 req.params.config.name = component + index;
956 if (timerName.equals(SHELLY_TIMER_AUTOON)) {
957 req.params.config.autoOn = value > 0;
958 req.params.config.autoOnDelay = value;
960 req.params.config.autoOff = value > 0;
961 req.params.config.autoOffDelay = value;
967 public void setLedStatus(String ledName, boolean value) throws ShellyApiException {
968 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
970 if (ledName.equals(SHELLY_LED_STATUS_DISABLE)) {
971 params.config.sysLedEnable = value;
972 } else if (ledName.equals(SHELLY_LED_POWER_DISABLE)) {
973 params.config.powerLed = value ? SHELLY2_POWERLED_OFF : SHELLY2_POWERLED_MATCH;
975 throw new ShellyApiException("API call not implemented for this LED type");
977 apiRequest(SHELLYRPC_METHOD_LED_SETCONFIG, params, Shelly2WsConfigResult.class);
981 public void resetMeterTotal(int id) throws ShellyApiException {
982 apiRequest(new Shelly2RpcRequest()
983 .withMethod(getProfile().is3EM ? SHELLYRPC_METHOD_EMDATARESET : SHELLYRPC_METHOD_EM1DATARESET)
988 public void muteSmokeAlarm(int index) throws ShellyApiException {
989 apiRequest(new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SMOKE_MUTE).withId(index));
993 public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
994 return new ShellySettingsLogin();
998 public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
999 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
1000 params.user = "admin";
1001 params.realm = config.serviceName;
1002 params.ha1 = sha256(params.user + ":" + params.realm + ":" + password);
1003 apiRequest(SHELLYRPC_METHOD_AUTHSET, params, String.class);
1005 ShellySettingsLogin res = new ShellySettingsLogin();
1007 res.username = params.user;
1008 res.password = password;
1009 return new ShellySettingsLogin();
1013 public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
1014 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
1015 params.config.ap = new Shelly2DeviceConfigAp();
1016 params.config.ap.rangeExtender = new Shelly2DeviceConfigApRE();
1017 params.config.ap.rangeExtender.enable = enable;
1018 Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_WIFISETCONG, params, Shelly2WsConfigResult.class);
1019 return res.restartRequired;
1023 public boolean setEthernet(boolean enable) throws ShellyApiException {
1024 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
1025 params.config.enable = enable;
1026 Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_ETHSETCONG, params, Shelly2WsConfigResult.class);
1027 return res.restartRequired;
1031 public boolean setBluetooth(boolean enable) throws ShellyApiException {
1032 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
1033 params.config.enable = enable;
1035 params.config.observer = new Shelly2DevConfigBleObserver();
1036 params.config.observer.enable = false;
1038 Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_BLESETCONG, params, Shelly2WsConfigResult.class);
1039 return res.restartRequired;
1043 public void deviceReboot() throws ShellyApiException {
1044 apiRequest(SHELLYRPC_METHOD_REBOOT, null, String.class);
1048 public String factoryReset() throws ShellyApiException {
1049 return apiRequest(SHELLYRPC_METHOD_RESET, null, String.class);
1053 public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException {
1054 Shelly2DeviceStatusSysAvlUpdate status = apiRequest(SHELLYRPC_METHOD_CHECKUPD, null,
1055 Shelly2DeviceStatusSysAvlUpdate.class);
1056 ShellyOtaCheckResult result = new ShellyOtaCheckResult();
1057 result.status = status.stable != null || status.beta != null ? "new" : "ok";
1062 public ShellySettingsUpdate firmwareUpdate(String fwurl) throws ShellyApiException {
1063 ShellySettingsUpdate res = new ShellySettingsUpdate();
1064 boolean prod = fwurl.contains("update");
1065 boolean beta = fwurl.contains("beta");
1067 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
1069 params.stage = prod ? "stable" : "beta";
1073 apiRequest(SHELLYRPC_METHOD_UPDATE, params, String.class);
1074 res.status = "Update initiated";
1079 public String setCloud(boolean enable) throws ShellyApiException {
1080 Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
1081 params.config.enable = enable;
1082 Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_CLOUDSET, params, Shelly2WsConfigResult.class);
1083 return res.restartRequired ? "restart required" : "ok";
1087 public String setDebug(boolean enabled) throws ShellyApiException {
1092 public String getDebugLog(String id) throws ShellyApiException {
1093 return ""; // Gen2 uses WS to publish debug log
1097 * The following API calls are not yet relevant, because currently there a no Plus/Pro (Gen2) devices of those
1098 * categories (e.g. bulbs)
1102 public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException {
1103 throw new ShellyApiException("API call not implemented");
1107 public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException {
1108 throw new ShellyApiException("API call not implemented");
1112 public void setLightMode(String mode) throws ShellyApiException {
1113 throw new ShellyApiException("API call not implemented");
1117 public void setValveMode(int valveId, boolean auto) throws ShellyApiException {
1118 throw new ShellyApiException("API call not implemented");
1122 public void setValvePosition(int valveId, double value) throws ShellyApiException {
1123 throw new ShellyApiException("API call not implemented");
1127 public void setValveTemperature(int valveId, double value) throws ShellyApiException {
1128 throw new ShellyApiException("API call not implemented");
1132 public void setValveProfile(int valveId, int value) throws ShellyApiException {
1133 throw new ShellyApiException("API call not implemented");
1137 public void setValveBoostTime(int valveId, int value) throws ShellyApiException {
1138 throw new ShellyApiException("API call not implemented");
1142 public void startValveBoost(int valveId, int value) throws ShellyApiException {
1143 throw new ShellyApiException("API call not implemented");
1147 public String resetStaCache() throws ShellyApiException {
1148 throw new ShellyApiException("API call not implemented");
1152 public void setActionURLs() throws ShellyApiException {
1153 // not relevant for Gen2
1157 public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
1158 // not relevant for Gen2
1159 return new ShellySettingsLogin();
1163 public String getCoIoTDescription() {
1164 return ""; // not relevant to Gen2
1168 public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
1169 throw new ShellyApiException("API call not implemented");
1173 public String setWiFiRecovery(boolean enable) throws ShellyApiException {
1174 return "failed"; // not supported by Gen2
1178 public String setApRoaming(boolean enable) throws ShellyApiException {
1179 return "false";// not supported by Gen2
1182 private void asyncApiRequest(String method) throws ShellyApiException {
1183 Shelly2RpcBaseMessage request = buildRequest(method, null);
1185 rpcSocket.sendMessage(gson.toJson(request)); // submit, result wull be async
1188 @SuppressWarnings("null")
1189 public <T> T apiRequest(String method, @Nullable Object params, Class<T> classOfT) throws ShellyApiException {
1191 Shelly2RpcBaseMessage req = buildRequest(method, params);
1193 reconnect(); // make sure WS is connected
1194 json = rpcPost(gson.toJson(req));
1195 } catch (ShellyApiException e) {
1196 ShellyApiResult res = e.getApiResult();
1197 String auth = getString(res.authChallenge);
1198 if (res.isHttpAccessUnauthorized() && !auth.isEmpty()) {
1199 String[] options = auth.split(",");
1200 authInfo = new Shelly2AuthChallenge();
1201 for (String o : options) {
1202 String key = substringBefore(o, "=").stripLeading().trim();
1203 String value = substringAfter(o, "=").replace("\"", "").trim();
1206 authInfo.authType = SHELLY2_AUTHTTYPE_DIGEST;
1209 authInfo.realm = value;
1212 // authInfo.nonce = Long.parseLong(value, 16);
1213 authInfo.nonce = value;
1216 authInfo.algorithm = value;
1220 json = rpcPost(gson.toJson(req));
1225 Shelly2RpcBaseMessage response = gson.fromJson(json, Shelly2RpcBaseMessage.class);
1226 if (response == null) {
1227 throw new IllegalArgumentException("Unable to cover API result to obhect");
1229 if (response.result != null) {
1230 // return sub element result as requested class type
1231 json = gson.toJson(gson.fromJson(json, Shelly2RpcBaseMessage.class).result);
1232 boolean isString = response.result instanceof String;
1233 return fromJson(gson, isString && "null".equalsIgnoreCase(((String) response.result)) ? "{}" : json,
1236 // return direct format
1237 return gson.fromJson(json, classOfT == String.class ? Shelly2RpcBaseMessage.class : classOfT);
1241 public <T> T apiRequest(Shelly2RpcRequest request, Class<T> classOfT) throws ShellyApiException {
1242 return apiRequest(request.method, request.params, classOfT);
1245 public void apiRequest(Shelly2RpcRequest request) throws ShellyApiException {
1246 apiRequest(request.method, request.params, Shelly2RpcBaseMessage.class);
1249 private String rpcPost(String postData) throws ShellyApiException {
1250 return httpPost(authInfo, postData);
1253 private void reconnect() throws ShellyApiException {
1254 if (!rpcSocket.isConnected()) {
1255 logger.debug("{}: Connect Rpc Socket (discovery = {})", thingName, discovery);
1256 rpcSocket.connect();
1260 private void disconnect() {
1261 if (rpcSocket.isConnected()) {
1262 logger.debug("{}: Disconnect Rpc Socket", thingName);
1264 rpcSocket.disconnect();
1267 public Shelly2RpctInterface getRpcHandler() {
1272 public void close() {
1273 if (initialized || rpcSocket.isConnected()) {
1274 logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName,
1275 rpcSocket.isConnected() ? "connected" : "disconnected", discovery);
1278 initialized = false;
1281 private void incProtErrors() {
1282 if (thing != null) {
1283 thing.incProtErrors();