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