]> git.basschouten.com Git - openhab-addons.git/blob
8fcb53a6f7dbb2d45a486ed38477332cafb05e93
[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.ShellySensorHum;
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 @NonNullByDefault
55 public class ShellyBluApi extends Shelly2ApiRpc {
56     private static final Logger logger = LoggerFactory.getLogger(ShellyBluApi.class);
57     private boolean connected = false; // true = BLU devices has connected
58     private ShellySettingsStatus deviceStatus = new ShellySettingsStatus();
59     private int lastPid = -1;
60
61     private static final Map<String, String> MAP_INPUT_EVENT_TYPE = Map.of( //
62             SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH, //
63             SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH, //
64             SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH, //
65             SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH, //
66             SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH, //
67             SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH, //
68             "1", SHELLY_BTNEVENT_1SHORTPUSH, //
69             "2", SHELLY_BTNEVENT_2SHORTPUSH, //
70             "3", SHELLY_BTNEVENT_3SHORTPUSH, //
71             "4", SHELLY_BTNEVENT_LONGPUSH);
72
73     /**
74      * Regular constructor - called by Thing handler
75      *
76      * @param thingName Symbolic thing name
77      * @param thing Thing Handler (ThingHandlerInterface)
78      */
79     public ShellyBluApi(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
80         super(thingName, thingTable, thing);
81
82         ShellyInputState input = new ShellyInputState();
83         deviceStatus.inputs = new ArrayList<>();
84         input.input = 0;
85         input.event = "";
86         input.eventCount = 0;
87         deviceStatus.inputs.add(input);
88     }
89
90     @Override
91     public void initialize() throws ShellyApiException {
92         if (!initialized) {
93             initialized = true;
94             connected = false;
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         profile.initialized = true;
167         return profile;
168     }
169
170     @Override
171     public ShellySettingsStatus getStatus() throws ShellyApiException {
172         if (!connected) {
173             throw new ShellyApiException("Thing is not yet initialized -> status not available");
174         }
175         return deviceStatus;
176     }
177
178     @Override
179     public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
180         if (!connected) {
181             throw new ShellyApiException("Thing is not yet initialized -> sensor data not available");
182         }
183
184         return sensorData;
185     }
186
187     @Override
188     public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
189         logger.trace("{}: ShellyEvent received: {}", thingName, gson.toJson(message));
190
191         boolean updated = false;
192         ShellyBluSensorHandler t = (ShellyBluSensorHandler) thing;
193         if (t == null) {
194             logger.debug("{}: Thing is not initialized -> ignore event", thingName);
195             return;
196         }
197
198         try {
199             ShellyDeviceProfile profile = getProfile();
200
201             t.incProtMessages();
202
203             if (!connected) {
204                 connected = true;
205                 t.setThingOnline();
206             } else {
207                 t.restartWatchdog();
208             }
209
210             for (Shelly2NotifyEvent e : message.params.events) {
211                 logger.debug("{}: BluEvent received: {}", thingName, gson.toJson(message));
212                 String event = getString(e.event);
213                 if (event.startsWith(SHELLY2_EVENT_BLUPREFIX)) {
214                     logger.debug("{}: BLU event {} received from address {}, pid={}", thingName, event,
215                             getString(e.data.addr), getInteger(e.data.pid));
216                     if (e.data.pid != null) {
217                         int pid = e.data.pid;
218                         if (pid == lastPid) {
219                             logger.debug("{}: Duplicate packet for PID={} received, ignore", thingName, pid);
220                             break;
221                         }
222                         lastPid = pid;
223                     }
224                     getThing().getProfile().gateway = message.src;
225                 }
226
227                 switch (event) {
228                     case SHELLY2_EVENT_BLUSCAN:
229                         if (e.data == null || e.data.addr == null) {
230                             logger.debug("{}: Inconsistent BLU scan result ignored: {}", thingName,
231                                     gson.toJson(message));
232                             break;
233                         }
234                         logger.debug("{}: BLU Device discovered", thingName);
235                         if (e.data.name != null) {
236                             profile.settings.name = ShellyDeviceProfile.buildBluServiceName(e.data.name, e.data.addr);
237                         }
238                         break;
239                     case SHELLY2_EVENT_BLUDATA:
240                         if (e.data == null || e.data.addr == null || e.data.pid == null) {
241                             logger.debug("{}: Inconsistent BLU packet ignored: {}", thingName, gson.toJson(message));
242                             break;
243                         }
244
245                         if (e.data.battery != null) {
246                             if (sensorData.bat == null) {
247                                 sensorData.bat = new ShellySensorBat();
248                             }
249                             sensorData.bat.value = (double) e.data.battery;
250                         }
251                         if (e.data.rssi != null) {
252                             deviceStatus.wifiSta.rssi = e.data.rssi;
253                         }
254                         if (e.data.windowState != null) {
255                             if (sensorData.sensor == null) {
256                                 sensorData.sensor = new ShellySensorState();
257                             }
258                             sensorData.sensor.isValid = true;
259                             sensorData.sensor.state = e.data.windowState == 1 ? SHELLY_API_DWSTATE_OPEN
260                                     : SHELLY_API_DWSTATE_CLOSE;
261                         }
262                         if (e.data.illuminance != null) {
263                             if (sensorData.lux == null) {
264                                 sensorData.lux = new ShellySensorLux();
265                             }
266                             sensorData.lux.isValid = true;
267                             sensorData.lux.value = (double) e.data.illuminance;
268                         }
269                         if (e.data.temperature != null) {
270                             if (sensorData.tmp == null) {
271                                 sensorData.tmp = new ShellySensorTmp();
272                             }
273                             sensorData.tmp.units = SHELLY_TEMP_CELSIUS;
274                             sensorData.tmp.tC = e.data.temperature;
275                             sensorData.tmp.isValid = true;
276                         }
277                         if (e.data.humidity != null) {
278                             if (sensorData.hum == null) {
279                                 sensorData.hum = new ShellySensorHum();
280                             }
281                             sensorData.hum.value = e.data.humidity;
282                         }
283                         if (e.data.rotation != null) {
284                             if (sensorData.accel == null) {
285                                 sensorData.accel = new ShellySensorAccel();
286                             }
287                             sensorData.accel.tilt = e.data.rotation.intValue();
288                         }
289                         if (e.data.motionState != null) {
290                             sensorData.motion = e.data.motionState == 1;
291                         }
292
293                         if (e.data.buttonEvent != null) {
294                             ShellyInputState input = deviceStatus.inputs != null ? deviceStatus.inputs.get(0)
295                                     : new ShellyInputState();
296                             input.event = mapValue(MAP_INPUT_EVENT_TYPE, e.data.buttonEvent + "");
297                             input.eventCount++;
298                             deviceStatus.inputs.set(0, input);
299                             // sensorData.inputs.set(0, input);
300
301                             String group = getProfile().getInputGroup(0);
302                             String suffix = profile.getInputSuffix(0);
303                             t.updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
304                             t.updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + suffix, getDecimal(input.eventCount));
305                             t.triggerButton(profile.getInputGroup(0), 0, input.event);
306                         }
307                         updated |= ShellyComponents.updateDeviceStatus(t, deviceStatus);
308                         updated |= ShellyComponents.updateSensors(getThing(), deviceStatus);
309                         break;
310                     default:
311                         super.onNotifyEvent(message);
312                 }
313             }
314         } catch (ShellyApiException e) {
315             logger.debug("{}: Unable to process event", thingName, e);
316             t.incProtErrors();
317         }
318         if (updated) {
319         }
320     }
321 }