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