]> git.basschouten.com Git - openhab-addons.git/blob
de9d40755b36f5294deb8dd426d80fd19340b464
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.Map;
22 import java.util.Random;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.shelly.internal.api.ShellyApiException;
28 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
29 import org.openhab.binding.shelly.internal.api.ShellyHttpClient;
30 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
33 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer;
35 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
36 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
37 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
38 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
39 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRoller;
40 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
41 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
42 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
43 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
44 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
45 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtAnalogInput;
46 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtDigitalInput;
47 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtHumidity;
48 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature;
49 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtTemperature.ShellyShortTemp;
50 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyExtVoltage;
51 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
52 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum;
53 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorLux;
54 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRsp;
55 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover;
56 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput;
57 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch;
58 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
59 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusLight;
60 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
61 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
62 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusEm;
63 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
64 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusIlluminance;
65 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
66 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusSmoke;
67 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
68 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
69 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
70 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
71 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2StatusEm1;
72 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
73 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
74 import org.openhab.binding.shelly.internal.handler.ShellyComponents;
75 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
76 import org.openhab.core.types.State;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 /**
81  * {@link Shelly2ApiClient} Low level part of the RPC API
82  *
83  * @author Markus Michels - Initial contribution
84  */
85 @NonNullByDefault
86 public class Shelly2ApiClient extends ShellyHttpClient {
87     private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
88     protected final Random random = new Random();
89     protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
90     protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
91     protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
92     protected @Nullable ShellyThingInterface thing;
93     protected @Nullable Shelly2AuthRsp authReq;
94
95     public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
96         super(thingName, thing);
97         this.thing = thing;
98     }
99
100     public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
101         super(thingName, config, httpClient);
102     }
103
104     protected static final Map<String, String> MAP_INMODE_BTNTYPE = Map.of(//
105             SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY, //
106             SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE, //
107             SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE, //
108             SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
109
110     protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = Map.of(//
111             SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH, //
112             SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH, //
113             SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH, //
114             SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH, //
115             SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH, //
116             SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
117
118     protected static final Map<String, String> MAP_INPUT_EVENT_ID = Map.of(//
119             SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF, //
120             SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON, //
121             SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH, //
122             SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH, //
123             SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH, //
124             SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH, //
125             SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH, //
126             SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
127
128     protected static final Map<String, String> MAP_INPUT_MODE = Map.of(//
129             SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON, //
130             SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE, //
131             SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
132
133     protected static final Map<String, String> MAP_ROLLER_STATE = Map.of(//
134             SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN, //
135             SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE, //
136             SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING, // Gen2-only
137             SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING, // Gen2-only
138             SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP, //
139             SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
140
141     protected static final Map<String, String> MAP_PROFILE = Map.of(//
142             SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY, //
143             SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER);
144
145     protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
146             Shelly2GetConfigResult dc) {
147         ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
148         addRelaySettings(relays, dc.switch0);
149         addRelaySettings(relays, dc.switch1);
150         addRelaySettings(relays, dc.switch2);
151         addRelaySettings(relays, dc.switch3);
152         addRelaySettings(relays, dc.switch100);
153         return !relays.isEmpty() ? relays : null;
154     }
155
156     private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
157             @Nullable Shelly2DevConfigSwitch cs) {
158         if (cs == null) {
159             return;
160         }
161
162         ShellySettingsRelay rsettings = new ShellySettingsRelay();
163         rsettings.id = cs.id;
164         rsettings.isValid = cs.id != null;
165         rsettings.name = cs.name;
166         rsettings.ison = false;
167         rsettings.autoOn = getBool(cs.autoOn) ? cs.autoOnDelay : 0;
168         rsettings.autoOff = getBool(cs.autoOff) ? cs.autoOffDelay : 0;
169         rsettings.hasTimer = false;
170         rsettings.btnType = mapValue(MAP_INMODE_BTNTYPE, getString(cs.mode).toLowerCase());
171         relays.add(rsettings);
172     }
173
174     protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
175             boolean channelUpdate) throws ShellyApiException {
176         boolean updated = false;
177
178         if (result.temperature0 != null && result.temperature0.tC != null && !getProfile().isSensor) {
179             if (status.tmp == null) {
180                 status.tmp = new ShellySensorTmp();
181             }
182             status.temperature = status.tmp.tC = result.temperature0.tC;
183         }
184
185         updated |= updateInputStatus(status, result, channelUpdate);
186         updated |= updateRelayStatus(status, result.switch0, channelUpdate);
187         updated |= updateRelayStatus(status, result.switch1, channelUpdate);
188         updated |= updateRelayStatus(status, result.switch2, channelUpdate);
189         updated |= updateRelayStatus(status, result.switch3, channelUpdate);
190         updated |= updateRelayStatus(status, result.switch100, channelUpdate);
191         updated |= updateRelayStatus(status, result.pm10, channelUpdate);
192         updated |= updateEmStatus(status, result.em0, channelUpdate);
193         updated |= updateEmStatus(status, result.em10, channelUpdate);
194         updated |= updateEmStatus(status, result.em11, channelUpdate);
195         updated |= updateRollerStatus(status, result.cover0, channelUpdate);
196         updated |= updateDimmerStatus(status, result.light0, channelUpdate);
197         if (channelUpdate) {
198             updated |= ShellyComponents.updateMeters(getThing(), status);
199         }
200
201         updateHumidityStatus(sensorData, result.humidity0);
202         updateTemperatureStatus(sensorData, result.temperature0);
203         updateIlluminanceStatus(sensorData, result.illuminance0);
204         updateSmokeStatus(sensorData, result.smoke0);
205         updateBatteryStatus(sensorData, result.devicepower0);
206         updateAddonStatus(status, result);
207         updated |= ShellyComponents.updateSensors(getThing(), status);
208         return updated;
209     }
210
211     private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
212             boolean channelUpdate) throws ShellyApiException {
213         if (rs == null) {
214             return false;
215         }
216         ShellyDeviceProfile profile = getProfile();
217
218         ShellySettingsRelay rstatus;
219         ShellyShortStatusRelay sr;
220         int rIdx = getRelayIdx(profile, rs.id);
221         if (profile.hasRelays) {
222             if (rIdx == -1) {
223                 throw new IllegalArgumentException("Update for invalid relay index");
224             }
225             rstatus = status.relays.get(rIdx);
226             sr = relayStatus.relays.get(rIdx);
227         } else {
228             rstatus = new ShellySettingsRelay();
229             sr = new ShellyShortStatusRelay();
230             rIdx = rs.id;
231         }
232
233         sr.isValid = rstatus.isValid = true;
234         sr.name = rstatus.name = status.name;
235         if (rs.output != null) {
236             sr.ison = rstatus.ison = getBool(rs.output);
237         }
238         if (getDouble(rs.timerStartetAt) > 0) {
239             int duration = (int) (now() - rs.timerStartetAt);
240             sr.timerRemaining = duration;
241         }
242         if (rs.temperature != null) {
243             if (status.tmp == null) {
244                 status.tmp = new ShellySensorTmp();
245             }
246             status.tmp.isValid = true;
247             status.tmp.tC = rs.temperature.tC;
248             status.tmp.tF = rs.temperature.tF;
249             status.tmp.units = "C";
250             sr.temperature = getDouble(rs.temperature.tC);
251             if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
252                 status.temperature = sr.temperature;
253             }
254         }
255
256         if (rs.errors != null) {
257             for (String error : rs.errors) {
258                 sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
259                 status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
260                 status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
261             }
262             sr.overtemperature = status.overtemperature;
263         }
264
265         ShellySettingsMeter sm = new ShellySettingsMeter();
266         ShellySettingsEMeter emeter = status.emeters != null ? status.emeters.get(rIdx) : new ShellySettingsEMeter();
267         if (rs.apower != null) {
268             sm.power = emeter.power = rs.apower;
269         }
270         if (rs.aenergy != null) {
271             // Gen2 reports Watt, needs to be converted to W/h
272             sm.total = emeter.total = rs.aenergy.total;
273             sm.counters = rs.aenergy.byMinute;
274             sm.timestamp = rs.aenergy.minuteTs;
275         }
276         if (rs.voltage != null) {
277             emeter.voltage = rs.voltage;
278         }
279         if (rs.current != null) {
280             emeter.current = rs.current;
281         }
282         if (rs.pf != null) {
283             emeter.pf = rs.pf;
284         }
285
286         if (profile.hasRelays) {
287             // Update internal structures
288             status.relays.set(rIdx, rstatus);
289             relayStatus.relays.set(rIdx, sr);
290         }
291
292         updateMeter(status, rIdx, sm, emeter, channelUpdate);
293         return channelUpdate && profile.hasRelays
294                 ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rIdx)
295                 : false;
296     }
297
298     private int getRelayIdx(ShellyDeviceProfile profile, @Nullable Integer id) {
299         if (id != null && profile.settings.relays != null) {
300             int idx = 0;
301             for (ShellySettingsRelay relay : profile.settings.relays) {
302                 if (relay.isValid && relay.id != null && relay.id.intValue() == id.intValue()) {
303                     return idx;
304                 }
305                 idx++;
306             }
307         }
308         return -1;
309     }
310
311     private void updateMeter(ShellySettingsStatus status, int id, ShellySettingsMeter sm, ShellySettingsEMeter emeter,
312             boolean channelUpdate) throws ShellyApiException {
313         if (getProfile().numMeters == 0) {
314             return;
315         }
316         sm.isValid = sm.power != null || sm.total != null;
317         emeter.isValid = emeter.current != null || emeter.voltage != null || emeter.power != null
318                 || emeter.total != null;
319         status.meters.set(id, sm);
320         status.emeters.set(id, emeter);
321         relayStatus.meters.set(id, sm);
322     }
323
324     private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2StatusEm1 em, boolean channelUpdate)
325             throws ShellyApiException {
326         if (em == null) {
327             return false;
328         }
329
330         ShellySettingsMeter sm = new ShellySettingsMeter();
331         ShellySettingsEMeter emeter = status.emeters.get(em.id);
332         if (em.actPower != null) {
333             sm.power = emeter.power = em.actPower;
334         }
335         if (em.aptrPower != null) {
336             emeter.totalReturned = em.aptrPower;
337         }
338         if (em.voltage != null) {
339             emeter.voltage = em.voltage;
340         }
341         if (em.current != null) {
342             emeter.current = em.current;
343         }
344         if (em.pf != null) {
345             emeter.pf = em.pf;
346         }
347         // Update internal structures
348         updateMeter(status, em.id, sm, emeter, channelUpdate);
349
350         postAlarms(em.errors);
351         return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
352     }
353
354     private boolean updateEmStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusEm em,
355             boolean channelUpdate) throws ShellyApiException {
356         if (em == null) {
357             return false;
358         }
359
360         if (em.totalCurrent != null) {
361             status.totalCurrent = em.totalCurrent;
362         }
363         if (em.totalActPower != null) {
364             status.totalPower = em.totalActPower;
365         }
366         if (em.totalAprtPower != null) {
367             status.totalReturned = em.totalAprtPower;
368         }
369
370         ShellySettingsMeter sm = new ShellySettingsMeter();
371         ShellySettingsEMeter emeter = status.emeters.get(0);
372         if (em.aActPower != null) {
373             sm.power = emeter.power = em.aActPower;
374         }
375         if (em.aAprtPower != null) {
376             emeter.totalReturned = em.aAprtPower;
377         }
378         if (em.aVoltage != null) {
379             emeter.voltage = em.aVoltage;
380         }
381         if (em.aCurrent != null) {
382             emeter.current = em.aCurrent;
383         }
384         if (em.aPF != null) {
385             emeter.pf = em.aPF;
386         }
387         // Update internal structures
388         updateMeter(status, 0, sm, emeter, channelUpdate);
389
390         if (status.emeters.size() > 1) {
391             sm = new ShellySettingsMeter();
392             emeter = status.emeters.get(1);
393             sm.isValid = emeter.isValid = true;
394             if (em.bActPower != null) {
395                 sm.power = emeter.power = em.bActPower;
396             }
397             if (em.bAprtPower != null) {
398                 emeter.totalReturned = em.bAprtPower;
399             }
400             if (em.bVoltage != null) {
401                 emeter.voltage = em.bVoltage;
402             }
403             if (em.bCurrent != null) {
404                 emeter.current = em.bCurrent;
405             }
406             if (em.bPF != null) {
407                 emeter.pf = em.bPF;
408             }
409             // Update internal structures
410             updateMeter(status, 1, sm, emeter, channelUpdate);
411         }
412
413         if (status.emeters.size() > 2) {
414             sm = new ShellySettingsMeter();
415             emeter = status.emeters.get(2);
416             sm.isValid = emeter.isValid = true;
417             if (em.cActPower != null) {
418                 sm.power = emeter.power = em.cActPower;
419             }
420             if (em.cAprtPower != null) {
421                 emeter.totalReturned = em.cAprtPower;
422             }
423             if (em.cVoltage != null) {
424                 emeter.voltage = em.cVoltage;
425             }
426             if (em.cCurrent != null) {
427                 emeter.current = em.cCurrent;
428             }
429             if (em.cPF != null) {
430                 emeter.pf = em.cPF;
431             }
432             // Update internal structures
433             updateMeter(status, 2, sm, emeter, channelUpdate);
434         }
435
436         return channelUpdate ? ShellyComponents.updateMeters(getThing(), status) : false;
437     }
438
439     protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
440             Shelly2GetConfigResult dc) {
441         if (dc.cover0 == null) {
442             return null;
443         }
444
445         ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
446
447         addRollerSettings(rollers, dc.cover0);
448         fillRollerFavorites(profile, dc);
449         return rollers;
450     }
451
452     private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
453             @Nullable Shelly2DevConfigCover coverConfig) {
454         if (coverConfig == null) {
455             return;
456         }
457
458         ShellySettingsRoller settings = new ShellySettingsRoller();
459         settings.isValid = true;
460         settings.defaultState = coverConfig.initialState;
461         settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
462         settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
463         settings.swapInputs = coverConfig.swapInputs;
464         settings.maxtime = 0.0; // n/a
465         settings.maxtimeOpen = coverConfig.maxtimeOpen;
466         settings.maxtimeClose = coverConfig.maxtimeClose;
467         if (coverConfig.safetySwitch != null) {
468             settings.safetySwitch = coverConfig.safetySwitch.enable;
469             settings.safetyAction = coverConfig.safetySwitch.action;
470         }
471         if (coverConfig.obstructionDetection != null) {
472             settings.obstacleAction = coverConfig.obstructionDetection.action;
473             settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
474             settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
475         }
476         rollers.add(settings);
477     }
478
479     private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
480         if (dc.sys.uiData.cover != null) {
481             String[] favorites = dc.sys.uiData.cover.split(",");
482             profile.settings.favorites = new ArrayList<>();
483             for (int i = 0; i < favorites.length; i++) {
484                 ShellyFavPos fav = new ShellyFavPos();
485                 fav.pos = Integer.parseInt(favorites[i]);
486                 fav.name = fav.pos + "%";
487                 profile.settings.favorites.add(fav);
488             }
489             profile.settings.favoritesEnabled = !profile.settings.favorites.isEmpty();
490             logger.debug("{}: Roller Favorites loaded: {}", thingName,
491                     profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
492         }
493     }
494
495     private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
496             boolean updateChannels) throws ShellyApiException {
497         if (cs == null) {
498             return false;
499         }
500
501         ShellyRollerStatus rs = status.rollers.get(cs.id);
502         ShellySettingsMeter sm = status.meters.get(cs.id);
503         ShellySettingsEMeter emeter = status.emeters.get(cs.id);
504         rs.isValid = sm.isValid = emeter.isValid = true;
505         if (cs.state != null) {
506             if (!getString(rs.state).equals(cs.state)) {
507                 logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
508                         mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
509             }
510             rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
511             rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
512         }
513         if (cs.currentPos != null) {
514             rs.currentPos = cs.currentPos;
515         }
516         if (cs.moveStartedAt != null) {
517             rs.duration = (int) (now() - cs.moveStartedAt.longValue());
518         }
519         if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
520             if (status.tmp == null) {
521                 status.tmp = new ShellySensorTmp();
522             }
523             status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
524         }
525         if (cs.apower != null) {
526             rs.power = sm.power = emeter.power = cs.apower;
527         }
528         if (cs.aenergy != null) {
529             sm.total = emeter.total = cs.aenergy.total;
530             sm.counters = cs.aenergy.byMinute;
531             if (cs.aenergy.minuteTs != null) {
532                 sm.timestamp = (long) cs.aenergy.minuteTs;
533             }
534         }
535         if (cs.voltage != null) {
536             emeter.voltage = cs.voltage;
537         }
538         if (cs.current != null) {
539             emeter.current = cs.current;
540         }
541         if (cs.pf != null) {
542             emeter.pf = cs.pf;
543         }
544
545         rollerStatus.set(cs.id, rs);
546         status.rollers.set(cs.id, rs);
547         relayStatus.meters.set(cs.id, sm);
548         status.meters.set(cs.id, sm);
549         status.emeters.set(cs.id, emeter);
550
551         postAlarms(cs.errors);
552         if (rs.calibrating != null && rs.calibrating) {
553             getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
554         }
555
556         return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
557     }
558
559     protected void fillDimmerSettings(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
560         if (!profile.isDimmer || dc.light0 == null) {
561             return;
562         }
563
564         if (profile.settings.dimmers != null) {
565             ShellySettingsDimmer ds = profile.settings.dimmers.get(0);
566             ds.autoOn = dc.light0.autoOnDelay;
567             ds.autoOff = dc.light0.autoOffDelay;
568             ds.name = dc.light0.name;
569             profile.settings.dimmers.set(0, ds);
570         }
571     }
572
573     private boolean updateDimmerStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusLight value,
574             boolean channelUpdate) throws ShellyApiException {
575         ShellyDeviceProfile profile = getProfile();
576         if (!profile.isDimmer || value == null) {
577             return false;
578         }
579
580         ShellyShortLightStatus ds = status.dimmers.get(0);
581         if (value.brightness != null) {
582             ds.brightness = value.brightness.intValue();
583         }
584         ds.ison = value.output;
585         ds.hasTimer = value.timerStartedAt != null;
586         ds.timerDuration = getDuration(value.timerStartedAt, value.timerDuration);
587         status.dimmers.set(0, ds);
588         return channelUpdate ? ShellyComponents.updateDimmers(getThing(), status) : false;
589     }
590
591     protected @Nullable Integer getDuration(@Nullable Double timerStartedAt, @Nullable Integer timerDuration) {
592         if (timerStartedAt == null || timerDuration == null) {
593             return null;
594         }
595         int duration = (int) (now() - timerStartedAt.longValue());
596         return duration <= timerDuration ? timerDuration - duration : 0;
597     }
598
599     // Addon
600     private void updateAddonStatus(ShellySettingsStatus status, @Nullable Shelly2DeviceStatusResult ds)
601             throws ShellyApiException {
602         if (ds == null) {
603             return;
604         }
605
606         if (ds.temperature100 != null) {
607             if (status.extTemperature == null) {
608                 status.extTemperature = new ShellyExtTemperature();
609             }
610             status.extTemperature.sensor1 = updateExtTempSensor(ds.temperature100);
611             status.extTemperature.sensor2 = updateExtTempSensor(ds.temperature101);
612             status.extTemperature.sensor3 = updateExtTempSensor(ds.temperature102);
613             status.extTemperature.sensor4 = updateExtTempSensor(ds.temperature103);
614             status.extTemperature.sensor5 = updateExtTempSensor(ds.temperature104);
615         }
616         if (ds.humidity100 != null) {
617             status.extHumidity = new ShellyExtHumidity(ds.humidity100.rh);
618         }
619         if (ds.voltmeter100 != null) {
620             status.extVoltage = new ShellyExtVoltage(ds.voltmeter100.voltage);
621         }
622         if (ds.input100 != null) {
623             if (ds.input100.state != null) {
624                 status.extDigitalInput = new ShellyExtDigitalInput(getBool(ds.input100.state));
625             } else if (ds.input100.percent != null) {
626                 status.extAnalogInput = new ShellyExtAnalogInput(getDouble(ds.input100.percent));
627             }
628         }
629     }
630
631     private @Nullable ShellyShortTemp updateExtTempSensor(@Nullable Shelly2DeviceStatusTempId value) {
632         if (value != null) {
633             ShellyShortTemp temp = new ShellyShortTemp();
634             temp.hwID = value.id.toString();
635             temp.tC = value.tC;
636             temp.tF = value.tF;
637             return temp;
638         }
639         return null;
640     }
641
642     protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
643         if (value == null) {
644             return;
645         }
646         if (sdata.hum == null) {
647             sdata.hum = new ShellySensorHum();
648         }
649         sdata.hum.value = getDouble(value.rh);
650     }
651
652     protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
653         if (value == null) {
654             return;
655         }
656         if (sdata.tmp == null) {
657             sdata.tmp = new ShellySensorTmp();
658         }
659         sdata.tmp.isValid = true;
660         sdata.tmp.units = SHELLY_TEMP_CELSIUS;
661         sdata.tmp.tC = value.tC;
662         sdata.tmp.tF = value.tF;
663     }
664
665     protected void updateIlluminanceStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusIlluminance value) {
666         if (value == null) {
667             return;
668         }
669         if (sdata.lux == null) {
670             sdata.lux = new ShellySensorLux();
671         }
672         sdata.lux.isValid = value.lux != null;
673         sdata.lux.value = value.lux;
674         sdata.lux.illumination = value.illumination;
675     }
676
677     protected void updateSmokeStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusSmoke value) {
678         if (value == null) {
679             return;
680         }
681         sdata.smoke = getBool(value.alarm);
682         sdata.mute = getBool(value.mute);
683     }
684
685     protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
686         if (value == null) {
687             return;
688         }
689         if (sdata.bat == null) {
690             sdata.bat = new ShellySensorBat();
691         }
692
693         if (value.battery != null) {
694             sdata.bat.voltage = value.battery.volt;
695             sdata.bat.value = value.battery.percent;
696         }
697         if (value.external != null) {
698             sdata.charger = value.external.present;
699         }
700     }
701
702     private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
703         if (errors != null) {
704             for (String e : errors) {
705                 if (e != null) {
706                     getThing().postEvent(e, false);
707                 }
708             }
709         }
710     }
711
712     protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
713             Shelly2GetConfigResult dc) {
714         if (dc.input0 == null) {
715             return null; // device has no input
716         }
717
718         ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
719         addInputSettings(inputs, dc.input0);
720         addInputSettings(inputs, dc.input1);
721         addInputSettings(inputs, dc.input2);
722         addInputSettings(inputs, dc.input3);
723         return inputs;
724     }
725
726     private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
727         if (ic == null) {
728             return;
729         }
730
731         ShellySettingsInput settings = new ShellySettingsInput();
732         settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
733                 : SHELLY_BTNT_EDGE;
734         inputs.add(settings);
735     }
736
737     protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
738             boolean updateChannels) throws ShellyApiException {
739         boolean updated = false;
740         updated |= addInputStatus(status, ds.input0, updateChannels);
741         updated |= addInputStatus(status, ds.input1, updateChannels);
742         updated |= addInputStatus(status, ds.input2, updateChannels);
743         updated |= addInputStatus(status, ds.input3, updateChannels);
744         status.inputs = relayStatus.inputs;
745         return updated;
746     }
747
748     private boolean addInputStatus(ShellySettingsStatus status, @Nullable Shelly2InputStatus is, boolean updateChannels)
749             throws ShellyApiException {
750         if (is == null) {
751             return false;
752         }
753         ShellyDeviceProfile profile = getProfile();
754
755         if (is.id == null || is.id > profile.numInputs) {
756             logger.debug("{}: Invalid input id: {}", thingName, is.id);
757             return false;
758         }
759
760         String group = profile.getInputGroup(is.id);
761         ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
762                 : new ShellyInputState();
763         boolean updated = false;
764         input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
765         if (input.event == null && profile.inButtonMode(is.id)) {
766             input.event = "";
767             input.eventCount = 0;
768         }
769         if (is.percent != null) { // analogous input
770             status.extAnalogInput = new ShellyExtAnalogInput(getDouble(is.percent));
771         }
772         relayStatus.inputs.set(is.id, input);
773         if (updateChannels) {
774             updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
775         }
776         return updated;
777     }
778
779     protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
780         Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
781         request.id = Math.abs(random.nextInt());
782         request.src = "openhab-" + config.localIp; // use a unique identifier;
783         request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
784         request.params = params;
785         request.auth = authReq;
786         return request;
787     }
788
789     protected String mapValue(Map<String, String> map, @Nullable String key) {
790         String value;
791         boolean known = key != null && !key.isEmpty() && map.containsKey(key);
792         value = known ? getString(map.get(key)) : "";
793         logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
794         return value;
795     }
796
797     protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
798         return getThing().updateChannel(group, channel, value);
799     }
800
801     protected ShellyThingInterface getThing() throws ShellyApiException {
802         ShellyThingInterface t = thing;
803         if (t != null) {
804             return t;
805         }
806         throw new ShellyApiException("Thing/profile not initialized!");
807     }
808
809     protected ShellyDeviceProfile getProfile() throws ShellyApiException {
810         if (thing != null) {
811             return thing.getProfile();
812         }
813         throw new ShellyApiException("Unable to get profile, thing not initialized!");
814     }
815 }