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.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;
21 import java.util.HashMap;
23 import java.util.Random;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.openhab.binding.shelly.internal.api.ShellyApiException;
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.ShellyFavPos;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
33 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
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.ShellyShortStatusRelay;
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.ShellyStatusSensor.ShellyExtAnalogInput;
45 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtDigitalInput;
46 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtHumidity;
47 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature;
48 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature.ShellyShortTemp;
49 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtVoltage;
50 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
51 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum;
52 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRequest;
53 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse;
54 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover;
55 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput;
56 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch;
57 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
58 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
59 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
60 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusEm;
61 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
62 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
63 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusSmoke;
64 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
65 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
66 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
67 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
68 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
69 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
70 import org.openhab.binding.shelly.internal.handler.ShellyComponents;
71 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
72 import org.openhab.core.types.State;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
77 * {@link Shelly2ApiClient} Low level part of the RPC API
79 * @author Markus Michels - Initial contribution
82 public class Shelly2ApiClient extends ShellyHttpClient {
83 private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
84 protected final Random random = new Random();
85 protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
86 protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
87 protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
88 protected @Nullable ShellyThingInterface thing;
89 protected @Nullable Shelly2AuthRequest authReq;
91 public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
92 super(thingName, thing);
96 public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
97 super(thingName, config, httpClient);
100 protected static final Map<String, String> MAP_INMODE_BTNTYPE = new HashMap<>();
102 MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY);
103 MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE);
104 MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE);
105 MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
108 protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
110 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH);
111 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH);
112 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH);
113 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH);
114 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH);
115 MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
118 protected static final Map<String, String> MAP_INPUT_EVENT_ID = new HashMap<>();
120 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF);
121 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON);
122 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH);
123 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH);
124 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH);
125 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH);
126 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH);
127 MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
130 protected static final Map<String, String> MAP_INPUT_MODE = new HashMap<>();
132 MAP_INPUT_MODE.put(SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON);
133 MAP_INPUT_MODE.put(SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE);
134 MAP_INPUT_MODE.put(SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
137 protected static final Map<String, String> MAP_ROLLER_STATE = new HashMap<>();
139 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN);
140 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE);
141 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING); // Gen2-only
142 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING); // Gen2-only
143 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP);
144 MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
147 protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
148 Shelly2GetConfigResult dc) {
149 if (dc.switch0 == null) {
152 ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
153 addRelaySettings(relays, dc.switch0);
154 addRelaySettings(relays, dc.switch1);
155 addRelaySettings(relays, dc.switch2);
156 addRelaySettings(relays, dc.switch3);
160 private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
161 @Nullable Shelly2DevConfigSwitch cs) {
166 ShellySettingsRelay rsettings = new ShellySettingsRelay();
167 rsettings.name = cs.name;
168 rsettings.ison = false;
169 rsettings.autoOn = getBool(cs.autoOn) ? cs.autoOnDelay : 0;
170 rsettings.autoOff = getBool(cs.autoOff) ? cs.autoOffDelay : 0;
171 rsettings.hasTimer = false;
172 rsettings.btnType = mapValue(MAP_INMODE_BTNTYPE, getString(cs.mode).toLowerCase());
173 relays.add(rsettings);
176 protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
177 boolean channelUpdate) throws ShellyApiException {
178 boolean updated = false;
180 if (result.temperature0 != null && !getProfile().isSensor) {
181 status.temperature = status.tmp.tC = result.temperature0.tC;
184 updated |= updateInputStatus(status, result, channelUpdate);
185 updated |= updateRelayStatus(status, result.switch0, channelUpdate);
186 updated |= updateRelayStatus(status, result.switch1, channelUpdate);
187 updated |= updateRelayStatus(status, result.switch2, channelUpdate);
188 updated |= updateRelayStatus(status, result.switch3, channelUpdate);
189 updated |= updateEmStatus(status, result.em0, channelUpdate);
190 updated |= updateRollerStatus(status, result.cover0, channelUpdate);
192 updated |= ShellyComponents.updateMeters(getThing(), status);
195 updateHumidityStatus(sensorData, result.humidity0);
196 updateTemperatureStatus(sensorData, result.temperature0);
197 updateSmokeStatus(sensorData, result.smoke0);
198 updateBatteryStatus(sensorData, result.devicepower0);
199 updateAddonStatus(status, result);
200 updated |= ShellyComponents.updateSensors(getThing(), status);
204 private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
205 boolean channelUpdate) throws ShellyApiException {
209 ShellyDeviceProfile profile = getProfile();
210 if (rs.id >= profile.numRelays) {
211 throw new IllegalArgumentException("Update for invalid relay index");
214 ShellySettingsRelay rstatus = status.relays.get(rs.id);
215 ShellyShortStatusRelay sr = relayStatus.relays.get(rs.id);
216 sr.isValid = rstatus.isValid = true;
217 sr.name = rstatus.name = status.name;
218 if (rs.output != null) {
219 sr.ison = rstatus.ison = getBool(rs.output);
221 if (getDouble(rs.timerStartetAt) > 0) {
222 int duration = (int) (now() - rs.timerStartetAt);
223 sr.timerRemaining = duration;
225 if (rs.temperature != null) {
226 status.tmp.isValid = true;
227 status.tmp.tC = rs.temperature.tC;
228 status.tmp.tF = rs.temperature.tF;
229 status.tmp.units = "C";
230 sr.temperature = getDouble(rs.temperature.tC);
231 if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
232 status.temperature = sr.temperature;
235 status.tmp.isValid = false;
237 if (rs.voltage != null) {
238 if (status.voltage == null || rs.voltage > status.voltage) {
239 status.voltage = rs.voltage;
242 if (rs.errors != null) {
243 for (String error : rs.errors) {
244 sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
245 status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
246 status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
248 sr.overtemperature = status.overtemperature;
251 ShellySettingsMeter sm = new ShellySettingsMeter();
252 ShellySettingsEMeter emeter = status.emeters.get(rs.id);
253 sm.isValid = emeter.isValid = true;
254 if (rs.apower != null) {
255 sm.power = emeter.power = rs.apower;
257 if (rs.aenergy != null) {
258 sm.total = emeter.total = rs.aenergy.total;
259 sm.counters = rs.aenergy.byMinute;
260 sm.timestamp = rs.aenergy.minuteTs;
262 if (rs.voltage != null) {
263 emeter.voltage = rs.voltage;
265 if (rs.current != null) {
266 emeter.current = rs.current;
272 // Update internal structures
273 status.relays.set(rs.id, rstatus);
274 relayStatus.relays.set(rs.id, sr);
275 updateMeter(status, rs.id, sm, emeter, channelUpdate);
276 return channelUpdate ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rs.id) : false;
279 private void updateMeter(ShellySettingsStatus status, int id, ShellySettingsMeter sm, ShellySettingsEMeter emeter,
280 boolean channelUpdate) throws ShellyApiException {
281 status.meters.set(id, sm);
282 status.emeters.set(id, emeter);
283 relayStatus.meters.set(id, sm);
286 private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusEm em,
287 boolean channelUpdate) throws ShellyApiException {
292 if (em.totalCurrent != null) {
293 status.totalCurrent = em.totalCurrent;
295 if (em.totalActPower != null) {
296 status.totalPower = em.totalActPower;
298 if (em.totalAprtPower != null) {
299 status.totalReturned = em.totalAprtPower;
302 ShellySettingsMeter sm = new ShellySettingsMeter();
303 ShellySettingsEMeter emeter = status.emeters.get(0);
304 sm.isValid = emeter.isValid = true;
305 if (em.aActPower != null) {
306 sm.power = emeter.power = em.aActPower;
308 if (em.aAprtPower != null) {
309 emeter.totalReturned = em.aAprtPower;
311 if (em.aVoltage != null) {
312 emeter.voltage = em.aVoltage;
314 if (em.aCurrent != null) {
315 emeter.current = em.aCurrent;
317 if (em.aPF != null) {
320 // Update internal structures
321 updateMeter(status, 0, sm, emeter, channelUpdate);
323 sm = new ShellySettingsMeter();
324 emeter = status.emeters.get(1);
325 sm.isValid = emeter.isValid = true;
326 if (em.bActPower != null) {
327 sm.power = emeter.power = em.bActPower;
329 if (em.bAprtPower != null) {
330 emeter.totalReturned = em.bAprtPower;
332 if (em.bVoltage != null) {
333 emeter.voltage = em.bVoltage;
335 if (em.bCurrent != null) {
336 emeter.current = em.bCurrent;
338 if (em.bPF != null) {
341 // Update internal structures
342 updateMeter(status, 1, sm, emeter, channelUpdate);
344 sm = new ShellySettingsMeter();
345 emeter = status.emeters.get(2);
346 sm.isValid = emeter.isValid = true;
347 if (em.cActPower != null) {
348 sm.power = emeter.power = em.cActPower;
350 if (em.cAprtPower != null) {
351 emeter.totalReturned = em.cAprtPower;
353 if (em.cVoltage != null) {
354 emeter.voltage = em.cVoltage;
356 if (em.cCurrent != null) {
357 emeter.current = em.cCurrent;
359 if (em.cPF != null) {
362 // Update internal structures
363 updateMeter(status, 2, sm, emeter, channelUpdate);
365 return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
368 protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
369 Shelly2GetConfigResult dc) {
370 if (dc.cover0 == null) {
374 ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
376 addRollerSettings(rollers, dc.cover0);
377 fillRollerFavorites(profile, dc);
381 private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
382 @Nullable Shelly2DevConfigCover coverConfig) {
383 if (coverConfig == null) {
387 ShellySettingsRoller settings = new ShellySettingsRoller();
388 settings.isValid = true;
389 settings.defaultState = coverConfig.initialState;
390 settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
391 settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
392 settings.swapInputs = coverConfig.swapInputs;
393 settings.maxtime = 0.0; // n/a
394 settings.maxtimeOpen = coverConfig.maxtimeOpen;
395 settings.maxtimeClose = coverConfig.maxtimeClose;
396 if (coverConfig.safetySwitch != null) {
397 settings.safetySwitch = coverConfig.safetySwitch.enable;
398 settings.safetyAction = coverConfig.safetySwitch.action;
400 if (coverConfig.obstructionDetection != null) {
401 settings.obstacleAction = coverConfig.obstructionDetection.action;
402 settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
403 settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
405 rollers.add(settings);
408 private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
409 if (dc.sys.uiData.cover != null) {
410 String[] favorites = dc.sys.uiData.cover.split(",");
411 profile.settings.favorites = new ArrayList<>();
412 for (int i = 0; i < favorites.length; i++) {
413 ShellyFavPos fav = new ShellyFavPos();
414 fav.pos = Integer.parseInt(favorites[i]);
415 fav.name = fav.pos + "%";
416 profile.settings.favorites.add(fav);
418 profile.settings.favoritesEnabled = profile.settings.favorites.size() > 0;
419 logger.debug("{}: Roller Favorites loaded: {}", thingName,
420 profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
424 private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
425 boolean updateChannels) throws ShellyApiException {
430 ShellyRollerStatus rs = status.rollers.get(cs.id);
431 ShellySettingsMeter sm = status.meters.get(cs.id);
432 ShellySettingsEMeter emeter = status.emeters.get(cs.id);
433 rs.isValid = sm.isValid = emeter.isValid = true;
434 if (cs.state != null) {
435 if (!getString(rs.state).equals(cs.state)) {
436 logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
437 mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
439 rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
440 rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
442 if (cs.currentPos != null) {
443 rs.currentPos = cs.currentPos;
445 if (cs.moveStartedAt != null) {
446 rs.duration = (int) (now() - cs.moveStartedAt.longValue());
448 if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
449 status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
451 if (cs.apower != null) {
452 rs.power = sm.power = emeter.power = cs.apower;
454 if (cs.aenergy != null) {
455 sm.total = emeter.total = cs.aenergy.total;
456 sm.counters = cs.aenergy.byMinute;
457 if (cs.aenergy.minuteTs != null) {
458 sm.timestamp = (long) cs.aenergy.minuteTs;
461 if (cs.voltage != null) {
462 emeter.voltage = cs.voltage;
464 if (cs.current != null) {
465 emeter.current = cs.current;
471 rollerStatus.set(cs.id, rs);
472 status.rollers.set(cs.id, rs);
473 relayStatus.meters.set(cs.id, sm);
474 status.meters.set(cs.id, sm);
475 status.emeters.set(cs.id, emeter);
477 postAlarms(cs.errors);
478 if (rs.calibrating != null && rs.calibrating) {
479 getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
482 return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
486 private void updateAddonStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusResult ds)
487 throws ShellyApiException {
492 if (ds.temperature100 != null) {
493 if (status.extTemperature == null) {
494 status.extTemperature = new ShellyExtTemperature();
496 status.extTemperature.sensor1 = updateExtTempSensor(ds.temperature100);
497 status.extTemperature.sensor2 = updateExtTempSensor(ds.temperature101);
498 status.extTemperature.sensor3 = updateExtTempSensor(ds.temperature102);
499 status.extTemperature.sensor4 = updateExtTempSensor(ds.temperature103);
500 status.extTemperature.sensor5 = updateExtTempSensor(ds.temperature104);
502 if (ds.humidity100 != null) {
503 status.extHumidity = new ShellyExtHumidity(ds.humidity100.rh);
505 if (ds.voltmeter100 != null) {
506 status.extVoltage = new ShellyExtVoltage(ds.voltmeter100.voltage);
508 if (ds.input100 != null) {
509 status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
513 private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
515 ShellyShortTemp temp = new ShellyShortTemp();
516 temp.hwID = value.id.toString();
524 protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
528 if (sdata.hum == null) {
529 sdata.hum = new ShellySensorHum();
531 sdata.hum.value = getDouble(value.rh);
534 protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
538 if (sdata.tmp == null) {
539 sdata.tmp = new ShellySensorTmp();
541 sdata.tmp.isValid = true;
542 sdata.tmp.units = SHELLY_TEMP_CELSIUS;
543 sdata.tmp.tC = value.tC;
544 sdata.tmp.tF = value.tF;
547 protected void updateSmokeStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusSmoke value) {
551 sdata.smoke = getBool(value.alarm);
552 sdata.mute = getBool(value.mute);
555 protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
559 if (sdata.bat == null) {
560 sdata.bat = new ShellySensorBat();
563 if (value.battery != null) {
564 sdata.bat.voltage = value.battery.volt;
565 sdata.bat.value = value.battery.percent;
567 if (value.external != null) {
568 sdata.charger = value.external.present;
572 private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
573 if (errors != null) {
574 for (String e : errors) {
576 getThing().postEvent(e, false);
582 protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
583 Shelly2GetConfigResult dc) {
584 if (dc.input0 == null) {
585 return null; // device has no input
588 ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
589 addInputSettings(inputs, dc.input0);
590 addInputSettings(inputs, dc.input1);
591 addInputSettings(inputs, dc.input2);
592 addInputSettings(inputs, dc.input3);
596 private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
601 ShellySettingsInput settings = new ShellySettingsInput();
602 settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
604 inputs.add(settings);
607 protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
608 boolean updateChannels) throws ShellyApiException {
609 boolean updated = false;
610 updated |= addInputStatus(status, ds.input0, updateChannels);
611 updated |= addInputStatus(status, ds.input1, updateChannels);
612 updated |= addInputStatus(status, ds.input2, updateChannels);
613 updated |= addInputStatus(status, ds.input3, updateChannels);
614 status.inputs = relayStatus.inputs;
618 private boolean addInputStatus(ShellySettingsStatus status, @Nullable Shelly2InputStatus is, boolean updateChannels)
619 throws ShellyApiException {
623 ShellyDeviceProfile profile = getProfile();
625 if (is.id == null || is.id > profile.numInputs) {
626 logger.debug("{}: Invalid input id: {}", thingName, is.id);
630 String group = profile.getInputGroup(is.id);
631 ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
632 : new ShellyInputState();
633 boolean updated = false;
634 input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
635 if (input.event == null && profile.inButtonMode(is.id)) {
637 input.eventCount = 0;
639 if (is.percent != null) { // analogous input
640 status.extAnalogInput = new ShellyExtAnalogInput(getDouble(is.percent));
642 relayStatus.inputs.set(is.id, input);
643 if (updateChannels) {
644 updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
649 protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
650 Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
651 request.id = Math.abs(random.nextInt());
652 request.src = thingName;
653 request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
654 request.params = params;
655 request.auth = authReq;
659 protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm,
660 String password) throws ShellyApiException {
661 Shelly2AuthRequest authReq = new Shelly2AuthRequest();
662 authReq.username = "admin";
663 authReq.realm = realm;
664 authReq.nonce = authParm.nonce;
665 authReq.cnonce = (long) Math.floor(Math.random() * 10e8);
666 authReq.nc = authParm.nc != null ? authParm.nc : 1;
667 authReq.authType = SHELLY2_AUTHTTYPE_DIGEST;
668 authReq.algorithm = SHELLY2_AUTHALG_SHA256;
669 String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password);
670 String ha2 = SHELLY2_AUTH_NOISE;
671 authReq.response = sha256(
672 ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2);
676 protected String mapValue(Map<String, String> map, @Nullable String key) {
678 boolean known = key != null && !key.isEmpty() && map.containsKey(key);
679 value = known ? getString(map.get(key)) : "";
680 logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
684 protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
685 return getThing().updateChannel(group, channel, value);
688 protected ShellyThingInterface getThing() throws ShellyApiException {
689 ShellyThingInterface t = thing;
693 throw new ShellyApiException("Thing/profile not initialized!");
696 protected ShellyDeviceProfile getProfile() throws ShellyApiException {
698 return thing.getProfile();
700 throw new ShellyApiException("Unable to get profile, thing not initialized!");