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