]> git.basschouten.com Git - openhab-addons.git/blob
1f1f9cb42e675cbdb61e81c02d23995932f4d42d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.hue.internal.discovery;
14
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
16
17 import java.util.Date;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.hue.internal.api.dto.clip2.MetaData;
29 import org.openhab.binding.hue.internal.api.dto.clip2.Resource;
30 import org.openhab.binding.hue.internal.api.dto.clip2.ResourceReference;
31 import org.openhab.binding.hue.internal.api.dto.clip2.enums.Archetype;
32 import org.openhab.binding.hue.internal.api.dto.clip2.enums.ResourceType;
33 import org.openhab.binding.hue.internal.exceptions.ApiException;
34 import org.openhab.binding.hue.internal.exceptions.AssetNotLoadedException;
35 import org.openhab.binding.hue.internal.handler.Clip2BridgeHandler;
36 import org.openhab.core.config.discovery.AbstractDiscoveryService;
37 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.ThingUID;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Discovery service to find resource things on a Hue Bridge that is running CLIP 2.
49  *
50  * @author Andrew Fiddian-Green - Initial Contribution
51  */
52 @NonNullByDefault
53 public class Clip2ThingDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
54
55     private final Logger logger = LoggerFactory.getLogger(Clip2ThingDiscoveryService.class);
56
57     private static final int DISCOVERY_TIMEOUT_SECONDS = 20;
58     private static final int DISCOVERY_INTERVAL_SECONDS = 600;
59
60     /**
61      * Map of resource types and respective thing types that shall be discovered.
62      */
63     private static final Map<ResourceType, ThingTypeUID> DISCOVERY_TYPES = Map.of( //
64             ResourceType.DEVICE, THING_TYPE_DEVICE, //
65             ResourceType.ROOM, THING_TYPE_ROOM, //
66             ResourceType.ZONE, THING_TYPE_ZONE, //
67             ResourceType.BRIDGE_HOME, THING_TYPE_ZONE);
68
69     private @Nullable Clip2BridgeHandler bridgeHandler;
70     private @Nullable ScheduledFuture<?> discoveryTask;
71
72     public Clip2ThingDiscoveryService() {
73         super(Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM, THING_TYPE_ZONE), DISCOVERY_TIMEOUT_SECONDS, true);
74     }
75
76     @Override
77     public void activate() {
78         Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
79         if (Objects.nonNull(bridgeHandler)) {
80             bridgeHandler.registerDiscoveryService(this);
81         }
82         super.activate(null);
83     }
84
85     @Override
86     public void deactivate() {
87         super.deactivate();
88         Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
89         if (Objects.nonNull(bridgeHandler)) {
90             bridgeHandler.unregisterDiscoveryService();
91             removeOlderResults(new Date().getTime(), bridgeHandler.getThing().getBridgeUID());
92             this.bridgeHandler = null;
93         }
94     }
95
96     /**
97      * If the bridge is online, then query it to get all resource types within it, which are allowed to be instantiated
98      * as OH things, and announce those respective things by calling the core 'thingDiscovered()' method.
99      */
100     private synchronized void discoverThings() {
101         Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
102         if (Objects.nonNull(bridgeHandler) && bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
103             try {
104                 ThingUID bridgeUID = bridgeHandler.getThing().getUID();
105                 for (Entry<ResourceType, ThingTypeUID> entry : DISCOVERY_TYPES.entrySet()) {
106                     for (Resource resource : bridgeHandler.getResources(new ResourceReference().setType(entry.getKey()))
107                             .getResources()) {
108
109                         MetaData metaData = resource.getMetaData();
110                         if (Objects.nonNull(metaData) && (metaData.getArchetype() == Archetype.BRIDGE_V2)) {
111                             // the bridge device is handled by a bridge thing handler
112                             continue;
113                         }
114
115                         String resourceId = resource.getId();
116                         String idv1 = resource.getIdV1();
117                         String resourceType = resource.getType().toString();
118                         String resourceName = resource.getName();
119                         String thingId = resourceId;
120                         String thingLabel = resourceName;
121                         String legacyThingUID = null;
122
123                         // special zone 'all lights'
124                         if (resource.getType() == ResourceType.BRIDGE_HOME) {
125                             thingLabel = bridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
126                         }
127
128                         Optional<Thing> legacyThingOptional = bridgeHandler.getLegacyThing(idv1);
129                         if (legacyThingOptional.isPresent()) {
130                             Thing legacyThing = legacyThingOptional.get();
131                             legacyThingUID = legacyThing.getUID().getAsString();
132                             thingId = legacyThing.getUID().getId();
133                             String legacyLabel = legacyThing.getLabel();
134                             thingLabel = Objects.nonNull(legacyLabel) ? legacyLabel : thingLabel;
135                         }
136
137                         DiscoveryResultBuilder builder = DiscoveryResultBuilder
138                                 .create(new ThingUID(entry.getValue(), bridgeUID, thingId)) //
139                                 .withBridge(bridgeUID) //
140                                 .withLabel(thingLabel) //
141                                 .withProperty(PROPERTY_RESOURCE_ID, resourceId)
142                                 .withProperty(PROPERTY_RESOURCE_TYPE, resourceType)
143                                 .withProperty(PROPERTY_RESOURCE_NAME, resourceName)
144                                 .withRepresentationProperty(PROPERTY_RESOURCE_ID);
145
146                         if (Objects.nonNull(legacyThingUID)) {
147                             builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
148                         }
149                         thingDiscovered(builder.build());
150                     }
151                 }
152             } catch (ApiException | AssetNotLoadedException e) {
153                 logger.debug("discoverThings() bridge is offline or in a bad state");
154             } catch (InterruptedException e) {
155             }
156         }
157         stopScan();
158     }
159
160     @Override
161     public @Nullable ThingHandler getThingHandler() {
162         return bridgeHandler;
163     }
164
165     @Override
166     public void setThingHandler(ThingHandler handler) {
167         if (handler instanceof Clip2BridgeHandler) {
168             bridgeHandler = (Clip2BridgeHandler) handler;
169         }
170     }
171
172     @Override
173     protected void startBackgroundDiscovery() {
174         ScheduledFuture<?> discoveryTask = this.discoveryTask;
175         if (Objects.isNull(discoveryTask) || discoveryTask.isCancelled()) {
176             this.discoveryTask = scheduler.scheduleWithFixedDelay(this::discoverThings, 0, DISCOVERY_INTERVAL_SECONDS,
177                     TimeUnit.SECONDS);
178         }
179     }
180
181     @Override
182     protected void startScan() {
183         scheduler.execute(this::discoverThings);
184     }
185
186     @Override
187     protected void stopBackgroundDiscovery() {
188         ScheduledFuture<?> discoveryTask = this.discoveryTask;
189         if (Objects.nonNull(discoveryTask)) {
190             discoveryTask.cancel(true);
191             this.discoveryTask = null;
192         }
193     }
194 }