]> git.basschouten.com Git - openhab-addons.git/blob
05ace6a722187ed4642e74489420684cfb3e49e7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.somfytahoma.internal.discovery;
14
15 import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
16
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaBridgeHandler;
27 import org.openhab.binding.somfytahoma.internal.model.*;
28 import org.openhab.core.config.discovery.AbstractDiscoveryService;
29 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
30 import org.openhab.core.config.discovery.DiscoveryService;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingTypeUID;
33 import org.openhab.core.thing.ThingUID;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.openhab.core.thing.binding.ThingHandlerService;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The {@link SomfyTahomaItemDiscoveryService} discovers rollershutters and
41  * action groups associated with your TahomaLink cloud account.
42  *
43  * @author Ondrej Pecta - Initial contribution
44  */
45 @NonNullByDefault
46 public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
47         implements DiscoveryService, ThingHandlerService {
48
49     private final Logger logger = LoggerFactory.getLogger(SomfyTahomaItemDiscoveryService.class);
50
51     private @Nullable SomfyTahomaBridgeHandler bridgeHandler;
52
53     private @Nullable ScheduledFuture<?> discoveryJob;
54
55     private static final int DISCOVERY_TIMEOUT_SEC = 10;
56     private static final int DISCOVERY_REFRESH_SEC = 3600;
57
58     public SomfyTahomaItemDiscoveryService() {
59         super(DISCOVERY_TIMEOUT_SEC);
60         logger.debug("Creating discovery service");
61     }
62
63     @Override
64     public void activate() {
65         super.activate(null);
66     }
67
68     @Override
69     public void deactivate() {
70         super.deactivate();
71     }
72
73     @Override
74     public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
75         if (handler instanceof SomfyTahomaBridgeHandler) {
76             bridgeHandler = (SomfyTahomaBridgeHandler) handler;
77         }
78     }
79
80     @Override
81     public @Nullable ThingHandler getThingHandler() {
82         return bridgeHandler;
83     }
84
85     @Override
86     protected void startBackgroundDiscovery() {
87         logger.debug("Starting SomfyTahoma background discovery");
88
89         ScheduledFuture<?> localDiscoveryJob = discoveryJob;
90         if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
91             discoveryJob = scheduler.scheduleWithFixedDelay(this::runDiscovery, 10, DISCOVERY_REFRESH_SEC,
92                     TimeUnit.SECONDS);
93         }
94     }
95
96     @Override
97     protected void stopBackgroundDiscovery() {
98         logger.debug("Stopping SomfyTahoma background discovery");
99         ScheduledFuture<?> localDiscoveryJob = discoveryJob;
100         if (localDiscoveryJob != null && !localDiscoveryJob.isCancelled()) {
101             localDiscoveryJob.cancel(true);
102         }
103     }
104
105     @Override
106     public Set<ThingTypeUID> getSupportedThingTypes() {
107         return SUPPORTED_THING_TYPES_UIDS;
108     }
109
110     @Override
111     protected void startScan() {
112         runDiscovery();
113     }
114
115     private synchronized void runDiscovery() {
116         logger.debug("Starting scanning for things...");
117
118         SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
119         if (localBridgeHandler != null && ThingStatus.ONLINE == localBridgeHandler.getThing().getStatus()) {
120             SomfyTahomaSetup setup = localBridgeHandler.getSetup();
121
122             if (setup == null) {
123                 return;
124             }
125
126             for (SomfyTahomaDevice device : setup.getDevices()) {
127                 discoverDevice(device);
128             }
129             for (SomfyTahomaGateway gw : setup.getGateways()) {
130                 gatewayDiscovered(gw);
131             }
132
133             List<SomfyTahomaActionGroup> actions = localBridgeHandler.listActionGroups();
134
135             for (SomfyTahomaActionGroup group : actions) {
136                 String oid = group.getOid();
137                 String label = group.getLabel();
138
139                 // actiongroups use oid as deviceURL
140                 actionGroupDiscovered(label, oid, oid);
141             }
142         } else {
143             logger.debug("Cannot start discovery since the bridge is not online!");
144         }
145     }
146
147     private void discoverDevice(SomfyTahomaDevice device) {
148         logger.debug("url: {}", device.getDeviceURL());
149         switch (device.getUiClass()) {
150             case CLASS_AWNING:
151                 // widget: PositionableHorizontalAwning
152                 deviceDiscovered(device, THING_TYPE_AWNING);
153                 break;
154             case CLASS_CONTACT_SENSOR:
155                 // widget: ContactSensor
156                 deviceDiscovered(device, THING_TYPE_CONTACTSENSOR);
157                 break;
158             case CLASS_CURTAIN:
159                 deviceDiscovered(device, THING_TYPE_CURTAIN);
160                 break;
161             case CLASS_EXTERIOR_SCREEN:
162                 // widget: PositionableScreen
163                 deviceDiscovered(device, THING_TYPE_EXTERIORSCREEN);
164                 break;
165             case CLASS_EXTERIOR_VENETIAN_BLIND:
166                 // widget: PositionableExteriorVenetianBlind
167                 deviceDiscovered(device, THING_TYPE_EXTERIORVENETIANBLIND);
168                 break;
169             case CLASS_GARAGE_DOOR:
170                 deviceDiscovered(device, THING_TYPE_GARAGEDOOR);
171                 break;
172             case CLASS_LIGHT:
173                 if ("DimmerLight".equals(device.getWidget())) {
174                     // widget: DimmerLight
175                     deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT);
176                 } else {
177                     // widget: TimedOnOffLight
178                     // widget: StatefulOnOffLight
179                     deviceDiscovered(device, THING_TYPE_LIGHT);
180                 }
181                 break;
182             case CLASS_LIGHT_SENSOR:
183                 deviceDiscovered(device, THING_TYPE_LIGHTSENSOR);
184                 break;
185             case CLASS_OCCUPANCY_SENSOR:
186                 // widget: OccupancySensor
187                 deviceDiscovered(device, THING_TYPE_OCCUPANCYSENSOR);
188                 break;
189             case CLASS_ON_OFF:
190                 // widget: StatefulOnOff
191                 deviceDiscovered(device, THING_TYPE_ONOFF);
192                 break;
193             case CLASS_ROLLER_SHUTTER:
194                 if (isSilentRollerShutter(device)) {
195                     // widget: PositionableRollerShutterWithLowSpeedManagement
196                     deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_SILENT);
197                 } else if (isUnoRollerShutter(device)) {
198                     // widget: PositionableRollerShutterUno
199                     deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_UNO);
200                 } else {
201                     // widget: PositionableRollerShutter
202                     // widget: PositionableTiltedRollerShutter
203                     deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER);
204                 }
205                 break;
206             case CLASS_SCREEN:
207                 // widget: PositionableTiltedScreen
208                 deviceDiscovered(device, THING_TYPE_SCREEN);
209                 break;
210             case CLASS_SMOKE_SENSOR:
211                 // widget: SmokeSensor
212                 deviceDiscovered(device, THING_TYPE_SMOKESENSOR);
213                 break;
214             case CLASS_VENETIAN_BLIND:
215                 deviceDiscovered(device, THING_TYPE_VENETIANBLIND);
216                 break;
217             case CLASS_WINDOW:
218                 // widget: PositionableTiltedWindow
219                 deviceDiscovered(device, THING_TYPE_WINDOW);
220                 break;
221             case CLASS_ALARM:
222                 if (device.getDeviceURL().startsWith("internal:")) {
223                     // widget: TSKAlarmController
224                     deviceDiscovered(device, THING_TYPE_INTERNAL_ALARM);
225                 } else if ("MyFoxAlarmController".equals(device.getWidget())) {
226                     // widget: MyFoxAlarmController
227                     deviceDiscovered(device, THING_TYPE_MYFOX_ALARM);
228                 } else {
229                     deviceDiscovered(device, THING_TYPE_EXTERNAL_ALARM);
230                 }
231                 break;
232             case CLASS_POD:
233                 if (hasState(device, CYCLIC_BUTTON_STATE)) {
234                     deviceDiscovered(device, THING_TYPE_POD);
235                 }
236                 break;
237             case CLASS_HEATING_SYSTEM:
238                 if ("SomfyThermostat".equals(device.getWidget())) {
239                     deviceDiscovered(device, THING_TYPE_THERMOSTAT);
240                 } else if ("ValveHeatingTemperatureInterface".equals(device.getWidget())) {
241                     deviceDiscovered(device, THING_TYPE_VALVE_HEATING_SYSTEM);
242                 } else if (isOnOffHeatingSystem(device)) {
243                     deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM);
244                 } else if (isZwaveHeatingSystem(device)) {
245                     deviceDiscovered(device, THING_TYPE_ZWAVE_HEATING_SYSTEM);
246                 } else {
247                     logUnsupportedDevice(device);
248                 }
249                 break;
250             case CLASS_EXTERIOR_HEATING_SYSTEM:
251                 if ("DimmerExteriorHeating".equals(device.getWidget())) {
252                     // widget: DimmerExteriorHeating
253                     deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM);
254                 } else {
255                     logUnsupportedDevice(device);
256                 }
257                 break;
258             case CLASS_HUMIDITY_SENSOR:
259                 if (hasState(device, WATER_DETECTION_STATE)) {
260                     deviceDiscovered(device, THING_TYPE_WATERSENSOR);
261                 } else {
262                     // widget: RelativeHumiditySensor
263                     deviceDiscovered(device, THING_TYPE_HUMIDITYSENSOR);
264                 }
265             case CLASS_DOOR_LOCK:
266                 // widget: UnlockDoorLockWithUnknownPosition
267                 deviceDiscovered(device, THING_TYPE_DOOR_LOCK);
268                 break;
269             case CLASS_PERGOLA:
270                 deviceDiscovered(device, THING_TYPE_PERGOLA);
271                 break;
272             case CLASS_WINDOW_HANDLE:
273                 // widget: ThreeWayWindowHandle
274                 deviceDiscovered(device, THING_TYPE_WINDOW_HANDLE);
275                 break;
276             case CLASS_TEMPERATURE_SENSOR:
277                 // widget: TemperatureSensor
278                 deviceDiscovered(device, THING_TYPE_TEMPERATURESENSOR);
279                 break;
280             case CLASS_GATE:
281                 deviceDiscovered(device, THING_TYPE_GATE);
282                 break;
283             case CLASS_ELECTRICITY_SENSOR:
284                 if (hasEnergyConsumption(device)) {
285                     deviceDiscovered(device, THING_TYPE_ELECTRICITYSENSOR);
286                 } else {
287                     logUnsupportedDevice(device);
288                 }
289                 break;
290             case CLASS_DOCK:
291                 // widget: Dock
292                 deviceDiscovered(device, THING_TYPE_DOCK);
293                 break;
294             case CLASS_SIREN:
295                 deviceDiscovered(device, THING_TYPE_SIREN);
296                 break;
297             case CLASS_ADJUSTABLE_SLATS_ROLLER_SHUTTER:
298                 deviceDiscovered(device, THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER);
299                 break;
300             case CLASS_CAMERA:
301                 if (hasMyfoxShutter(device)) {
302                     // widget: MyFoxSecurityCamera
303                     deviceDiscovered(device, THING_TYPE_MYFOX_CAMERA);
304                 } else {
305                     logUnsupportedDevice(device);
306                 }
307                 break;
308             case THING_PROTOCOL_GATEWAY:
309             case THING_REMOTE_CONTROLLER:
310                 // widget: AlarmRemoteController
311             case THING_NETWORK_COMPONENT:
312                 break;
313             default:
314                 logUnsupportedDevice(device);
315         }
316     }
317
318     private boolean isStateLess(SomfyTahomaDevice device) {
319         return device.getStates().isEmpty() || (device.getStates().size() == 1 && hasState(device, STATUS_STATE));
320     }
321
322     private void logUnsupportedDevice(SomfyTahomaDevice device) {
323         if (!isStateLess(device)) {
324             logger.info("Detected a new unsupported device: {} with widgetName: {}", device.getUiClass(),
325                     device.getWidget());
326             logger.info("If you want to add the support, please create a new issue and attach the information below");
327             logger.info("Device definition:\n{}", device.getDefinition());
328
329             StringBuilder sb = new StringBuilder().append('\n');
330             for (SomfyTahomaState state : device.getStates()) {
331                 sb.append(state.toString()).append('\n');
332             }
333             logger.info("Current device states: {}", sb);
334         }
335     }
336
337     private boolean hasState(SomfyTahomaDevice device, String state) {
338         return device.getDefinition().getStates().stream().anyMatch(st -> state.equals(st.getQualifiedName()));
339     }
340
341     private boolean hasMyfoxShutter(SomfyTahomaDevice device) {
342         return hasState(device, MYFOX_SHUTTER_STATUS_STATE);
343     }
344
345     private boolean hasEnergyConsumption(SomfyTahomaDevice device) {
346         return hasState(device, ENERGY_CONSUMPTION_STATE);
347     }
348
349     private boolean isSilentRollerShutter(SomfyTahomaDevice device) {
350         return "PositionableRollerShutterWithLowSpeedManagement".equals(device.getWidget());
351     }
352
353     private boolean isUnoRollerShutter(SomfyTahomaDevice device) {
354         return "PositionableRollerShutterUno".equals(device.getWidget());
355     }
356
357     private boolean isOnOffHeatingSystem(SomfyTahomaDevice device) {
358         return hasCommmand(device, COMMAND_SET_HEATINGLEVEL);
359     }
360
361     private boolean isZwaveHeatingSystem(SomfyTahomaDevice device) {
362         return hasState(device, ZWAVE_SET_POINT_TYPE_STATE);
363     }
364
365     private boolean hasCommmand(SomfyTahomaDevice device, String command) {
366         return device.getDefinition().getCommands().stream().anyMatch(cmd -> command.equals(cmd.getCommandName()));
367     }
368
369     private void deviceDiscovered(SomfyTahomaDevice device, ThingTypeUID thingTypeUID) {
370         deviceDiscovered(device.getLabel(), device.getDeviceURL(), device.getOid(), thingTypeUID,
371                 hasState(device, RSSI_LEVEL_STATE));
372     }
373
374     private void deviceDiscovered(String label, String deviceURL, String oid, ThingTypeUID thingTypeUID, boolean rssi) {
375         Map<String, Object> properties = new HashMap<>();
376         properties.put("url", deviceURL);
377         properties.put(NAME_STATE, label);
378         if (rssi) {
379             properties.put(RSSI_LEVEL_STATE, "-1");
380         }
381
382         SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
383         if (localBridgeHandler != null) {
384             ThingUID thingUID = new ThingUID(thingTypeUID, localBridgeHandler.getThing().getUID(), oid);
385
386             logger.debug("Detected a/an {} - label: {} oid: {}", thingTypeUID.getId(), label, oid);
387             thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
388                     .withProperties(properties).withRepresentationProperty("url").withLabel(label)
389                     .withBridge(localBridgeHandler.getThing().getUID()).build());
390         }
391     }
392
393     private void actionGroupDiscovered(String label, String deviceURL, String oid) {
394         deviceDiscovered(label, deviceURL, oid, THING_TYPE_ACTIONGROUP, false);
395     }
396
397     private void gatewayDiscovered(SomfyTahomaGateway gw) {
398         Map<String, Object> properties = new HashMap<>(1);
399         String type = gatewayTypes.getOrDefault(gw.getType(), "UNKNOWN");
400         String id = gw.getGatewayId();
401         properties.put("id", id);
402         properties.put("type", type);
403
404         SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
405         if (localBridgeHandler != null) {
406             ThingUID thingUID = new ThingUID(THING_TYPE_GATEWAY, localBridgeHandler.getThing().getUID(), id);
407
408             logger.debug("Detected a gateway with id: {} and type: {}", id, type);
409             thingDiscovered(
410                     DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GATEWAY).withProperties(properties)
411                             .withRepresentationProperty("id").withLabel("Somfy Gateway (" + type + ")")
412                             .withBridge(localBridgeHandler.getThing().getUID()).build());
413         }
414     }
415 }