2 * Copyright (c) 2010-2023 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.api1;
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.util.ShellyUtils.*;
19 import java.nio.charset.StandardCharsets;
20 import java.util.Base64;
21 import java.util.HashMap;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.shelly.internal.api.ShellyApiException;
28 import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
29 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
30 import org.openhab.binding.shelly.internal.api.ShellyHttpClient;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
33 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySendKeyList;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySenseKeyCode;
35 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
36 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLight;
37 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
38 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
39 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
40 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
41 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
42 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
43 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
44 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyThermnostat;
45 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
46 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
47 import org.openhab.core.library.unit.ImperialUnits;
48 import org.openhab.core.library.unit.SIUnits;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.google.gson.JsonSyntaxException;
55 * {@link Shelly1HttpApi} wraps the Shelly REST API and provides various low level function to access the device api
59 * @author Markus Michels - Initial contribution
62 public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterface {
63 private final Logger logger = LoggerFactory.getLogger(Shelly1HttpApi.class);
64 private final ShellyDeviceProfile profile;
66 public Shelly1HttpApi(String thingName, ShellyThingInterface thing) {
67 super(thingName, thing);
68 profile = thing.getProfile();
72 * Simple initialization - called by discovery handler
74 * @param thingName Symbolic thing name
75 * @param config Thing Configuration
76 * @param httpClient HTTP Client to be passed to ShellyHttpClient
78 public Shelly1HttpApi(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
79 super(thingName, config, httpClient);
80 this.profile = new ShellyDeviceProfile();
84 public void initialize() throws ShellyApiException {
85 profile.device = getDeviceInfo();
89 public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
90 ShellySettingsDevice info = callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class);
92 basicAuth = getBool(info.auth);
94 if (getString(info.mode).isEmpty()) { // older Gen1 Firmware
95 if (getInteger(info.numRollers) > 0) {
96 info.mode = SHELLY_CLASS_ROLLER;
97 } else if (getInteger(info.numOutputs) > 0) {
98 info.mode = SHELLY_CLASS_RELAY;
107 public String setDebug(boolean enabled) throws ShellyApiException {
108 return callApi(SHELLY_URL_SETTINGS + "?debug_enable=" + Boolean.valueOf(enabled), String.class);
112 public String getDebugLog(String id) throws ShellyApiException {
113 return callApi("/debug/" + id, String.class);
117 * Initialize the device profile
119 * @param thingType Type of DEVICE as returned from the thing properties (based on discovery)
120 * @return Initialized ShellyDeviceProfile
121 * @throws ShellyApiException
124 public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice device)
125 throws ShellyApiException {
126 if (device != null) {
127 profile.device = device;
129 if (profile.device.type == null) {
130 profile.device = getDeviceInfo();
132 String json = httpRequest(SHELLY_URL_SETTINGS);
133 if (json.contains("\"type\":\"SHDM-")) {
134 logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName);
135 json = fixDimmerJson(json);
138 // Map settings to device profile for Light and Sense
139 profile.initialize(thingType, json, profile.device);
141 // 2nd level initialization
142 profile.thingName = profile.device.hostname;
143 if (profile.isLight && (profile.numMeters == 0)) {
144 logger.debug("{}: Get number of meters from light status", thingName);
145 ShellyStatusLight status = getLightStatus();
146 profile.numMeters = status.meters != null ? status.meters.size() : 0;
148 if (profile.isSense) {
149 profile.irCodes = getIRCodeList();
150 logger.debug("{}: Sense stored key list loaded, {} entries.", thingName, profile.irCodes.size());
157 public boolean isInitialized() {
158 return profile.initialized;
162 * Get generic device settings/status. Json returned from API will be mapped to a Gson object
164 * @return Device settings/status as ShellySettingsStatus object
165 * @throws ShellyApiException
168 public ShellySettingsStatus getStatus() throws ShellyApiException {
171 json = httpRequest(SHELLY_URL_STATUS);
173 // Dimmer2 returns invalid json type for loaderror :-(
174 json = json.replace("\"loaderror\":0,", "\"loaderror\":false,")
175 .replace("\"loaderror\":1,", "\"loaderror\":true,")
176 .replace("\"tmp\":{\"value\": \"null\",", "\"tmp\":{\"value\": null,");
177 ShellySettingsStatus status = fromJson(gson, json, ShellySettingsStatus.class);
180 } catch (JsonSyntaxException e) {
181 throw new ShellyApiException("Unable to parse JSON: " + json, e);
186 public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException {
187 return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex, ShellyStatusRelay.class);
191 public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
192 callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(), String.class);
196 public void resetMeterTotal(int id) throws ShellyApiException {
197 callApi(SHELLY_URL_STATUS_EMETER + "/" + id + "?reset_totals=true", ShellyStatusRelay.class);
201 public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException {
202 return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
203 ShellyShortLightStatus.class);
207 public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException {
208 String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : "";
209 httpRequest(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness);
213 public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException {
214 String uri = SHELLY_URL_CONTROL_ROLLER + "/" + rollerIndex + "/pos";
215 return callApi(uri, ShellyRollerStatus.class);
219 public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException {
220 httpRequest(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex + "?go=" + turnMode);
224 public void setRollerPos(int relayIndex, int position) throws ShellyApiException {
225 httpRequest(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex + "?go=to_pos&roller_pos=" + position);
229 public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException {
230 return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class);
234 public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
235 ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class);
236 if (profile.isSense) {
237 // complete reported data, map C to F or vice versa: C=(F - 32) * 0.5556;
238 status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value
239 : ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(getDouble(status.tmp.value))
241 double f = (double) SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT)
242 .convert(getDouble(status.tmp.value));
243 status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value : f;
245 if ((status.charger == null) && (profile.settings.externalPower != null)) {
246 // SHelly H&T uses external_power, Sense uses charger
247 status.charger = profile.settings.externalPower != 0;
249 if (status.tmp != null && status.tmp.tC == null && status.tmp.value != null) { // Motion is is missing tC and tF
250 status.tmp.tC = getString(status.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)
251 ? ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(status.tmp.value).doubleValue()
258 public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
259 String type = SHELLY_CLASS_RELAY;
260 if (profile.isRoller) {
261 type = SHELLY_CLASS_ROLLER;
262 } else if (profile.isLight) {
263 type = SHELLY_CLASS_LIGHT;
265 String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value;
270 public void setSleepTime(int value) throws ShellyApiException {
271 httpRequest(SHELLY_URL_SETTINGS + "?sleep_time=" + value);
275 public void setValveTemperature(int valveId, int value) throws ShellyApiException {
276 httpRequest("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value);
280 public void setValveMode(int valveId, boolean auto) throws ShellyApiException {
281 String uri = "/settings/thermostat/" + valveId + "?target_t_enabled=" + (auto ? "1" : "0");
282 if (auto && profile.settings.thermostats != null) {
283 uri = uri + "&target_t=" + getDouble(profile.settings.thermostats.get(0).targetTemp.value);
285 httpRequest(uri); // percentage to open the valve
289 public void setValveProfile(int valveId, int value) throws ShellyApiException {
290 String uri = "/settings/thermostat/" + valveId + "?";
291 httpRequest(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value));
295 public void setValvePosition(int valveId, double value) throws ShellyApiException {
296 httpRequest("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve
300 public void setValveBoostTime(int valveId, int value) throws ShellyApiException {
301 httpRequest("/settings/thermostat/" + valveId + "?boost_minutes=" + value);
305 public void startValveBoost(int valveId, int value) throws ShellyApiException {
306 if (profile.settings.thermostats != null) {
307 ShellyThermnostat t = profile.settings.thermostats.get(0);
308 int minutes = value != -1 ? value : getInteger(t.boostMinutes);
309 httpRequest("/thermostat/0?boost_minutes=" + minutes);
314 public void setLedStatus(String ledName, boolean value) throws ShellyApiException {
315 httpRequest(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
318 public ShellySettingsLight getLightSettings() throws ShellyApiException {
319 return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class);
323 public ShellyStatusLight getLightStatus() throws ShellyApiException {
324 return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class);
327 public void setLightSetting(String parm, String value) throws ShellyApiException {
328 httpRequest(SHELLY_URL_SETTINGS + "?" + parm + "=" + value);
332 public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
333 return callApi(SHELLY_URL_SETTINGS + "/login", ShellySettingsLogin.class);
337 public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
338 return callApi(SHELLY_URL_SETTINGS + "/login?enabled=yes&username=" + urlEncode(user) + "&password="
339 + urlEncode(password), ShellySettingsLogin.class);
343 public String getCoIoTDescription() throws ShellyApiException {
345 return callApi("/cit/d", String.class);
346 } catch (ShellyApiException e) {
347 if (e.getApiResult().isNotFound()) {
348 return ""; // only supported by FW 1.10+
355 public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
356 return callApi(SHELLY_URL_SETTINGS + "?coiot_enable=true&coiot_peer=" + peer, ShellySettingsLogin.class);
360 public String deviceReboot() throws ShellyApiException {
361 return callApi(SHELLY_URL_RESTART, String.class);
365 public String factoryReset() throws ShellyApiException {
366 return callApi(SHELLY_URL_SETTINGS + "?reset=true", String.class);
370 public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException {
371 return callApi("/ota/check", ShellyOtaCheckResult.class); // nw FW 1.10+: trigger update check
375 public String setWiFiRecovery(boolean enable) throws ShellyApiException {
376 return callApi(SHELLY_URL_SETTINGS + "?wifirecovery_reboot_enabled=" + (enable ? "true" : "false"),
377 String.class); // FW 1.10+: Enable auto-restart on WiFi problems
381 public String setApRoaming(boolean enable) throws ShellyApiException { // FW 1.10+: Enable AP Roadming
382 return callApi(SHELLY_URL_SETTINGS + "?ap_roaming_enabled=" + (enable ? "true" : "false"), String.class);
386 public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
391 public boolean setEthernet(boolean enable) throws ShellyApiException {
396 public boolean setBluetooth(boolean enable) throws ShellyApiException {
401 public String resetStaCache() throws ShellyApiException { // FW 1.10+: Reset cached STA/AP list and to a rescan
402 return callApi("/sta_cache_reset", String.class);
406 public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException {
407 return callApi("/ota?" + uri, ShellySettingsUpdate.class);
411 public String setCloud(boolean enabled) throws ShellyApiException {
412 return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class);
416 * Change between White and Color Mode
419 * @throws ShellyApiException
422 public void setLightMode(String mode) throws ShellyApiException {
423 if (!mode.isEmpty() && !profile.device.mode.equals(mode)) {
424 setLightSetting(SHELLY_API_MODE, mode);
425 profile.device.mode = mode;
426 profile.inColor = profile.isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
431 * Set a single light parameter
433 * @param lightIndex Index of the light, usually 0 for Bulb and 0..3 for RGBW2.
434 * @param parm Name of the parameter (see API spec)
435 * @param value The value
436 * @throws ShellyApiException
439 public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException {
440 // Bulb, RGW2: /<color mode>/<light id>?parm?value
441 // Dimmer: /light/<light id>?parm=value
442 httpRequest(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value);
446 public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException {
447 String url = getControlUriPrefix(lightIndex) + "?";
449 for (String key : parameters.keySet()) {
453 url = url + key + "=" + parameters.get(key);
460 * Retrieve the IR Code list from the Shelly Sense device. The list could be customized by the user. It defines the
461 * symbolic key code, which gets
462 * map into a PRONTO code
464 * @return Map of key codes
465 * @throws ShellyApiException
467 public Map<String, String> getIRCodeList() throws ShellyApiException {
468 String result = httpRequest(SHELLY_URL_LIST_IR);
469 // take pragmatic approach to make the returned JSon into named arrays for Gson parsing
470 String keyList = substringAfter(result, "[");
471 keyList = substringBeforeLast(keyList, "]");
472 keyList = keyList.replaceAll(java.util.regex.Pattern.quote("\",\""), "\", \"name\": \"");
473 keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":");
474 keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} ");
475 String json = "{\"key_codes\" : [" + keyList + "] }";
476 ShellySendKeyList codes = fromJson(gson, json, ShellySendKeyList.class);
477 Map<String, String> list = new HashMap<>();
478 for (ShellySenseKeyCode key : codes.keyCodes) {
480 list.put(key.id, key.name);
487 * Sends an IR key code to the Shelly Sense.
489 * @param keyCode A keyCoud could be a symbolic name (as defined in the key map on the device) or a PRONTO Code in
490 * plain or hex64 format
492 * @throws ShellyApiException
493 * @throws IllegalArgumentException
496 public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
498 if (profile.irCodes.containsKey(keyCode)) {
499 type = SHELLY_IR_CODET_STORED;
500 } else if ((keyCode.length() > 4) && keyCode.contains(" ")) {
501 type = SHELLY_IR_CODET_PRONTO;
503 type = SHELLY_IR_CODET_PRONTO_HEX;
505 String url = SHELLY_URL_SEND_IR + "?type=" + type;
506 if (type.equals(SHELLY_IR_CODET_STORED)) {
507 url = url + "&" + "id=" + keyCode;
508 } else if (type.equals(SHELLY_IR_CODET_PRONTO)) {
509 String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8));
510 url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code;
511 } else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) {
512 url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode;
517 public void setSenseSetting(String setting, String value) throws ShellyApiException {
518 httpRequest(SHELLY_URL_SETTINGS + "?" + setting + "=" + value);
522 * Set event callback URLs. Depending on the device different event types are supported. In fact all of them will be
523 * redirected to the binding's servlet and act as a trigger to schedule a status update
525 * @throws ShellyApiException
528 public void setActionURLs() throws ShellyApiException {
531 setSensorEventUrls();
534 private void setRelayEvents() throws ShellyApiException {
535 if (profile.settings.relays != null) {
536 int num = profile.isRoller ? profile.numRollers : profile.numRelays;
537 for (int i = 0; i < num; i++) {
543 private void setDimmerEvents() throws ShellyApiException {
544 if (profile.settings.dimmers != null) {
545 int sz = profile.settings.dimmers.size();
546 for (int i = 0; i < sz; i++) {
549 } else if (profile.isLight) {
555 public void muteSmokeAlarm(int id) throws ShellyApiException {
556 throw new ShellyApiException("Request not supported");
560 * Set sensor Action URLs
562 * @throws ShellyApiException
564 private void setSensorEventUrls() throws ShellyApiException, ShellyApiException {
565 if (profile.isSensor) {
566 logger.debug("{}: Set Sensor Reporting URL", thingName);
567 setEventUrl(config.eventsSensorReport, SHELLY_EVENT_SENSORREPORT, SHELLY_EVENT_DARK, SHELLY_EVENT_TWILIGHT,
568 SHELLY_EVENT_FLOOD_DETECTED, SHELLY_EVENT_FLOOD_GONE, SHELLY_EVENT_OPEN, SHELLY_EVENT_CLOSE,
569 SHELLY_EVENT_VIBRATION, SHELLY_EVENT_ALARM_MILD, SHELLY_EVENT_ALARM_HEAVY, SHELLY_EVENT_ALARM_OFF,
570 SHELLY_EVENT_TEMP_OVER, SHELLY_EVENT_TEMP_UNDER);
575 * Set/delete Relay/Roller/Dimmer Action URLs
577 * @param index Device Index (0-based)
578 * @throws ShellyApiException
580 private void setEventUrls(Integer index) throws ShellyApiException {
581 if (profile.isRoller) {
582 setEventUrl(EVENT_TYPE_ROLLER, 0, config.eventsRoller, SHELLY_EVENT_ROLLER_OPEN, SHELLY_EVENT_ROLLER_CLOSE,
583 SHELLY_EVENT_ROLLER_STOP);
584 } else if (profile.isDimmer) {
586 setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsButton, SHELLY_EVENT_BTN1_ON, SHELLY_EVENT_BTN1_OFF,
587 SHELLY_EVENT_BTN2_ON, SHELLY_EVENT_BTN2_OFF);
588 setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH1, SHELLY_EVENT_LONGPUSH1,
589 SHELLY_EVENT_SHORTPUSH2, SHELLY_EVENT_LONGPUSH2);
592 setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
593 } else if (profile.hasRelays) {
594 // Standard relays: btn_xxx, out_xxx, short/longpush URLs
595 setEventUrl(EVENT_TYPE_RELAY, index, config.eventsButton, SHELLY_EVENT_BTN_ON, SHELLY_EVENT_BTN_OFF);
596 setEventUrl(EVENT_TYPE_RELAY, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH, SHELLY_EVENT_LONGPUSH);
597 setEventUrl(EVENT_TYPE_RELAY, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
598 } else if (profile.isLight) {
600 setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
604 private void setEventUrl(boolean enabled, String... eventTypes) throws ShellyApiException {
605 if (config.localIp.isEmpty()) {
606 throw new ShellyApiException(thingName + ": Local IP address was not detected, can't build Callback URL");
608 for (String eventType : eventTypes) {
609 if (profile.containsEventUrl(eventType)) {
610 // H&T adds the type=xx to report_url itself, so we need to ommit here
611 String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType;
612 String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
613 String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
614 + profile.thingName + "/" + eclass + urlParm;
615 String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
616 String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
617 if (!enabled && !profile.settingsJson.contains(testUrl)) {
618 // Don't set URL to null when the current one doesn't point to this OH
619 // Don't interfere with a 3rd party App
622 if (!profile.settingsJson.contains(testUrl)) {
623 // Current Action URL is != new URL
624 logger.debug("{}: Set new url for event type {}: {}", thingName, eventType, newUrl);
625 httpRequest(SHELLY_URL_SETTINGS + "?" + mkEventUrl(eventType) + "=" + urlEncode(newUrl));
631 private void setEventUrl(String deviceClass, Integer index, boolean enabled, String... eventTypes)
632 throws ShellyApiException {
633 for (String eventType : eventTypes) {
634 if (profile.containsEventUrl(eventType)) {
635 String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
636 + profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
637 String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
638 String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
639 if (!enabled && !profile.settingsJson.contains(test)) {
640 // Don't set URL to null when the current one doesn't point to this OH
641 // Don't interfere with a 3rd party App
644 test = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
645 if (!profile.settingsJson.contains(test)) {
646 // Current Action URL is != new URL
647 logger.debug("{}: Set URL for type {} to {}", thingName, eventType, newUrl);
648 httpRequest(SHELLY_URL_SETTINGS + "/" + deviceClass + "/" + index + "?" + mkEventUrl(eventType)
649 + "=" + urlEncode(newUrl));
655 private static String mkEventUrl(String eventType) {
656 return eventType + SHELLY_EVENTURL_SUFFIX;
660 public String getControlUriPrefix(Integer id) {
662 if (profile.isLight || profile.isDimmer) {
663 if (profile.isDuo || profile.isDimmer) {
665 uri = SHELLY_URL_CONTROL_LIGHT;
668 uri = "/" + (profile.inColor ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
672 uri = SHELLY_URL_CONTROL_RELEAY;
674 uri = uri + "/" + id;
679 public int getTimeoutErrors() {
680 return timeoutErrors;
684 public int getTimeoutsRecovered() {
685 return timeoutsRecovered;
689 public void close() {
693 public void startScan() {