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.CHANNEL_INPUT;
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.util.ShellyUtils.*;
20 import java.util.ArrayList;
22 import java.util.Random;
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.ShellyDeviceProfile;
29 import org.openhab.binding.shelly.internal.api.ShellyHttpClient;
30 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
33 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer;
35 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
36 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
37 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
38 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
39 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRoller;
40 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
41 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
42 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
43 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
44 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
45 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtAnalogInput;
46 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtDigitalInput;
47 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtHumidity;
48 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature;
49 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature.ShellyShortTemp;
50 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtVoltage;
51 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
52 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum;
53 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorLux;
54 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRsp;
55 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover;
56 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput;
57 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch;
58 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
59 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusLight;
60 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
61 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
62 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusEm;
63 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
64 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusIlluminance;
65 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
66 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusSmoke;
67 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
68 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
69 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
70 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
71 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2StatusEm1;
72 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
73 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
74 import org.openhab.binding.shelly.internal.handler.ShellyComponents;
75 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
76 import org.openhab.core.types.State;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
81 * {@link Shelly2ApiClient} Low level part of the RPC API
83 * @author Markus Michels - Initial contribution
86 public class Shelly2ApiClient extends ShellyHttpClient {
87 private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
88 protected final Random random = new Random();
89 protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
90 protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
91 protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
92 protected @Nullable ShellyThingInterface thing;
93 protected @Nullable Shelly2AuthRsp authReq;
95 public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
96 super(thingName, thing);
100 public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
101 super(thingName, config, httpClient);
104 protected static final Map<String, String> MAP_INMODE_BTNTYPE = Map.of(//
105 SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY, //
106 SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE, //
107 SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE, //
108 SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
110 protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = Map.of(//
111 SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH, //
112 SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH, //
113 SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH, //
114 SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH, //
115 SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH, //
116 SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
118 protected static final Map<String, String> MAP_INPUT_EVENT_ID = Map.of(//
119 SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF, //
120 SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON, //
121 SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH, //
122 SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH, //
123 SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH, //
124 SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH, //
125 SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH, //
126 SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
128 protected static final Map<String, String> MAP_INPUT_MODE = Map.of(//
129 SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON, //
130 SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE, //
131 SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
133 protected static final Map<String, String> MAP_ROLLER_STATE = Map.of(//
134 SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN, //
135 SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE, //
136 SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING, // Gen2-only
137 SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING, // Gen2-only
138 SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP, //
139 SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
141 protected static final Map<String, String> MAP_PROFILE = Map.of(//
142 SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY, //
143 SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER);
145 protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
146 Shelly2GetConfigResult dc) {
147 ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
148 addRelaySettings(relays, dc.switch0);
149 addRelaySettings(relays, dc.switch1);
150 addRelaySettings(relays, dc.switch2);
151 addRelaySettings(relays, dc.switch3);
152 addRelaySettings(relays, dc.switch100);
153 return !relays.isEmpty() ? relays : null;
156 private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
157 @Nullable Shelly2DevConfigSwitch cs) {
162 ShellySettingsRelay rsettings = new ShellySettingsRelay();
163 rsettings.id = cs.id;
164 rsettings.isValid = cs.id != null;
165 rsettings.name = cs.name;
166 rsettings.ison = false;
167 rsettings.autoOn = getBool(cs.autoOn) ? cs.autoOnDelay : 0;
168 rsettings.autoOff = getBool(cs.autoOff) ? cs.autoOffDelay : 0;
169 rsettings.hasTimer = false;
170 rsettings.btnType = mapValue(MAP_INMODE_BTNTYPE, getString(cs.mode).toLowerCase());
171 relays.add(rsettings);
174 protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
175 boolean channelUpdate) throws ShellyApiException {
176 boolean updated = false;
178 if (result.temperature0 != null && result.temperature0.tC != null && !getProfile().isSensor) {
179 if (status.tmp == null) {
180 status.tmp = new ShellySensorTmp();
182 status.temperature = status.tmp.tC = result.temperature0.tC;
185 updated |= updateInputStatus(status, result, channelUpdate);
186 updated |= updateRelayStatus(status, result.switch0, channelUpdate);
187 updated |= updateRelayStatus(status, result.switch1, channelUpdate);
188 updated |= updateRelayStatus(status, result.switch2, channelUpdate);
189 updated |= updateRelayStatus(status, result.switch3, channelUpdate);
190 updated |= updateRelayStatus(status, result.switch100, channelUpdate);
191 updated |= updateRelayStatus(status, result.pm10, channelUpdate);
192 updated |= updateEmStatus(status, result.em0, channelUpdate);
193 updated |= updateEmStatus(status, result.em10, channelUpdate);
194 updated |= updateEmStatus(status, result.em11, channelUpdate);
195 updated |= updateRollerStatus(status, result.cover0, channelUpdate);
196 updated |= updateDimmerStatus(status, result.light0, channelUpdate);
198 updated |= ShellyComponents.updateMeters(getThing(), status);
201 updateHumidityStatus(sensorData, result.humidity0);
202 updateTemperatureStatus(sensorData, result.temperature0);
203 updateIlluminanceStatus(sensorData, result.illuminance0);
204 updateSmokeStatus(sensorData, result.smoke0);
205 updateBatteryStatus(sensorData, result.devicepower0);
206 updateAddonStatus(status, result);
207 updated |= ShellyComponents.updateSensors(getThing(), status);
211 private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
212 boolean channelUpdate) throws ShellyApiException {
216 ShellyDeviceProfile profile = getProfile();
218 ShellySettingsRelay rstatus;
219 ShellyShortStatusRelay sr;
220 int rIdx = getRelayIdx(profile, rs.id);
221 if (profile.hasRelays) {
223 throw new IllegalArgumentException("Update for invalid relay index");
225 rstatus = status.relays.get(rIdx);
226 sr = relayStatus.relays.get(rIdx);
228 rstatus = new ShellySettingsRelay();
229 sr = new ShellyShortStatusRelay();
233 sr.isValid = rstatus.isValid = true;
234 sr.name = rstatus.name = status.name;
235 if (rs.output != null) {
236 sr.ison = rstatus.ison = getBool(rs.output);
238 if (getDouble(rs.timerStartetAt) > 0) {
239 int duration = (int) (now() - rs.timerStartetAt);
240 sr.timerRemaining = duration;
242 if (rs.temperature != null) {
243 if (status.tmp == null) {
244 status.tmp = new ShellySensorTmp();
246 status.tmp.isValid = true;
247 status.tmp.tC = rs.temperature.tC;
248 status.tmp.tF = rs.temperature.tF;
249 status.tmp.units = "C";
250 sr.temperature = getDouble(rs.temperature.tC);
251 if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
252 status.temperature = sr.temperature;
256 if (rs.errors != null) {
257 for (String error : rs.errors) {
258 sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
259 status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
260 status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
262 sr.overtemperature = status.overtemperature;
265 ShellySettingsMeter sm = new ShellySettingsMeter();
266 ShellySettingsEMeter emeter = status.emeters != null ? status.emeters.get(rIdx) : new ShellySettingsEMeter();
267 if (rs.apower != null) {
268 sm.power = emeter.power = rs.apower;
270 if (rs.aenergy != null) {
271 // Gen2 reports Watt, needs to be converted to W/h
272 sm.total = emeter.total = rs.aenergy.total;
273 sm.counters = rs.aenergy.byMinute;
274 sm.timestamp = rs.aenergy.minuteTs;
276 if (rs.voltage != null) {
277 emeter.voltage = rs.voltage;
279 if (rs.current != null) {
280 emeter.current = rs.current;
286 if (profile.hasRelays) {
287 // Update internal structures
288 status.relays.set(rIdx, rstatus);
289 relayStatus.relays.set(rIdx, sr);
292 updateMeter(status, rIdx, sm, emeter, channelUpdate);
293 return channelUpdate && profile.hasRelays
294 ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rIdx)
298 private int getRelayIdx(ShellyDeviceProfile profile, @Nullable Integer id) {
299 if (id != null && profile.settings.relays != null) {
301 for (ShellySettingsRelay relay : profile.settings.relays) {
302 if (relay.isValid && relay.id != null && relay.id.intValue() == id.intValue()) {
311 private void updateMeter(ShellySettingsStatus status, int id, ShellySettingsMeter sm, ShellySettingsEMeter emeter,
312 boolean channelUpdate) throws ShellyApiException {
313 if (getProfile().numMeters == 0) {
316 sm.isValid = sm.power != null || sm.total != null;
317 emeter.isValid = emeter.current != null || emeter.voltage != null || emeter.power != null
318 || emeter.total != null;
319 status.meters.set(id, sm);
320 status.emeters.set(id, emeter);
321 relayStatus.meters.set(id, sm);
324 private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2StatusEm1 em, boolean channelUpdate)
325 throws ShellyApiException {
330 ShellySettingsMeter sm = new ShellySettingsMeter();
331 ShellySettingsEMeter emeter = status.emeters.get(em.id);
332 if (em.actPower != null) {
333 sm.power = emeter.power = em.actPower;
335 if (em.aptrPower != null) {
336 emeter.totalReturned = em.aptrPower;
338 if (em.voltage != null) {
339 emeter.voltage = em.voltage;
341 if (em.current != null) {
342 emeter.current = em.current;
347 // Update internal structures
348 updateMeter(status, em.id, sm, emeter, channelUpdate);
350 postAlarms(em.errors);
351 return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
354 private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusEm em,
355 boolean channelUpdate) throws ShellyApiException {
360 if (em.totalCurrent != null) {
361 status.totalCurrent = em.totalCurrent;
363 if (em.totalActPower != null) {
364 status.totalPower = em.totalActPower;
366 if (em.totalAprtPower != null) {
367 status.totalReturned = em.totalAprtPower;
370 ShellySettingsMeter sm = new ShellySettingsMeter();
371 ShellySettingsEMeter emeter = status.emeters.get(0);
372 if (em.aActPower != null) {
373 sm.power = emeter.power = em.aActPower;
375 if (em.aAprtPower != null) {
376 emeter.totalReturned = em.aAprtPower;
378 if (em.aVoltage != null) {
379 emeter.voltage = em.aVoltage;
381 if (em.aCurrent != null) {
382 emeter.current = em.aCurrent;
384 if (em.aPF != null) {
387 // Update internal structures
388 updateMeter(status, 0, sm, emeter, channelUpdate);
390 if (status.emeters.size() > 1) {
391 sm = new ShellySettingsMeter();
392 emeter = status.emeters.get(1);
393 sm.isValid = emeter.isValid = true;
394 if (em.bActPower != null) {
395 sm.power = emeter.power = em.bActPower;
397 if (em.bAprtPower != null) {
398 emeter.totalReturned = em.bAprtPower;
400 if (em.bVoltage != null) {
401 emeter.voltage = em.bVoltage;
403 if (em.bCurrent != null) {
404 emeter.current = em.bCurrent;
406 if (em.bPF != null) {
409 // Update internal structures
410 updateMeter(status, 1, sm, emeter, channelUpdate);
413 if (status.emeters.size() > 2) {
414 sm = new ShellySettingsMeter();
415 emeter = status.emeters.get(2);
416 sm.isValid = emeter.isValid = true;
417 if (em.cActPower != null) {
418 sm.power = emeter.power = em.cActPower;
420 if (em.cAprtPower != null) {
421 emeter.totalReturned = em.cAprtPower;
423 if (em.cVoltage != null) {
424 emeter.voltage = em.cVoltage;
426 if (em.cCurrent != null) {
427 emeter.current = em.cCurrent;
429 if (em.cPF != null) {
432 // Update internal structures
433 updateMeter(status, 2, sm, emeter, channelUpdate);
436 return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
439 protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
440 Shelly2GetConfigResult dc) {
441 if (dc.cover0 == null) {
445 ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
447 addRollerSettings(rollers, dc.cover0);
448 fillRollerFavorites(profile, dc);
452 private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
453 @Nullable Shelly2DevConfigCover coverConfig) {
454 if (coverConfig == null) {
458 ShellySettingsRoller settings = new ShellySettingsRoller();
459 settings.isValid = true;
460 settings.defaultState = coverConfig.initialState;
461 settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
462 settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
463 settings.swapInputs = coverConfig.swapInputs;
464 settings.maxtime = 0.0; // n/a
465 settings.maxtimeOpen = coverConfig.maxtimeOpen;
466 settings.maxtimeClose = coverConfig.maxtimeClose;
467 if (coverConfig.safetySwitch != null) {
468 settings.safetySwitch = coverConfig.safetySwitch.enable;
469 settings.safetyAction = coverConfig.safetySwitch.action;
471 if (coverConfig.obstructionDetection != null) {
472 settings.obstacleAction = coverConfig.obstructionDetection.action;
473 settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
474 settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
476 rollers.add(settings);
479 private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
480 if (dc.sys.uiData.cover != null) {
481 String[] favorites = dc.sys.uiData.cover.split(",");
482 profile.settings.favorites = new ArrayList<>();
483 for (int i = 0; i < favorites.length; i++) {
484 ShellyFavPos fav = new ShellyFavPos();
485 fav.pos = Integer.parseInt(favorites[i]);
486 fav.name = fav.pos + "%";
487 profile.settings.favorites.add(fav);
489 profile.settings.favoritesEnabled = !profile.settings.favorites.isEmpty();
490 logger.debug("{}: Roller Favorites loaded: {}", thingName,
491 profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
495 private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
496 boolean updateChannels) throws ShellyApiException {
501 ShellyRollerStatus rs = status.rollers.get(cs.id);
502 ShellySettingsMeter sm = status.meters.get(cs.id);
503 ShellySettingsEMeter emeter = status.emeters.get(cs.id);
504 rs.isValid = sm.isValid = emeter.isValid = true;
505 if (cs.state != null) {
506 if (!getString(rs.state).equals(cs.state)) {
507 logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
508 mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
510 rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
511 rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
513 if (cs.currentPos != null) {
514 rs.currentPos = cs.currentPos;
516 if (cs.moveStartedAt != null) {
517 rs.duration = (int) (now() - cs.moveStartedAt.longValue());
519 if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
520 if (status.tmp == null) {
521 status.tmp = new ShellySensorTmp();
523 status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
525 if (cs.apower != null) {
526 rs.power = sm.power = emeter.power = cs.apower;
528 if (cs.aenergy != null) {
529 sm.total = emeter.total = cs.aenergy.total;
530 sm.counters = cs.aenergy.byMinute;
531 if (cs.aenergy.minuteTs != null) {
532 sm.timestamp = (long) cs.aenergy.minuteTs;
535 if (cs.voltage != null) {
536 emeter.voltage = cs.voltage;
538 if (cs.current != null) {
539 emeter.current = cs.current;
545 rollerStatus.set(cs.id, rs);
546 status.rollers.set(cs.id, rs);
547 relayStatus.meters.set(cs.id, sm);
548 status.meters.set(cs.id, sm);
549 status.emeters.set(cs.id, emeter);
551 postAlarms(cs.errors);
552 if (rs.calibrating != null && rs.calibrating) {
553 getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
556 return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
559 protected void fillDimmerSettings(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
560 if (!profile.isDimmer || dc.light0 == null) {
564 if (profile.settings.dimmers != null) {
565 ShellySettingsDimmer ds = profile.settings.dimmers.get(0);
566 ds.autoOn = dc.light0.autoOnDelay;
567 ds.autoOff = dc.light0.autoOffDelay;
568 ds.name = dc.light0.name;
569 profile.settings.dimmers.set(0, ds);
573 private boolean updateDimmerStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusLight value,
574 boolean channelUpdate) throws ShellyApiException {
575 ShellyDeviceProfile profile = getProfile();
576 if (!profile.isDimmer || value == null) {
580 ShellyShortLightStatus ds = status.dimmers.get(0);
581 if (value.brightness != null) {
582 ds.brightness = value.brightness.intValue();
584 ds.ison = value.output;
585 ds.hasTimer = value.timerStartedAt != null;
586 ds.timerDuration = getDuration(value.timerStartedAt, value.timerDuration);
587 status.dimmers.set(0, ds);
588 return channelUpdate ? ShellyComponents.updateDimmers(getThing(), status) : false;
591 protected @Nullable Integer getDuration(@Nullable Double timerStartedAt, @Nullable Integer timerDuration) {
592 if (timerStartedAt == null || timerDuration == null) {
595 int duration = (int) (now() - timerStartedAt.longValue());
596 return duration <= timerDuration ? timerDuration - duration : 0;
600 private void updateAddonStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusResult ds)
601 throws ShellyApiException {
606 if (ds.temperature100 != null) {
607 if (status.extTemperature == null) {
608 status.extTemperature = new ShellyExtTemperature();
610 status.extTemperature.sensor1 = updateExtTempSensor(ds.temperature100);
611 status.extTemperature.sensor2 = updateExtTempSensor(ds.temperature101);
612 status.extTemperature.sensor3 = updateExtTempSensor(ds.temperature102);
613 status.extTemperature.sensor4 = updateExtTempSensor(ds.temperature103);
614 status.extTemperature.sensor5 = updateExtTempSensor(ds.temperature104);
616 if (ds.humidity100 != null) {
617 status.extHumidity = new ShellyExtHumidity(ds.humidity100.rh);
619 if (ds.voltmeter100 != null) {
620 status.extVoltage = new ShellyExtVoltage(ds.voltmeter100.voltage);
622 if (ds.input100 != null) {
623 if (ds.input100.state != null) {
624 status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
625 } else if (ds.input100.percent != null) {
626 status.extAnalogInput = new ShellyExtAnalogInput(getDouble(ds.input100.percent));
631 private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
633 ShellyShortTemp temp = new ShellyShortTemp();
634 temp.hwID = value.id.toString();
642 protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
646 if (sdata.hum == null) {
647 sdata.hum = new ShellySensorHum();
649 sdata.hum.value = getDouble(value.rh);
652 protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
656 if (sdata.tmp == null) {
657 sdata.tmp = new ShellySensorTmp();
659 sdata.tmp.isValid = true;
660 sdata.tmp.units = SHELLY_TEMP_CELSIUS;
661 sdata.tmp.tC = value.tC;
662 sdata.tmp.tF = value.tF;
665 protected void updateIlluminanceStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusIlluminance value) {
669 if (sdata.lux == null) {
670 sdata.lux = new ShellySensorLux();
672 sdata.lux.isValid = value.lux != null;
673 sdata.lux.value = value.lux;
674 sdata.lux.illumination = value.illumination;
677 protected void updateSmokeStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusSmoke value) {
681 sdata.smoke = getBool(value.alarm);
682 sdata.mute = getBool(value.mute);
685 protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
689 if (sdata.bat == null) {
690 sdata.bat = new ShellySensorBat();
693 if (value.battery != null) {
694 sdata.bat.voltage = value.battery.volt;
695 sdata.bat.value = value.battery.percent;
697 if (value.external != null) {
698 sdata.charger = value.external.present;
702 private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
703 if (errors != null) {
704 for (String e : errors) {
706 getThing().postEvent(e, false);
712 protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
713 Shelly2GetConfigResult dc) {
714 if (dc.input0 == null) {
715 return null; // device has no input
718 ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
719 addInputSettings(inputs, dc.input0);
720 addInputSettings(inputs, dc.input1);
721 addInputSettings(inputs, dc.input2);
722 addInputSettings(inputs, dc.input3);
726 private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
731 ShellySettingsInput settings = new ShellySettingsInput();
732 settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
734 inputs.add(settings);
737 protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
738 boolean updateChannels) throws ShellyApiException {
739 boolean updated = false;
740 updated |= addInputStatus(status, ds.input0, updateChannels);
741 updated |= addInputStatus(status, ds.input1, updateChannels);
742 updated |= addInputStatus(status, ds.input2, updateChannels);
743 updated |= addInputStatus(status, ds.input3, updateChannels);
744 status.inputs = relayStatus.inputs;
748 private boolean addInputStatus(ShellySettingsStatus status, @Nullable Shelly2InputStatus is, boolean updateChannels)
749 throws ShellyApiException {
753 ShellyDeviceProfile profile = getProfile();
755 if (is.id == null || is.id > profile.numInputs) {
756 logger.debug("{}: Invalid input id: {}", thingName, is.id);
760 String group = profile.getInputGroup(is.id);
761 ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
762 : new ShellyInputState();
763 boolean updated = false;
764 input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
765 if (input.event == null && profile.inButtonMode(is.id)) {
767 input.eventCount = 0;
769 if (is.percent != null) { // analogous input
770 status.extAnalogInput = new ShellyExtAnalogInput(getDouble(is.percent));
772 relayStatus.inputs.set(is.id, input);
773 if (updateChannels) {
774 updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
779 protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
780 Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
781 request.id = Math.abs(random.nextInt());
782 request.src = "openhab-" + config.localIp; // use a unique identifier;
783 request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
784 request.params = params;
785 request.auth = authReq;
789 protected String mapValue(Map<String, String> map, @Nullable String key) {
791 boolean known = key != null && !key.isEmpty() && map.containsKey(key);
792 value = known ? getString(map.get(key)) : "";
793 logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
797 protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
798 return getThing().updateChannel(group, channel, value);
801 protected ShellyThingInterface getThing() throws ShellyApiException {
802 ShellyThingInterface t = thing;
806 throw new ShellyApiException("Thing/profile not initialized!");
809 protected ShellyDeviceProfile getProfile() throws ShellyApiException {
811 return thing.getProfile();
813 throw new ShellyApiException("Unable to get profile, thing not initialized!");