2 * Copyright (c) 2010-2020 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.somfytahoma.internal.discovery;
15 import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
17 import java.util.HashMap;
18 import java.util.List;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
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;
40 * The {@link SomfyTahomaItemDiscoveryService} discovers rollershutters and
41 * action groups associated with your TahomaLink cloud account.
43 * @author Ondrej Pecta - Initial contribution
46 public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
47 implements DiscoveryService, ThingHandlerService {
49 private final Logger logger = LoggerFactory.getLogger(SomfyTahomaItemDiscoveryService.class);
51 private @Nullable SomfyTahomaBridgeHandler bridgeHandler;
53 private @Nullable ScheduledFuture<?> discoveryJob;
55 private static final int DISCOVERY_TIMEOUT_SEC = 10;
56 private static final int DISCOVERY_REFRESH_SEC = 3600;
58 public SomfyTahomaItemDiscoveryService() {
59 super(DISCOVERY_TIMEOUT_SEC);
60 logger.debug("Creating discovery service");
64 public void activate() {
69 public void deactivate() {
74 public void setThingHandler(@NonNullByDefault({}) ThingHandler handler) {
75 if (handler instanceof SomfyTahomaBridgeHandler) {
76 bridgeHandler = (SomfyTahomaBridgeHandler) handler;
81 public @Nullable ThingHandler getThingHandler() {
86 protected void startBackgroundDiscovery() {
87 logger.debug("Starting SomfyTahoma background discovery");
89 ScheduledFuture<?> localDiscoveryJob = discoveryJob;
90 if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
91 discoveryJob = scheduler.scheduleWithFixedDelay(this::runDiscovery, 10, DISCOVERY_REFRESH_SEC,
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);
106 public Set<ThingTypeUID> getSupportedThingTypes() {
107 return SUPPORTED_THING_TYPES_UIDS;
111 protected void startScan() {
115 private synchronized void runDiscovery() {
116 logger.debug("Starting scanning for things...");
118 SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
119 if (localBridgeHandler != null && ThingStatus.ONLINE == localBridgeHandler.getThing().getStatus()) {
120 SomfyTahomaSetup setup = localBridgeHandler.getSetup();
126 for (SomfyTahomaDevice device : setup.getDevices()) {
127 discoverDevice(device);
129 for (SomfyTahomaGateway gw : setup.getGateways()) {
130 gatewayDiscovered(gw);
133 List<SomfyTahomaActionGroup> actions = localBridgeHandler.listActionGroups();
135 for (SomfyTahomaActionGroup group : actions) {
136 String oid = group.getOid();
137 String label = group.getLabel();
139 // actiongroups use oid as deviceURL
140 actionGroupDiscovered(label, oid, oid);
143 logger.debug("Cannot start discovery since the bridge is not online!");
147 private void discoverDevice(SomfyTahomaDevice device) {
148 logger.debug("url: {}", device.getDeviceURL());
149 switch (device.getUiClass()) {
151 // widget: PositionableHorizontalAwning
152 deviceDiscovered(device, THING_TYPE_AWNING);
154 case CLASS_CONTACT_SENSOR:
155 // widget: ContactSensor
156 deviceDiscovered(device, THING_TYPE_CONTACTSENSOR);
159 deviceDiscovered(device, THING_TYPE_CURTAIN);
161 case CLASS_EXTERIOR_SCREEN:
162 // widget: PositionableScreen
163 deviceDiscovered(device, THING_TYPE_EXTERIORSCREEN);
165 case CLASS_EXTERIOR_VENETIAN_BLIND:
166 // widget: PositionableExteriorVenetianBlind
167 deviceDiscovered(device, THING_TYPE_EXTERIORVENETIANBLIND);
169 case CLASS_GARAGE_DOOR:
170 deviceDiscovered(device, THING_TYPE_GARAGEDOOR);
173 if ("DimmerLight".equals(device.getWidget())) {
174 // widget: DimmerLight
175 deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT);
177 // widget: TimedOnOffLight
178 // widget: StatefulOnOffLight
179 deviceDiscovered(device, THING_TYPE_LIGHT);
182 case CLASS_LIGHT_SENSOR:
183 deviceDiscovered(device, THING_TYPE_LIGHTSENSOR);
185 case CLASS_OCCUPANCY_SENSOR:
186 // widget: OccupancySensor
187 deviceDiscovered(device, THING_TYPE_OCCUPANCYSENSOR);
190 // widget: StatefulOnOff
191 deviceDiscovered(device, THING_TYPE_ONOFF);
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);
201 // widget: PositionableRollerShutter
202 // widget: PositionableTiltedRollerShutter
203 deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER);
207 // widget: PositionableTiltedScreen
208 deviceDiscovered(device, THING_TYPE_SCREEN);
210 case CLASS_SMOKE_SENSOR:
211 // widget: SmokeSensor
212 deviceDiscovered(device, THING_TYPE_SMOKESENSOR);
214 case CLASS_VENETIAN_BLIND:
215 deviceDiscovered(device, THING_TYPE_VENETIANBLIND);
218 // widget: PositionableTiltedWindow
219 deviceDiscovered(device, THING_TYPE_WINDOW);
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);
229 deviceDiscovered(device, THING_TYPE_EXTERNAL_ALARM);
233 if (hasState(device, CYCLIC_BUTTON_STATE)) {
234 deviceDiscovered(device, THING_TYPE_POD);
237 case CLASS_HEATING_SYSTEM:
238 if ("SomfyThermostat".equals(device.getWidget())) {
239 deviceDiscovered(device, THING_TYPE_THERMOSTAT);
240 } else if (isOnOffHeatingSystem(device)) {
241 deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM);
243 deviceDiscovered(device, THING_TYPE_HEATING_SYSTEM);
246 case CLASS_EXTERIOR_HEATING_SYSTEM:
247 if ("DimmerExteriorHeating".equals(device.getWidget())) {
248 // widget: DimmerExteriorHeating
249 deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM);
251 logUnsupportedDevice(device);
254 case CLASS_HUMIDITY_SENSOR:
255 if (hasState(device, WATER_DETECTION_STATE)) {
256 deviceDiscovered(device, THING_TYPE_WATERSENSOR);
258 // widget: RelativeHumiditySensor
259 deviceDiscovered(device, THING_TYPE_HUMIDITYSENSOR);
261 case CLASS_DOOR_LOCK:
262 // widget: UnlockDoorLockWithUnknownPosition
263 deviceDiscovered(device, THING_TYPE_DOOR_LOCK);
266 deviceDiscovered(device, THING_TYPE_PERGOLA);
268 case CLASS_WINDOW_HANDLE:
269 // widget: ThreeWayWindowHandle
270 deviceDiscovered(device, THING_TYPE_WINDOW_HANDLE);
272 case CLASS_TEMPERATURE_SENSOR:
273 // widget: TemperatureSensor
274 deviceDiscovered(device, THING_TYPE_TEMPERATURESENSOR);
277 deviceDiscovered(device, THING_TYPE_GATE);
279 case CLASS_ELECTRICITY_SENSOR:
280 if (hasEnergyConsumption(device)) {
281 deviceDiscovered(device, THING_TYPE_ELECTRICITYSENSOR);
283 logUnsupportedDevice(device);
288 deviceDiscovered(device, THING_TYPE_DOCK);
291 deviceDiscovered(device, THING_TYPE_SIREN);
293 case CLASS_ADJUSTABLE_SLATS_ROLLER_SHUTTER:
294 deviceDiscovered(device, THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER);
297 if (hasMyfoxShutter(device)) {
298 // widget: MyFoxSecurityCamera
299 deviceDiscovered(device, THING_TYPE_MYFOX_CAMERA);
301 logUnsupportedDevice(device);
304 case THING_PROTOCOL_GATEWAY:
305 case THING_REMOTE_CONTROLLER:
306 // widget: AlarmRemoteController
307 case THING_NETWORK_COMPONENT:
310 logUnsupportedDevice(device);
314 private boolean isStateLess(SomfyTahomaDevice device) {
315 return device.getStates().isEmpty() || (device.getStates().size() == 1 && hasState(device, STATUS_STATE));
318 private void logUnsupportedDevice(SomfyTahomaDevice device) {
319 if (!isStateLess(device)) {
320 logger.info("Detected a new unsupported device: {} with widgetName: {}", device.getUiClass(),
322 logger.info("If you want to add the support, please create a new issue and attach the information below");
323 logger.info("Device definition:\n{}", device.getDefinition());
325 StringBuilder sb = new StringBuilder().append('\n');
326 for (SomfyTahomaState state : device.getStates()) {
327 sb.append(state.toString()).append('\n');
329 logger.info("Current device states: {}", sb);
333 private boolean hasState(SomfyTahomaDevice device, String state) {
334 return device.getDefinition().getStates().stream().anyMatch(st -> state.equals(st.getQualifiedName()));
337 private boolean hasMyfoxShutter(SomfyTahomaDevice device) {
338 return hasState(device, MYFOX_SHUTTER_STATUS_STATE);
341 private boolean hasEnergyConsumption(SomfyTahomaDevice device) {
342 return hasState(device, ENERGY_CONSUMPTION_STATE);
345 private boolean isSilentRollerShutter(SomfyTahomaDevice device) {
346 return hasCommmand(device, COMMAND_SET_CLOSURESPEED);
349 private boolean isUnoRollerShutter(SomfyTahomaDevice device) {
350 return hasState(device, TARGET_CLOSURE_STATE);
353 private boolean isOnOffHeatingSystem(SomfyTahomaDevice device) {
354 return hasCommmand(device, COMMAND_SET_HEATINGLEVEL);
357 private boolean hasCommmand(SomfyTahomaDevice device, String command) {
358 return device.getDefinition().getCommands().stream().anyMatch(cmd -> command.equals(cmd.getCommandName()));
361 private void deviceDiscovered(SomfyTahomaDevice device, ThingTypeUID thingTypeUID) {
362 deviceDiscovered(device.getLabel(), device.getDeviceURL(), device.getOid(), thingTypeUID,
363 hasState(device, RSSI_LEVEL_STATE));
366 private void deviceDiscovered(String label, String deviceURL, String oid, ThingTypeUID thingTypeUID, boolean rssi) {
367 Map<String, Object> properties = new HashMap<>();
368 properties.put("url", deviceURL);
369 properties.put(NAME_STATE, label);
371 properties.put(RSSI_LEVEL_STATE, "-1");
374 SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
375 if (localBridgeHandler != null) {
376 ThingUID thingUID = new ThingUID(thingTypeUID, localBridgeHandler.getThing().getUID(), oid);
378 logger.debug("Detected a/an {} - label: {} oid: {}", thingTypeUID.getId(), label, oid);
379 thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
380 .withProperties(properties).withRepresentationProperty("url").withLabel(label)
381 .withBridge(localBridgeHandler.getThing().getUID()).build());
385 private void actionGroupDiscovered(String label, String deviceURL, String oid) {
386 deviceDiscovered(label, deviceURL, oid, THING_TYPE_ACTIONGROUP, false);
389 private void gatewayDiscovered(SomfyTahomaGateway gw) {
390 Map<String, Object> properties = new HashMap<>(1);
391 String type = gatewayTypes.getOrDefault(gw.getType(), "UNKNOWN");
392 String id = gw.getGatewayId();
393 properties.put("id", id);
394 properties.put("type", type);
396 SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
397 if (localBridgeHandler != null) {
398 ThingUID thingUID = new ThingUID(THING_TYPE_GATEWAY, localBridgeHandler.getThing().getUID(), id);
400 logger.debug("Detected a gateway with id: {} and type: {}", id, type);
402 DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_GATEWAY).withProperties(properties)
403 .withRepresentationProperty("id").withLabel("Somfy Gateway (" + type + ")")
404 .withBridge(localBridgeHandler.getThing().getUID()).build());