]> git.basschouten.com Git - openhab-addons.git/blob
97fb99ca26da89bb0c95cab2b21b9c3e8b0dff70
[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.Shelly2DeviceStatusHumidity;
61 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
62 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
63 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
64 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
65 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
66 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
67 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
68 import org.openhab.binding.shelly.internal.handler.ShellyComponents;
69 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
70 import org.openhab.core.types.State;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 /**
75  * {@link Shelly2ApiClient} Low level part of the RPC API
76  *
77  * @author Markus Michels - Initial contribution
78  */
79 @NonNullByDefault
80 public class Shelly2ApiClient extends ShellyHttpClient {
81     private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
82     protected final Random random = new Random();
83     protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
84     protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
85     protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
86     protected @Nullable ShellyThingInterface thing;
87     protected @Nullable Shelly2AuthRequest authReq;
88
89     public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
90         super(thingName, thing);
91         this.thing = thing;
92     }
93
94     public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
95         super(thingName, config, httpClient);
96     }
97
98     protected static final Map<String, String> MAP_INMODE_BTNTYPE = new HashMap<>();
99     static {
100         MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY);
101         MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE);
102         MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE);
103         MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
104     }
105
106     protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
107     static {
108         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH);
109         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH);
110         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH);
111         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH);
112         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH);
113         MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
114     }
115
116     protected static final Map<String, String> MAP_INPUT_EVENT_ID = new HashMap<>();
117     static {
118         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF);
119         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON);
120         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH);
121         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH);
122         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH);
123         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH);
124         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH);
125         MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
126     }
127
128     protected static final Map<String, String> MAP_INPUT_MODE = new HashMap<>();
129     static {
130         MAP_INPUT_MODE.put(SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON);
131         MAP_INPUT_MODE.put(SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE);
132         MAP_INPUT_MODE.put(SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
133     }
134
135     protected static final Map<String, String> MAP_ROLLER_STATE = new HashMap<>();
136     static {
137         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN);
138         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE);
139         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING); // Gen2-only
140         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING); // Gen2-only
141         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP);
142         MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
143     }
144
145     protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
146             Shelly2GetConfigResult dc) {
147         if (dc.switch0 == null) {
148             return null;
149         }
150         ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
151         addRelaySettings(relays, dc.switch0);
152         addRelaySettings(relays, dc.switch1);
153         addRelaySettings(relays, dc.switch2);
154         addRelaySettings(relays, dc.switch3);
155         return relays;
156     }
157
158     private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
159             @Nullable Shelly2DevConfigSwitch cs) {
160         if (cs == null) {
161             return;
162         }
163
164         ShellySettingsRelay rsettings = new ShellySettingsRelay();
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);
172     }
173
174     protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
175             boolean channelUpdate) throws ShellyApiException {
176         boolean updated = false;
177
178         updated |= updateInputStatus(status, result, channelUpdate);
179         updated |= updateRelayStatus(status, result.switch0, channelUpdate);
180         updated |= updateRelayStatus(status, result.switch1, channelUpdate);
181         updated |= updateRelayStatus(status, result.switch2, channelUpdate);
182         updated |= updateRelayStatus(status, result.switch3, channelUpdate);
183         updated |= updateRollerStatus(status, result.cover0, channelUpdate);
184         if (channelUpdate) {
185             updated |= ShellyComponents.updateMeters(getThing(), status);
186         }
187
188         updateHumidityStatus(sensorData, result.humidity0);
189         updateTemperatureStatus(sensorData, result.temperature0);
190         updateBatteryStatus(sensorData, result.devicepower0);
191         updateAddonStatus(status, result);
192         updated |= ShellyComponents.updateSensors(getThing(), status);
193         return updated;
194     }
195
196     private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
197             boolean channelUpdate) throws ShellyApiException {
198         if (rs == null) {
199             return false;
200         }
201         ShellyDeviceProfile profile = getProfile();
202         if (rs.id >= profile.numRelays) {
203             throw new IllegalArgumentException("Update for invalid relay index");
204         }
205
206         ShellySettingsRelay rstatus = status.relays.get(rs.id);
207         ShellyShortStatusRelay sr = relayStatus.relays.get(rs.id);
208         sr.isValid = rstatus.isValid = true;
209         sr.name = rstatus.name = status.name;
210         if (rs.output != null) {
211             sr.ison = rstatus.ison = getBool(rs.output);
212         }
213         if (getDouble(rs.timerStartetAt) > 0) {
214             int duration = (int) (now() - rs.timerStartetAt);
215             sr.timerRemaining = duration;
216         }
217         if (rs.temperature != null) {
218             status.tmp.isValid = true;
219             status.tmp.tC = rs.temperature.tC;
220             status.tmp.tF = rs.temperature.tF;
221             status.tmp.units = "C";
222             sr.temperature = getDouble(rs.temperature.tC);
223             if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
224                 status.temperature = sr.temperature;
225             }
226         } else {
227             status.tmp.isValid = false;
228         }
229         if (rs.voltage != null) {
230             if (status.voltage == null || rs.voltage > status.voltage) {
231                 status.voltage = rs.voltage;
232             }
233         }
234         if (rs.errors != null) {
235             for (String error : rs.errors) {
236                 sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
237                 status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
238                 status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
239             }
240             sr.overtemperature = status.overtemperature;
241         }
242
243         ShellySettingsMeter sm = new ShellySettingsMeter();
244         ShellySettingsEMeter emeter = status.emeters.get(rs.id);
245         sm.isValid = emeter.isValid = true;
246         if (rs.apower != null) {
247             sm.power = emeter.power = rs.apower;
248         }
249         if (rs.aenergy != null) {
250             sm.total = emeter.total = rs.aenergy.total;
251             sm.counters = rs.aenergy.byMinute;
252             sm.timestamp = rs.aenergy.minuteTs;
253         }
254         if (rs.voltage != null) {
255             emeter.voltage = rs.voltage;
256         }
257         if (rs.current != null) {
258             emeter.current = rs.current;
259         }
260         if (rs.pf != null) {
261             emeter.pf = rs.pf;
262         }
263
264         // Update internal structures
265         status.relays.set(rs.id, rstatus);
266         status.meters.set(rs.id, sm);
267         status.emeters.set(rs.id, emeter);
268         relayStatus.relays.set(rs.id, sr);
269         relayStatus.meters.set(rs.id, sm);
270
271         return channelUpdate ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rs.id) : false;
272     }
273
274     protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
275             Shelly2GetConfigResult dc) {
276         if (dc.cover0 == null) {
277             return null;
278         }
279
280         ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
281
282         addRollerSettings(rollers, dc.cover0);
283         fillRollerFavorites(profile, dc);
284         return rollers;
285     }
286
287     private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
288             @Nullable Shelly2DevConfigCover coverConfig) {
289         if (coverConfig == null) {
290             return;
291         }
292
293         ShellySettingsRoller settings = new ShellySettingsRoller();
294         settings.isValid = true;
295         settings.defaultState = coverConfig.initialState;
296         settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
297         settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
298         settings.swapInputs = coverConfig.swapInputs;
299         settings.maxtime = 0.0; // n/a
300         settings.maxtimeOpen = coverConfig.maxtimeOpen;
301         settings.maxtimeClose = coverConfig.maxtimeClose;
302         if (coverConfig.safetySwitch != null) {
303             settings.safetySwitch = coverConfig.safetySwitch.enable;
304             settings.safetyAction = coverConfig.safetySwitch.action;
305         }
306         if (coverConfig.obstructionDetection != null) {
307             settings.obstacleAction = coverConfig.obstructionDetection.action;
308             settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
309             settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
310         }
311         rollers.add(settings);
312     }
313
314     private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
315         if (dc.sys.uiData.cover != null) {
316             String[] favorites = dc.sys.uiData.cover.split(",");
317             profile.settings.favorites = new ArrayList<>();
318             for (int i = 0; i < favorites.length; i++) {
319                 ShellyFavPos fav = new ShellyFavPos();
320                 fav.pos = Integer.parseInt(favorites[i]);
321                 fav.name = fav.pos + "%";
322                 profile.settings.favorites.add(fav);
323             }
324             profile.settings.favoritesEnabled = profile.settings.favorites.size() > 0;
325             logger.debug("{}: Roller Favorites loaded: {}", thingName,
326                     profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
327         }
328     }
329
330     private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
331             boolean updateChannels) throws ShellyApiException {
332         if (cs == null) {
333             return false;
334         }
335
336         ShellyRollerStatus rs = status.rollers.get(cs.id);
337         ShellySettingsMeter sm = status.meters.get(cs.id);
338         ShellySettingsEMeter emeter = status.emeters.get(cs.id);
339         rs.isValid = sm.isValid = emeter.isValid = true;
340         if (cs.state != null) {
341             if (!getString(rs.state).equals(cs.state)) {
342                 logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
343                         mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
344             }
345             rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
346             rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
347         }
348         if (cs.currentPos != null) {
349             rs.currentPos = cs.currentPos;
350         }
351         if (cs.moveStartedAt != null) {
352             rs.duration = (int) (now() - cs.moveStartedAt.longValue());
353         }
354         if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
355             status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
356         }
357         if (cs.apower != null) {
358             rs.power = sm.power = emeter.power = cs.apower;
359         }
360         if (cs.aenergy != null) {
361             sm.total = emeter.total = cs.aenergy.total;
362             sm.counters = cs.aenergy.byMinute;
363             sm.timestamp = (long) cs.aenergy.minuteTs;
364         }
365         if (cs.voltage != null) {
366             emeter.voltage = cs.voltage;
367         }
368         if (cs.current != null) {
369             emeter.current = cs.current;
370         }
371         if (cs.pf != null) {
372             emeter.pf = cs.pf;
373         }
374
375         rollerStatus.set(cs.id, rs);
376         status.rollers.set(cs.id, rs);
377         relayStatus.meters.set(cs.id, sm);
378         status.meters.set(cs.id, sm);
379         status.emeters.set(cs.id, emeter);
380
381         postAlarms(cs.errors);
382         if (rs.calibrating != null && rs.calibrating) {
383             getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
384         }
385
386         return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
387     }
388
389     // Addon
390     private void updateAddonStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusResult ds)
391             throws ShellyApiException {
392         if (ds == null) {
393             return;
394         }
395
396         if (ds.temperature100 != null) {
397             if (status.extTemperature == null) {
398                 status.extTemperature = new ShellyExtTemperature();
399             }
400             status.extTemperature.sensor1 = updateExtTempSensor(ds.temperature100);
401             status.extTemperature.sensor2 = updateExtTempSensor(ds.temperature101);
402             status.extTemperature.sensor3 = updateExtTempSensor(ds.temperature102);
403             status.extTemperature.sensor4 = updateExtTempSensor(ds.temperature103);
404             status.extTemperature.sensor5 = updateExtTempSensor(ds.temperature104);
405         }
406         if (ds.humidity100 != null) {
407             status.extHumidity = new ShellyExtHumidity(ds.humidity100.rh);
408         }
409         if (ds.voltmeter100 != null) {
410             status.extVoltage = new ShellyExtVoltage(ds.voltmeter100.voltage);
411         }
412         if (ds.input100 != null) {
413             status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
414         }
415     }
416
417     private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
418         if (value != null) {
419             ShellyShortTemp temp = new ShellyShortTemp();
420             temp.hwID = value.id.toString();
421             temp.tC = value.tC;
422             temp.tF = value.tF;
423             return temp;
424         }
425         return null;
426     }
427
428     protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
429         if (value == null) {
430             return;
431         }
432         if (sdata.hum == null) {
433             sdata.hum = new ShellySensorHum();
434         }
435         sdata.hum.value = getDouble(value.rh);
436     }
437
438     protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
439         if (value == null) {
440             return;
441         }
442         if (sdata.tmp == null) {
443             sdata.tmp = new ShellySensorTmp();
444         }
445         sdata.tmp.isValid = true;
446         sdata.tmp.units = SHELLY_TEMP_CELSIUS;
447         sdata.tmp.tC = value.tC;
448         sdata.tmp.tF = value.tF;
449     }
450
451     protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
452         if (value == null) {
453             return;
454         }
455         if (sdata.bat == null) {
456             sdata.bat = new ShellySensorBat();
457         }
458
459         if (value.battery != null) {
460             sdata.bat.voltage = value.battery.volt;
461             sdata.bat.value = value.battery.percent;
462         }
463         if (value.external != null) {
464             sdata.charger = value.external.present;
465         }
466     }
467
468     private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
469         if (errors != null) {
470             for (String e : errors) {
471                 if (e != null) {
472                     getThing().postEvent(e, false);
473                 }
474             }
475         }
476     }
477
478     protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
479             Shelly2GetConfigResult dc) {
480         if (dc.input0 == null) {
481             return null; // device has no input
482         }
483
484         ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
485         addInputSettings(inputs, dc.input0);
486         addInputSettings(inputs, dc.input1);
487         addInputSettings(inputs, dc.input2);
488         addInputSettings(inputs, dc.input3);
489         return inputs;
490     }
491
492     private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
493         if (ic == null) {
494             return;
495         }
496
497         ShellySettingsInput settings = new ShellySettingsInput();
498         settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
499                 : SHELLY_BTNT_EDGE;
500         inputs.add(settings);
501     }
502
503     protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
504             boolean updateChannels) throws ShellyApiException {
505         boolean updated = false;
506         updated |= addInputStatus(status, ds.input0, updateChannels);
507         updated |= addInputStatus(status, ds.input1, updateChannels);
508         updated |= addInputStatus(status, ds.input2, updateChannels);
509         updated |= addInputStatus(status, ds.input3, updateChannels);
510         status.inputs = relayStatus.inputs;
511         return updated;
512     }
513
514     private boolean addInputStatus(ShellySettingsStatus status, @Nullable Shelly2InputStatus is, boolean updateChannels)
515             throws ShellyApiException {
516         if (is == null) {
517             return false;
518         }
519         ShellyDeviceProfile profile = getProfile();
520
521         if (is.id == null || is.id > profile.numInputs) {
522             logger.debug("{}: Invalid input id: {}", thingName, is.id);
523             return false;
524         }
525
526         String group = profile.getInputGroup(is.id);
527         ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
528                 : new ShellyInputState();
529         boolean updated = false;
530         input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
531         if (input.event == null && profile.inButtonMode(is.id)) {
532             input.event = "";
533             input.eventCount = 0;
534         }
535         if (is.percent != null) { // analogous input
536             status.extAnalogInput = new ShellyExtAnalogInput(getDouble(is.percent));
537         }
538         relayStatus.inputs.set(is.id, input);
539         if (updateChannels) {
540             updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
541         }
542         return updated;
543     }
544
545     protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
546         Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
547         request.id = Math.abs(random.nextInt());
548         request.src = thingName;
549         request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
550         request.params = params;
551         request.auth = authReq;
552         return request;
553     }
554
555     protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm,
556             String password) throws ShellyApiException {
557         Shelly2AuthRequest authReq = new Shelly2AuthRequest();
558         authReq.username = "admin";
559         authReq.realm = realm;
560         authReq.nonce = authParm.nonce;
561         authReq.cnonce = (long) Math.floor(Math.random() * 10e8);
562         authReq.nc = authParm.nc != null ? authParm.nc : 1;
563         authReq.authType = SHELLY2_AUTHTTYPE_DIGEST;
564         authReq.algorithm = SHELLY2_AUTHALG_SHA256;
565         String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password);
566         String ha2 = SHELLY2_AUTH_NOISE;
567         authReq.response = sha256(
568                 ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2);
569         return authReq;
570     }
571
572     protected String mapValue(Map<String, String> map, @Nullable String key) {
573         String value;
574         boolean known = key != null && !key.isEmpty() && map.containsKey(key);
575         value = known ? getString(map.get(key)) : "";
576         logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
577         return value;
578     }
579
580     protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
581         return getThing().updateChannel(group, channel, value);
582     }
583
584     protected ShellyThingInterface getThing() throws ShellyApiException {
585         ShellyThingInterface t = thing;
586         if (t != null) {
587             return t;
588         }
589         throw new ShellyApiException("Thing/profile not initialized!");
590     }
591
592     ShellyDeviceProfile getProfile() throws ShellyApiException {
593         if (thing != null) {
594             return thing.getProfile();
595         }
596         throw new ShellyApiException("Unable to get profile, thing not initialized!");
597     }
598 }