]> git.basschouten.com Git - openhab-addons.git/blob
c871e20dc4a3307dc6b8c7ad7be8bed9ca8df222
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.shelly.internal.api2;
14
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.*;
19
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Random;
24
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;
75
76 /**
77  * {@link Shelly2ApiClient} Low level part of the RPC API
78  *
79  * @author Markus Michels - Initial contribution
80  */
81 @NonNullByDefault
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;
90
91     public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
92         super(thingName, thing);
93         this.thing = thing;
94     }
95
96     public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
97         super(thingName, config, httpClient);
98     }
99
100     protected static final Map<String, String> MAP_INMODE_BTNTYPE = new HashMap<>();
101     static {
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);
106     }
107
108     protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
109     static {
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);
116     }
117
118     protected static final Map<String, String> MAP_INPUT_EVENT_ID = new HashMap<>();
119     static {
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);
128     }
129
130     protected static final Map<String, String> MAP_INPUT_MODE = new HashMap<>();
131     static {
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);
135     }
136
137     protected static final Map<String, String> MAP_ROLLER_STATE = new HashMap<>();
138     static {
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
145     }
146
147     protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
148             Shelly2GetConfigResult dc) {
149         if (dc.switch0 == null) {
150             return null;
151         }
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);
157         return relays;
158     }
159
160     private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
161             @Nullable Shelly2DevConfigSwitch cs) {
162         if (cs == null) {
163             return;
164         }
165
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);
174     }
175
176     protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
177             boolean channelUpdate) throws ShellyApiException {
178         boolean updated = false;
179
180         if (result.temperature0 != null && !getProfile().isSensor) {
181             status.temperature = status.tmp.tC = result.temperature0.tC;
182         }
183
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);
191         if (channelUpdate) {
192             updated |= ShellyComponents.updateMeters(getThing(), status);
193         }
194
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);
201         return updated;
202     }
203
204     private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
205             boolean channelUpdate) throws ShellyApiException {
206         if (rs == null) {
207             return false;
208         }
209         ShellyDeviceProfile profile = getProfile();
210         if (rs.id >= profile.numRelays) {
211             throw new IllegalArgumentException("Update for invalid relay index");
212         }
213
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);
220         }
221         if (getDouble(rs.timerStartetAt) > 0) {
222             int duration = (int) (now() - rs.timerStartetAt);
223             sr.timerRemaining = duration;
224         }
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;
233             }
234         } else {
235             status.tmp.isValid = false;
236         }
237         if (rs.voltage != null) {
238             if (status.voltage == null || rs.voltage > status.voltage) {
239                 status.voltage = rs.voltage;
240             }
241         }
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);
247             }
248             sr.overtemperature = status.overtemperature;
249         }
250
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;
256         }
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;
261         }
262         if (rs.voltage != null) {
263             emeter.voltage = rs.voltage;
264         }
265         if (rs.current != null) {
266             emeter.current = rs.current;
267         }
268         if (rs.pf != null) {
269             emeter.pf = rs.pf;
270         }
271
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;
277     }
278
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);
284     }
285
286     private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusEm em,
287             boolean channelUpdate) throws ShellyApiException {
288         if (em == null) {
289             return false;
290         }
291
292         boolean updated = false;
293         ShellySettingsMeter sm = new ShellySettingsMeter();
294         ShellySettingsEMeter emeter = status.emeters.get(0);
295         sm.isValid = emeter.isValid = true;
296         if (em.aActPower != null) {
297             sm.power = emeter.power = em.aActPower;
298         }
299         if (em.aAprtPower != null) {
300             emeter.totalReturned = em.aAprtPower;
301         }
302         if (em.aVoltage != null) {
303             emeter.voltage = em.aVoltage;
304         }
305         if (em.aCurrent != null) {
306             emeter.current = em.aCurrent;
307         }
308         if (em.aPF != null) {
309             emeter.pf = em.aPF;
310         }
311         // Update internal structures
312         updateMeter(status, 0, sm, emeter, channelUpdate);
313
314         sm = new ShellySettingsMeter();
315         emeter = status.emeters.get(1);
316         sm.isValid = emeter.isValid = true;
317         if (em.bActPower != null) {
318             sm.power = emeter.power = em.bActPower;
319         }
320         if (em.bAprtPower != null) {
321             emeter.totalReturned = em.bAprtPower;
322         }
323         if (em.bVoltage != null) {
324             emeter.voltage = em.bVoltage;
325         }
326         if (em.bCurrent != null) {
327             emeter.current = em.bCurrent;
328         }
329         if (em.bPF != null) {
330             emeter.pf = em.bPF;
331         }
332         // Update internal structures
333         updateMeter(status, 1, sm, emeter, channelUpdate);
334
335         sm = new ShellySettingsMeter();
336         emeter = status.emeters.get(2);
337         sm.isValid = emeter.isValid = true;
338         if (em.cActPower != null) {
339             sm.power = emeter.power = em.cActPower;
340         }
341         if (em.cAprtPower != null) {
342             emeter.totalReturned = em.cAprtPower;
343         }
344         if (em.cVoltage != null) {
345             emeter.voltage = em.cVoltage;
346         }
347         if (em.cCurrent != null) {
348             emeter.current = em.cCurrent;
349         }
350         if (em.cPF != null) {
351             emeter.pf = em.cPF;
352         }
353         // Update internal structures
354         updateMeter(status, 2, sm, emeter, channelUpdate);
355
356         return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
357     }
358
359     protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
360             Shelly2GetConfigResult dc) {
361         if (dc.cover0 == null) {
362             return null;
363         }
364
365         ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
366
367         addRollerSettings(rollers, dc.cover0);
368         fillRollerFavorites(profile, dc);
369         return rollers;
370     }
371
372     private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
373             @Nullable Shelly2DevConfigCover coverConfig) {
374         if (coverConfig == null) {
375             return;
376         }
377
378         ShellySettingsRoller settings = new ShellySettingsRoller();
379         settings.isValid = true;
380         settings.defaultState = coverConfig.initialState;
381         settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
382         settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
383         settings.swapInputs = coverConfig.swapInputs;
384         settings.maxtime = 0.0; // n/a
385         settings.maxtimeOpen = coverConfig.maxtimeOpen;
386         settings.maxtimeClose = coverConfig.maxtimeClose;
387         if (coverConfig.safetySwitch != null) {
388             settings.safetySwitch = coverConfig.safetySwitch.enable;
389             settings.safetyAction = coverConfig.safetySwitch.action;
390         }
391         if (coverConfig.obstructionDetection != null) {
392             settings.obstacleAction = coverConfig.obstructionDetection.action;
393             settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
394             settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
395         }
396         rollers.add(settings);
397     }
398
399     private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
400         if (dc.sys.uiData.cover != null) {
401             String[] favorites = dc.sys.uiData.cover.split(",");
402             profile.settings.favorites = new ArrayList<>();
403             for (int i = 0; i < favorites.length; i++) {
404                 ShellyFavPos fav = new ShellyFavPos();
405                 fav.pos = Integer.parseInt(favorites[i]);
406                 fav.name = fav.pos + "%";
407                 profile.settings.favorites.add(fav);
408             }
409             profile.settings.favoritesEnabled = profile.settings.favorites.size() > 0;
410             logger.debug("{}: Roller Favorites loaded: {}", thingName,
411                     profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
412         }
413     }
414
415     private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
416             boolean updateChannels) throws ShellyApiException {
417         if (cs == null) {
418             return false;
419         }
420
421         ShellyRollerStatus rs = status.rollers.get(cs.id);
422         ShellySettingsMeter sm = status.meters.get(cs.id);
423         ShellySettingsEMeter emeter = status.emeters.get(cs.id);
424         rs.isValid = sm.isValid = emeter.isValid = true;
425         if (cs.state != null) {
426             if (!getString(rs.state).equals(cs.state)) {
427                 logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
428                         mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
429             }
430             rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
431             rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
432         }
433         if (cs.currentPos != null) {
434             rs.currentPos = cs.currentPos;
435         }
436         if (cs.moveStartedAt != null) {
437             rs.duration = (int) (now() - cs.moveStartedAt.longValue());
438         }
439         if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
440             status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
441         }
442         if (cs.apower != null) {
443             rs.power = sm.power = emeter.power = cs.apower;
444         }
445         if (cs.aenergy != null) {
446             sm.total = emeter.total = cs.aenergy.total;
447             sm.counters = cs.aenergy.byMinute;
448             if (cs.aenergy.minuteTs != null) {
449                 sm.timestamp = (long) cs.aenergy.minuteTs;
450             }
451         }
452         if (cs.voltage != null) {
453             emeter.voltage = cs.voltage;
454         }
455         if (cs.current != null) {
456             emeter.current = cs.current;
457         }
458         if (cs.pf != null) {
459             emeter.pf = cs.pf;
460         }
461
462         rollerStatus.set(cs.id, rs);
463         status.rollers.set(cs.id, rs);
464         relayStatus.meters.set(cs.id, sm);
465         status.meters.set(cs.id, sm);
466         status.emeters.set(cs.id, emeter);
467
468         postAlarms(cs.errors);
469         if (rs.calibrating != null && rs.calibrating) {
470             getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
471         }
472
473         return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
474     }
475
476     // Addon
477     private void updateAddonStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusResult ds)
478             throws ShellyApiException {
479         if (ds == null) {
480             return;
481         }
482
483         if (ds.temperature100 != null) {
484             if (status.extTemperature == null) {
485                 status.extTemperature = new ShellyExtTemperature();
486             }
487             status.extTemperature.sensor1 = updateExtTempSensor(ds.temperature100);
488             status.extTemperature.sensor2 = updateExtTempSensor(ds.temperature101);
489             status.extTemperature.sensor3 = updateExtTempSensor(ds.temperature102);
490             status.extTemperature.sensor4 = updateExtTempSensor(ds.temperature103);
491             status.extTemperature.sensor5 = updateExtTempSensor(ds.temperature104);
492         }
493         if (ds.humidity100 != null) {
494             status.extHumidity = new ShellyExtHumidity(ds.humidity100.rh);
495         }
496         if (ds.voltmeter100 != null) {
497             status.extVoltage = new ShellyExtVoltage(ds.voltmeter100.voltage);
498         }
499         if (ds.input100 != null) {
500             status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
501         }
502     }
503
504     private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
505         if (value != null) {
506             ShellyShortTemp temp = new ShellyShortTemp();
507             temp.hwID = value.id.toString();
508             temp.tC = value.tC;
509             temp.tF = value.tF;
510             return temp;
511         }
512         return null;
513     }
514
515     protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
516         if (value == null) {
517             return;
518         }
519         if (sdata.hum == null) {
520             sdata.hum = new ShellySensorHum();
521         }
522         sdata.hum.value = getDouble(value.rh);
523     }
524
525     protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
526         if (value == null) {
527             return;
528         }
529         if (sdata.tmp == null) {
530             sdata.tmp = new ShellySensorTmp();
531         }
532         sdata.tmp.isValid = true;
533         sdata.tmp.units = SHELLY_TEMP_CELSIUS;
534         sdata.tmp.tC = value.tC;
535         sdata.tmp.tF = value.tF;
536     }
537
538     protected void updateSmokeStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusSmoke value) {
539         if (value == null) {
540             return;
541         }
542         sdata.smoke = getBool(value.alarm);
543         sdata.mute = getBool(value.mute);
544     }
545
546     protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
547         if (value == null) {
548             return;
549         }
550         if (sdata.bat == null) {
551             sdata.bat = new ShellySensorBat();
552         }
553
554         if (value.battery != null) {
555             sdata.bat.voltage = value.battery.volt;
556             sdata.bat.value = value.battery.percent;
557         }
558         if (value.external != null) {
559             sdata.charger = value.external.present;
560         }
561     }
562
563     private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
564         if (errors != null) {
565             for (String e : errors) {
566                 if (e != null) {
567                     getThing().postEvent(e, false);
568                 }
569             }
570         }
571     }
572
573     protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
574             Shelly2GetConfigResult dc) {
575         if (dc.input0 == null) {
576             return null; // device has no input
577         }
578
579         ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
580         addInputSettings(inputs, dc.input0);
581         addInputSettings(inputs, dc.input1);
582         addInputSettings(inputs, dc.input2);
583         addInputSettings(inputs, dc.input3);
584         return inputs;
585     }
586
587     private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
588         if (ic == null) {
589             return;
590         }
591
592         ShellySettingsInput settings = new ShellySettingsInput();
593         settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
594                 : SHELLY_BTNT_EDGE;
595         inputs.add(settings);
596     }
597
598     protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
599             boolean updateChannels) throws ShellyApiException {
600         boolean updated = false;
601         updated |= addInputStatus(status, ds.input0, updateChannels);
602         updated |= addInputStatus(status, ds.input1, updateChannels);
603         updated |= addInputStatus(status, ds.input2, updateChannels);
604         updated |= addInputStatus(status, ds.input3, updateChannels);
605         status.inputs = relayStatus.inputs;
606         return updated;
607     }
608
609     private boolean addInputStatus(ShellySettingsStatus status, @Nullable Shelly2InputStatus is, boolean updateChannels)
610             throws ShellyApiException {
611         if (is == null) {
612             return false;
613         }
614         ShellyDeviceProfile profile = getProfile();
615
616         if (is.id == null || is.id > profile.numInputs) {
617             logger.debug("{}: Invalid input id: {}", thingName, is.id);
618             return false;
619         }
620
621         String group = profile.getInputGroup(is.id);
622         ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
623                 : new ShellyInputState();
624         boolean updated = false;
625         input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
626         if (input.event == null && profile.inButtonMode(is.id)) {
627             input.event = "";
628             input.eventCount = 0;
629         }
630         if (is.percent != null) { // analogous input
631             status.extAnalogInput = new ShellyExtAnalogInput(getDouble(is.percent));
632         }
633         relayStatus.inputs.set(is.id, input);
634         if (updateChannels) {
635             updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
636         }
637         return updated;
638     }
639
640     protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
641         Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
642         request.id = Math.abs(random.nextInt());
643         request.src = thingName;
644         request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
645         request.params = params;
646         request.auth = authReq;
647         return request;
648     }
649
650     protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm,
651             String password) throws ShellyApiException {
652         Shelly2AuthRequest authReq = new Shelly2AuthRequest();
653         authReq.username = "admin";
654         authReq.realm = realm;
655         authReq.nonce = authParm.nonce;
656         authReq.cnonce = (long) Math.floor(Math.random() * 10e8);
657         authReq.nc = authParm.nc != null ? authParm.nc : 1;
658         authReq.authType = SHELLY2_AUTHTTYPE_DIGEST;
659         authReq.algorithm = SHELLY2_AUTHALG_SHA256;
660         String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password);
661         String ha2 = SHELLY2_AUTH_NOISE;
662         authReq.response = sha256(
663                 ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2);
664         return authReq;
665     }
666
667     protected String mapValue(Map<String, String> map, @Nullable String key) {
668         String value;
669         boolean known = key != null && !key.isEmpty() && map.containsKey(key);
670         value = known ? getString(map.get(key)) : "";
671         logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
672         return value;
673     }
674
675     protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
676         return getThing().updateChannel(group, channel, value);
677     }
678
679     protected ShellyThingInterface getThing() throws ShellyApiException {
680         ShellyThingInterface t = thing;
681         if (t != null) {
682             return t;
683         }
684         throw new ShellyApiException("Thing/profile not initialized!");
685     }
686
687     ShellyDeviceProfile getProfile() throws ShellyApiException {
688         if (thing != null) {
689             return thing.getProfile();
690         }
691         throw new ShellyApiException("Unable to get profile, thing not initialized!");
692     }
693 }