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