]> git.basschouten.com Git - openhab-addons.git/blob
6c5c706b176dd4d2df36ac527ed43601499049b5
[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.velux.internal.discovery;
14
15 import static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
16
17 import java.util.HashSet;
18 import java.util.Set;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.velux.internal.VeluxBindingConstants;
25 import org.openhab.binding.velux.internal.VeluxBindingProperties;
26 import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
27 import org.openhab.binding.velux.internal.handler.VeluxBridgeHandler;
28 import org.openhab.binding.velux.internal.things.VeluxProduct;
29 import org.openhab.binding.velux.internal.things.VeluxProductSerialNo;
30 import org.openhab.binding.velux.internal.things.VeluxScene;
31 import org.openhab.binding.velux.internal.utils.Localization;
32 import org.openhab.binding.velux.internal.utils.ManifestInformation;
33 import org.openhab.core.config.discovery.AbstractDiscoveryService;
34 import org.openhab.core.config.discovery.DiscoveryResult;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.config.discovery.DiscoveryService;
37 import org.openhab.core.i18n.LocaleProvider;
38 import org.openhab.core.i18n.LocationProvider;
39 import org.openhab.core.i18n.TranslationProvider;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.ThingUID;
42 import org.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.Reference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link VeluxDiscoveryService} is responsible for discovering actuators and scenes on the current Velux Bridge.
49  *
50  * @author Guenther Schreiner - Initial contribution.
51  */
52 @NonNullByDefault
53 @Component(service = DiscoveryService.class, configurationPid = "discovery.velux")
54 public class VeluxDiscoveryService extends AbstractDiscoveryService implements Runnable {
55     private final Logger logger = LoggerFactory.getLogger(VeluxDiscoveryService.class);
56
57     // Class internal
58
59     private static final int DISCOVER_TIMEOUT_SECONDS = 60;
60
61     private @NonNullByDefault({}) LocaleProvider localeProvider;
62     private @NonNullByDefault({}) TranslationProvider i18nProvider;
63     private Localization localization = Localization.UNKNOWN;
64     private final Set<VeluxBridgeHandler> bridgeHandlers = new HashSet<>();
65
66     @Nullable
67     private ScheduledFuture<?> backgroundTask = null;
68
69     // Private
70
71     @SuppressWarnings("PMD.CompareObjectsWithEquals")
72     private void updateLocalization() {
73         if (localization == Localization.UNKNOWN && localeProvider != null && i18nProvider != null) {
74             logger.trace("updateLocalization(): creating Localization based on locale={},translation={}).",
75                     localeProvider, i18nProvider);
76             localization = new Localization(localeProvider, i18nProvider);
77         }
78     }
79
80     /**
81      * Constructor
82      * <P>
83      * Initializes the {@link VeluxDiscoveryService} without any further information.
84      */
85     public VeluxDiscoveryService() {
86         super(VeluxBindingConstants.DISCOVERABLE_THINGS, DISCOVER_TIMEOUT_SECONDS);
87         logger.trace("VeluxDiscoveryService(without Bridge) just initialized.");
88     }
89
90     @Reference
91     protected void setLocaleProvider(final LocaleProvider givenLocaleProvider) {
92         logger.trace("setLocaleProvider(): provided locale={}.", givenLocaleProvider);
93         localeProvider = givenLocaleProvider;
94         updateLocalization();
95     }
96
97     @Reference
98     protected void setTranslationProvider(TranslationProvider givenI18nProvider) {
99         logger.trace("setTranslationProvider(): provided translation={}.", givenI18nProvider);
100         i18nProvider = givenI18nProvider;
101         updateLocalization();
102     }
103
104     /**
105      * Constructor
106      * <P>
107      * Initializes the {@link VeluxDiscoveryService} with a reference to the well-prepared environment with a
108      * {@link VeluxBridgeHandler}.
109      *
110      * @param localizationHandler Initialized localization handler.
111      */
112     public VeluxDiscoveryService(Localization localizationHandler) {
113         super(VeluxBindingConstants.DISCOVERABLE_THINGS, DISCOVER_TIMEOUT_SECONDS);
114         logger.trace("VeluxDiscoveryService(locale={},i18n={}) just initialized.", localeProvider, i18nProvider);
115         localization = localizationHandler;
116     }
117
118     /**
119      * Constructor
120      * <P>
121      * Initializes the {@link VeluxDiscoveryService} with a reference to the well-prepared environment with a
122      * {@link VeluxBridgeHandler}.
123      *
124      * @param locationProvider Provider for a location.
125      * @param localeProvider Provider for a locale.
126      * @param i18nProvider Provider for the internationalization.
127      */
128     public VeluxDiscoveryService(LocationProvider locationProvider, LocaleProvider localeProvider,
129             TranslationProvider i18nProvider) {
130         this(new Localization(localeProvider, i18nProvider));
131         logger.trace("VeluxDiscoveryService(locale={},i18n={}) finished.", localeProvider, i18nProvider);
132     }
133
134     @Override
135     public void deactivate() {
136         logger.trace("deactivate() called.");
137         super.deactivate();
138     }
139
140     @Override
141     protected synchronized void startScan() {
142         logger.trace("startScan() called.");
143
144         logger.debug("startScan(): creating a thing of type binding.");
145         ThingUID thingUID = new ThingUID(THING_TYPE_BINDING, "org_openhab_binding_velux");
146         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
147                 .withProperty(VeluxBindingProperties.PROPERTY_BINDING_BUNDLEVERSION,
148                         ManifestInformation.getBundleVersion())
149                 .withRepresentationProperty(VeluxBindingProperties.PROPERTY_BINDING_BUNDLEVERSION)
150                 .withLabel(localization.getText("discovery.velux.binding...label")).build();
151         logger.debug("startScan(): registering new thing {}.", discoveryResult);
152         thingDiscovered(discoveryResult);
153
154         scheduler.execute(() -> {
155             discoverBridges();
156         });
157
158         if (bridgeHandlers.isEmpty()) {
159             logger.debug("startScan(): VeluxDiscoveryService cannot proceed due to missing Velux bridge(s).");
160         } else {
161             logger.debug("startScan(): Starting Velux discovery scan for scenes and actuators.");
162             discoverScenes();
163             discoverProducts();
164         }
165         logger.trace("startScan() done.");
166     }
167
168     @Override
169     public synchronized void stopScan() {
170         logger.trace("stopScan() called.");
171         super.stopScan();
172         logger.trace("stopScan() done.");
173     }
174
175     @Override
176     public void run() {
177         logger.trace("run() called.");
178     }
179
180     /**
181      * Discover the gateway-defined scenes.
182      */
183     private void discoverScenes() {
184         logger.trace("discoverScenes() called.");
185         for (VeluxBridgeHandler bridgeHandlerX : bridgeHandlers) {
186             ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
187             logger.debug("discoverScenes(): discovering all scenes on bridge {}.", bridgeUID);
188             for (VeluxScene scene : bridgeHandlerX.existingScenes().values()) {
189                 String sceneName = scene.getName().toString();
190                 logger.trace("discoverScenes(): found scene {}.", sceneName);
191
192                 String label = sceneName.replaceAll("\\P{Alnum}", "_");
193                 logger.trace("discoverScenes(): using label {}.", label);
194
195                 ThingTypeUID thingTypeUID = THING_TYPE_VELUX_SCENE;
196                 ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
197                 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
198                         .withProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME, sceneName)
199                         .withRepresentationProperty(VeluxBindingProperties.PROPERTY_SCENE_NAME).withBridge(bridgeUID)
200                         .withLabel(label).build();
201                 logger.debug("discoverScenes(): registering new thing {}.", discoveryResult);
202                 thingDiscovered(discoveryResult);
203             }
204         }
205         logger.trace("discoverScenes() finished.");
206     }
207
208     /**
209      * Discover the gateway-defined products/actuators.
210      */
211     private void discoverProducts() {
212         logger.trace("discoverProducts() called.");
213         for (VeluxBridgeHandler bridgeHandlerX : bridgeHandlers) {
214             ThingUID bridgeUID = bridgeHandlerX.getThing().getUID();
215             logger.debug("discoverProducts(): discovering all actuators on bridge {}.", bridgeUID);
216             for (VeluxProduct product : bridgeHandlerX.existingProducts().values()) {
217                 String serialNumber = product.getSerialNumber();
218                 String actuatorName = product.getProductName().toString();
219                 logger.trace("discoverProducts() found actuator {} (name {}).", serialNumber, actuatorName);
220                 String identifier;
221                 if (serialNumber.equals(VeluxProductSerialNo.UNKNOWN)) {
222                     identifier = actuatorName;
223                 } else {
224                     identifier = serialNumber;
225                 }
226                 String label = actuatorName.replaceAll("\\P{Alnum}", "_");
227                 logger.trace("discoverProducts(): using label {}.", label);
228                 ThingTypeUID thingTypeUID;
229                 boolean isInverted = false;
230                 logger.trace("discoverProducts() dealing with {} (type {}).", product, product.getProductType());
231                 switch (product.getProductType()) {
232                     case SLIDER_WINDOW:
233                         logger.trace("discoverProducts(): creating window.");
234                         thingTypeUID = THING_TYPE_VELUX_WINDOW;
235                         isInverted = true;
236                         break;
237
238                     case SLIDER_SHUTTER:
239                         logger.trace("discoverProducts(): creating rollershutter.");
240                         thingTypeUID = THING_TYPE_VELUX_ROLLERSHUTTER;
241                         break;
242
243                     default:
244                         logger.trace("discoverProducts(): creating actuator.");
245                         thingTypeUID = THING_TYPE_VELUX_ACTUATOR;
246                 }
247                 ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, label);
248
249                 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
250                         .withProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER, identifier)
251                         .withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_NAME, actuatorName)
252                         .withProperty(VeluxBindingProperties.PROPERTY_ACTUATOR_INVERTED, isInverted)
253                         .withRepresentationProperty(VeluxBindingProperties.CONFIG_ACTUATOR_SERIALNUMBER)
254                         .withBridge(bridgeUID).withLabel(actuatorName).build();
255                 logger.debug("discoverProducts(): registering new thing {}.", discoveryResult);
256                 thingDiscovered(discoveryResult);
257             }
258         }
259         logger.trace("discoverProducts() finished.");
260     }
261
262     /**
263      * Add a {@link VeluxBridgeHandler} to the {@link VeluxDiscoveryService}
264      *
265      * @param bridge Velux bridge handler.
266      * @return true if the bridge was added, or false if it was already present
267      */
268     public boolean addBridge(VeluxBridgeHandler bridge) {
269         if (!bridgeHandlers.contains(bridge)) {
270             logger.trace("VeluxDiscoveryService(): registering bridge {} for discovery.", bridge);
271             bridgeHandlers.add(bridge);
272             return true;
273         }
274         logger.trace("VeluxDiscoveryService(): bridge {} already registered for discovery.", bridge);
275         return false;
276     }
277
278     /**
279      * Remove a {@link VeluxBridgeHandler} from the {@link VeluxDiscoveryService}
280      *
281      * @param bridge Velux bridge handler.
282      * @return true if the bridge was removed, or false if it was not present
283      */
284     public boolean removeBridge(VeluxBridgeHandler bridge) {
285         return bridgeHandlers.remove(bridge);
286     }
287
288     /**
289      * Check if the {@link VeluxDiscoveryService} list of {@link VeluxBridgeHandler} is empty
290      *
291      * @return true if empty
292      */
293     public boolean isEmpty() {
294         return bridgeHandlers.isEmpty();
295     }
296
297     /**
298      * Discover any bridges on the network that are not yet instantiated.
299      */
300     private void discoverBridges() {
301         // discover the list of IP addresses of bridges on the network
302         Set<String> foundBridgeIpAddresses = VeluxBridgeFinder.discoverIpAddresses(scheduler);
303         // publish discovery results
304         for (String ipAddr : foundBridgeIpAddresses) {
305             ThingUID thingUID = new ThingUID(THING_TYPE_BRIDGE, ipAddr.replace(".", "_"));
306             DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_BRIDGE)
307                     .withProperty(VeluxBridgeConfiguration.BRIDGE_IPADDRESS, ipAddr)
308                     .withRepresentationProperty(VeluxBridgeConfiguration.BRIDGE_IPADDRESS)
309                     .withLabel(String.format("Velux Bridge (%s)", ipAddr)).build();
310             thingDiscovered(result);
311         }
312     }
313
314     @Override
315     protected void startBackgroundDiscovery() {
316         logger.trace("startBackgroundDiscovery() called.");
317         ScheduledFuture<?> task = this.backgroundTask;
318         if (task == null || task.isCancelled()) {
319             this.backgroundTask = scheduler.scheduleWithFixedDelay(this::startScan, 10, 600, TimeUnit.SECONDS);
320         }
321     }
322
323     @Override
324     protected void stopBackgroundDiscovery() {
325         logger.trace("stopBackgroundDiscovery() called.");
326         ScheduledFuture<?> task = this.backgroundTask;
327         if (task != null) {
328             task.cancel(true);
329         }
330     }
331 }