]> git.basschouten.com Git - openhab-addons.git/blob
3758cee18125e972ce81565f0a2571893ced9f85
[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.*;
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
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.shelly.internal.api.ShellyApiException;
26 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
27 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
28 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode;
29 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
30 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
31 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
32 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
33 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
34 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorAccel;
35 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
36 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorLux;
37 import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorState;
38 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
39 import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
40 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
41 import org.openhab.binding.shelly.internal.handler.ShellyBluSensorHandler;
42 import org.openhab.binding.shelly.internal.handler.ShellyComponents;
43 import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
44 import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * {@link ShellyBluApi} implementsBLU interface
50  *
51  * @author Markus Michels - Initial contribution
52  */
53 @NonNullByDefault
54 public class ShellyBluApi extends Shelly2ApiRpc {
55     private static final Logger logger = LoggerFactory.getLogger(ShellyBluApi.class);
56     private boolean connected = false; // true = BLU devices has connected
57     private ShellySettingsStatus deviceStatus = new ShellySettingsStatus();
58     private int lastPid = -1;
59
60     private static final Map<String, String> MAP_INPUT_EVENT_TYPE = Map.of( //
61             SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH, //
62             SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH, //
63             SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH, //
64             SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH, //
65             SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH, //
66             SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH, //
67             "1", SHELLY_BTNEVENT_1SHORTPUSH, //
68             "2", SHELLY_BTNEVENT_2SHORTPUSH, //
69             "3", SHELLY_BTNEVENT_3SHORTPUSH, //
70             "4", SHELLY_BTNEVENT_LONGPUSH);
71
72     /**
73      * Regular constructor - called by Thing handler
74      *
75      * @param thingName Symbolic thing name
76      * @param thing Thing Handler (ThingHandlerInterface)
77      */
78     public ShellyBluApi(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
79         super(thingName, thingTable, thing);
80
81         ShellyInputState input = new ShellyInputState();
82         deviceStatus.inputs = new ArrayList<>();
83         input.input = 0;
84         input.event = "";
85         input.eventCount = 0;
86         deviceStatus.inputs.add(input);
87     }
88
89     @Override
90     public void initialize() throws ShellyApiException {
91         if (!initialized) {
92             initialized = true;
93             connected = false;
94         } else {
95         }
96     }
97
98     @Override
99     public boolean isInitialized() {
100         return initialized;
101     }
102
103     @Override
104     public void setConfig(String thingName, ShellyThingConfiguration config) {
105         this.thingName = thingName;
106         this.config = config;
107     }
108
109     @Override
110     public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
111         ShellySettingsDevice info = new ShellySettingsDevice();
112         info.hostname = !config.serviceName.isEmpty() ? config.serviceName : "";
113         info.fw = "";
114         info.type = "BLU";
115         info.mac = config.deviceAddress;
116         info.auth = false;
117         info.gen = 2;
118         return info;
119     }
120
121     @Override
122     public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo)
123             throws ShellyApiException {
124         ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
125
126         if (devInfo != null) {
127             profile.device = devInfo;
128         }
129         profile.isBlu = true;
130         profile.settingsJson = "{}";
131         profile.thingName = thingName;
132         profile.name = getString(profile.settings.name);
133         if (profile.gateway.isEmpty()) {
134             profile.gateway = getThing().getProperty(PROPERTY_GW_DEVICE);
135         }
136
137         profile.device = getDeviceInfo();
138         if (config.serviceName.isEmpty()) {
139             config.serviceName = getString(profile.device.hostname);
140         }
141
142         // for now we have no API to get this information
143         profile.fwDate = profile.fwVersion = profile.status.update.oldVersion = "";
144         profile.status.hasUpdate = profile.status.update.hasUpdate = false;
145
146         if (profile.hasBattery) {
147             profile.settings.sleepMode = new ShellySensorSleepMode();
148             profile.settings.sleepMode.unit = "m";
149             profile.settings.sleepMode.period = 720;
150         }
151
152         if (profile.isButton) {
153             ShellySettingsInput settings = new ShellySettingsInput();
154             profile.numInputs = 1;
155             settings.btnType = SHELLY_BTNT_MOMENTARY;
156
157             if (profile.settings.inputs != null) {
158                 profile.settings.inputs.set(0, settings);
159             } else {
160                 profile.settings.inputs = new ArrayList<>();
161                 profile.settings.inputs.add(settings);
162             }
163             profile.status = deviceStatus;
164         }
165
166         if (!connected) {
167             throw new ShellyApiException("BLU Device not yet connected");
168         }
169
170         profile.initialized = true;
171         return profile;
172     }
173
174     @Override
175     public ShellySettingsStatus getStatus() throws ShellyApiException {
176         if (!connected) {
177             throw new ShellyApiException("Thing is not yet initialized -> status not available");
178         }
179         return deviceStatus;
180     }
181
182     @Override
183     public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
184         if (!connected) {
185             throw new ShellyApiException("Thing is not yet initialized -> sensor data not available");
186         }
187
188         return sensorData;
189     }
190
191     @Override
192     public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
193         logger.trace("{}: ShellyEvent received: {}", thingName, gson.toJson(message));
194
195         boolean updated = false;
196         ShellyBluSensorHandler t = (ShellyBluSensorHandler) thing;
197         if (t == null) {
198             logger.debug("{}: Thing is not initialized -> ignore event", thingName);
199             return;
200         }
201
202         try {
203             ShellyDeviceProfile profile = getProfile();
204
205             t.incProtMessages();
206
207             if (!connected) {
208                 connected = true;
209                 t.setThingOnline();
210             } else {
211                 t.restartWatchdog();
212             }
213
214             for (Shelly2NotifyEvent e : message.params.events) {
215                 logger.debug("{}: BluEvent received: {}", thingName, gson.toJson(message));
216                 String event = getString(e.event);
217                 if (event.startsWith(SHELLY2_EVENT_BLUPREFIX)) {
218                     logger.debug("{}: BLU event {} received from address {}, pid={}", thingName, event,
219                             getString(e.data.addr), getInteger(e.data.pid));
220                     if (e.data.pid != null) {
221                         int pid = e.data.pid;
222                         if (pid == lastPid) {
223                             logger.debug("{}: Duplicate packet for PID={} received, ignore", thingName, pid);
224                             break;
225                         }
226                         lastPid = pid;
227                     }
228                     getThing().getProfile().gateway = message.src;
229                 }
230
231                 switch (event) {
232                     case SHELLY2_EVENT_BLUSCAN:
233                         if (e.data == null || e.data.addr == null) {
234                             logger.debug("{}: Inconsistent BLU scan result ignored: {}", thingName,
235                                     gson.toJson(message));
236                             break;
237                         }
238                         logger.debug("{}: BLU Device discovered", thingName);
239                         if (e.data.name != null) {
240                             profile.settings.name = ShellyDeviceProfile.buildBluServiceName(e.data.name, e.data.addr);
241                         }
242                         break;
243                     case SHELLY2_EVENT_BLUDATA:
244                         if (e.data == null || e.data.addr == null || e.data.pid == null) {
245                             logger.debug("{}: Inconsistent BLU packet ignored: {}", thingName, gson.toJson(message));
246                             break;
247                         }
248
249                         if (e.data.battery != null) {
250                             if (sensorData.bat == null) {
251                                 sensorData.bat = new ShellySensorBat();
252                             }
253                             sensorData.bat.value = (double) e.data.battery;
254                         }
255                         if (e.data.rssi != null) {
256                             deviceStatus.wifiSta.rssi = e.data.rssi;
257                         }
258                         if (e.data.windowState != null) {
259                             if (sensorData.sensor == null) {
260                                 sensorData.sensor = new ShellySensorState();
261                             }
262                             sensorData.sensor.isValid = true;
263                             sensorData.sensor.state = e.data.windowState == 1 ? SHELLY_API_DWSTATE_OPEN
264                                     : SHELLY_API_DWSTATE_CLOSE;
265                         }
266                         if (e.data.illuminance != null) {
267                             if (sensorData.lux == null) {
268                                 sensorData.lux = new ShellySensorLux();
269                             }
270                             sensorData.lux.isValid = true;
271                             sensorData.lux.value = (double) e.data.illuminance;
272                         }
273                         if (e.data.temperature != null) {
274                             if (sensorData.tmp == null) {
275                                 sensorData.tmp = new ShellySensorTmp();
276                             }
277                             sensorData.tmp.tC = e.data.temperature;
278                             sensorData.tmp.isValid = true;
279                         }
280                         if (e.data.rotation != null) {
281                             if (sensorData.accel == null) {
282                                 sensorData.accel = new ShellySensorAccel();
283                             }
284                             sensorData.accel.tilt = e.data.rotation.intValue();
285                         }
286                         if (e.data.motionState != null) {
287                             sensorData.motion = e.data.motionState == 1;
288                         }
289
290                         if (e.data.buttonEvent != null) {
291                             ShellyInputState input = deviceStatus.inputs != null ? deviceStatus.inputs.get(0)
292                                     : new ShellyInputState();
293                             input.event = mapValue(MAP_INPUT_EVENT_TYPE, e.data.buttonEvent + "");
294                             input.eventCount++;
295                             deviceStatus.inputs.set(0, input);
296                             // sensorData.inputs.set(0, input);
297
298                             String group = getProfile().getInputGroup(0);
299                             String suffix = profile.getInputSuffix(0);
300                             t.updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
301                             t.updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + suffix, getDecimal(input.eventCount));
302                             t.triggerButton(profile.getInputGroup(0), 0, input.event);
303                         }
304                         updated |= ShellyComponents.updateDeviceStatus(t, deviceStatus);
305                         updated |= ShellyComponents.updateSensors(getThing(), deviceStatus);
306                         break;
307                     default:
308                         super.onNotifyEvent(message);
309                 }
310             }
311         } catch (ShellyApiException e) {
312             logger.debug("{}: Unable to process event", thingName, e);
313             t.incProtErrors();
314         }
315         if (updated) {
316         }
317     }
318 }