2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.shelly.internal.api2;
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.*;
21 import java.util.ArrayList;
22 import java.util.HashMap;
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;
50 * {@link ShellyBluApi} implementsBLU interface
52 * @author Markus Michels - Initial contribution
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;
60 private static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
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);
75 * Regular constructor - called by Thing handler
77 * @param thingName Symbolic thing name
78 * @param thing Thing Handler (ThingHandlerInterface)
80 public ShellyBluApi(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
81 super(thingName, thingTable, thing);
83 ShellyInputState input = new ShellyInputState();
84 deviceStatus.inputs = new ArrayList<>();
88 deviceStatus.inputs.add(input);
92 public void initialize() throws ShellyApiException {
101 public boolean isInitialized() {
106 public void setConfig(String thingName, ShellyThingConfiguration config) {
107 this.thingName = thingName;
108 this.config = config;
112 public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
113 ShellySettingsDevice info = new ShellySettingsDevice();
114 info.hostname = !config.serviceName.isEmpty() ? config.serviceName : "";
117 info.mac = config.deviceAddress;
124 public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo)
125 throws ShellyApiException {
126 ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
128 if (devInfo != null) {
129 profile.device = devInfo;
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);
139 ShellySettingsDevice device = getDeviceInfo();
140 if (config.serviceName.isEmpty()) {
141 config.serviceName = getString(profile.device.hostname);
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;
148 if (profile.hasBattery) {
149 profile.settings.sleepMode = new ShellySensorSleepMode();
150 profile.settings.sleepMode.unit = "m";
151 profile.settings.sleepMode.period = 720;
154 if (profile.isButton) {
155 ShellySettingsInput settings = new ShellySettingsInput();
156 profile.numInputs = 1;
157 settings.btnType = SHELLY_BTNT_MOMENTARY;
159 if (profile.settings.inputs != null) {
160 profile.settings.inputs.set(0, settings);
162 profile.settings.inputs = new ArrayList<>();
163 profile.settings.inputs.add(settings);
165 profile.status = deviceStatus;
169 throw new ShellyApiException("BLU Device not yet connected");
172 profile.initialized = true;
177 public ShellySettingsStatus getStatus() throws ShellyApiException {
179 throw new ShellyApiException("Thing is not yet initialized -> status not available");
185 public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
187 throw new ShellyApiException("Thing is not yet initialized -> sensor data not available");
194 public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
195 logger.trace("{}: ShellyEvent received: {}", thingName, gson.toJson(message));
197 boolean updated = false;
198 ShellyBluSensorHandler t = (ShellyBluSensorHandler) thing;
200 logger.debug("{}: Thing is not initialized -> ignore event", thingName);
205 ShellyDeviceProfile profile = getProfile();
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);
230 getThing().getProfile().gateway = message.src;
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));
240 logger.debug("{}: BLU Device discovered", thingName);
241 if (e.data.name != null) {
242 profile.settings.name = buildBluServiceName(e.data.name, e.data.addr);
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));
251 if (e.data.battery != null) {
252 if (sensorData.bat == null) {
253 sensorData.bat = new ShellySensorBat();
255 sensorData.bat.value = (double) e.data.battery;
257 if (e.data.rssi != null) {
258 deviceStatus.wifiSta.rssi = e.data.rssi;
260 if (e.data.windowState != null) {
261 if (sensorData.sensor == null) {
262 sensorData.sensor = new ShellySensorState();
264 sensorData.sensor.isValid = true;
265 sensorData.sensor.state = e.data.windowState == 1 ? SHELLY_API_DWSTATE_OPEN
266 : SHELLY_API_DWSTATE_CLOSE;
268 if (e.data.illuminance != null) {
269 if (sensorData.lux == null) {
270 sensorData.lux = new ShellySensorLux();
272 sensorData.lux.isValid = true;
273 sensorData.lux.value = (double) e.data.illuminance;
275 if (e.data.temperature != null) {
276 if (sensorData.tmp == null) {
277 sensorData.tmp = new ShellySensorTmp();
279 sensorData.tmp.tC = e.data.temperature;
280 sensorData.tmp.isValid = true;
282 if (e.data.rotation != null) {
283 if (sensorData.accel == null) {
284 sensorData.accel = new ShellySensorAccel();
286 sensorData.accel.tilt = e.data.rotation.intValue();
288 if (e.data.motionState != null) {
289 sensorData.motion = e.data.motionState == 1;
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 + "");
297 deviceStatus.inputs.set(0, input);
298 // sensorData.inputs.set(0, input);
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);
306 updated |= ShellyComponents.updateDeviceStatus(t, deviceStatus);
307 updated |= ShellyComponents.updateSensors(getThing(), deviceStatus);
310 super.onNotifyEvent(message);
313 } catch (ShellyApiException e) {
314 logger.debug("{}: Unable to process event", thingName, e);
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
324 case SHELLYDT_BLUBUTTON:
325 return (THING_TYPE_SHELLYBLUBUTTON_STR + "-" + mac).toLowerCase();
327 return (THING_TYPE_SHELLYBLUDW_STR + "-" + mac).toLowerCase();
328 case SHELLYDT_BLUMOTION:
329 return (THING_TYPE_SHELLYBLUMOTION_STR + "-" + mac).toLowerCase();
331 throw new IllegalArgumentException("Unsupported BLU device model " + model);