]> git.basschouten.com Git - openhab-addons.git/blob
63b014b0f731a90ff396a0f48782725879424601
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.time.Instant;
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.AbstractThingHandlerDiscoveryService;
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.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.ServiceScope;
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 @Component(scope = ServiceScope.PROTOTYPE, service = Clip2ThingDiscoveryService.class)
53 @NonNullByDefault
54 public class Clip2ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<Clip2BridgeHandler> {
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 ScheduledFuture<?> discoveryTask;
70
71     public Clip2ThingDiscoveryService() {
72         super(Clip2BridgeHandler.class, Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM, THING_TYPE_ZONE),
73                 DISCOVERY_TIMEOUT_SECONDS, true);
74     }
75
76     @Override
77     public void initialize() {
78         thingHandler.registerDiscoveryService(this);
79         super.initialize();
80     }
81
82     @Override
83     public void dispose() {
84         super.dispose();
85         thingHandler.unregisterDiscoveryService();
86         removeOlderResults(Instant.now().toEpochMilli(), thingHandler.getThing().getBridgeUID());
87     }
88
89     /**
90      * If the bridge is online, then query it to get all resource types within it, which are allowed to be instantiated
91      * as OH things, and announce those respective things by calling the core 'thingDiscovered()' method.
92      */
93     private synchronized void discoverThings() {
94         if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
95             try {
96                 ThingUID bridgeUID = thingHandler.getThing().getUID();
97                 for (Entry<ResourceType, ThingTypeUID> entry : DISCOVERY_TYPES.entrySet()) {
98                     for (Resource resource : thingHandler.getResources(new ResourceReference().setType(entry.getKey()))
99                             .getResources()) {
100
101                         MetaData metaData = resource.getMetaData();
102                         if (Objects.nonNull(metaData) && (metaData.getArchetype() == Archetype.BRIDGE_V2)) {
103                             // the bridge device is handled by a bridge thing handler
104                             continue;
105                         }
106
107                         String resourceId = resource.getId();
108                         String idv1 = resource.getIdV1();
109                         String resourceType = resource.getType().toString();
110                         String resourceName = resource.getName();
111                         String thingId = resourceId;
112                         String thingLabel = resourceName;
113                         String legacyThingUID = null;
114
115                         // special zone 'all lights'
116                         if (resource.getType() == ResourceType.BRIDGE_HOME) {
117                             thingLabel = thingHandler.getLocalizedText(ALL_LIGHTS_KEY);
118                         }
119
120                         Optional<Thing> legacyThingOptional = thingHandler.getLegacyThing(idv1);
121                         if (legacyThingOptional.isPresent()) {
122                             Thing legacyThing = legacyThingOptional.get();
123                             legacyThingUID = legacyThing.getUID().getAsString();
124                             thingId = legacyThing.getUID().getId();
125                             String legacyLabel = legacyThing.getLabel();
126                             thingLabel = Objects.nonNull(legacyLabel) ? legacyLabel : thingLabel;
127                         }
128
129                         DiscoveryResultBuilder builder = DiscoveryResultBuilder
130                                 .create(new ThingUID(entry.getValue(), bridgeUID, thingId)) //
131                                 .withBridge(bridgeUID) //
132                                 .withLabel(thingLabel) //
133                                 .withProperty(PROPERTY_RESOURCE_ID, resourceId)
134                                 .withProperty(PROPERTY_RESOURCE_TYPE, resourceType)
135                                 .withProperty(PROPERTY_RESOURCE_NAME, resourceName)
136                                 .withRepresentationProperty(PROPERTY_RESOURCE_ID);
137
138                         if (Objects.nonNull(legacyThingUID)) {
139                             builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
140                         }
141                         thingDiscovered(builder.build());
142                     }
143                 }
144             } catch (ApiException | AssetNotLoadedException e) {
145                 logger.debug("discoverThings() bridge is offline or in a bad state");
146             } catch (InterruptedException e) {
147             }
148         }
149         stopScan();
150     }
151
152     @Override
153     protected void startBackgroundDiscovery() {
154         ScheduledFuture<?> discoveryTask = this.discoveryTask;
155         if (Objects.isNull(discoveryTask) || discoveryTask.isCancelled()) {
156             this.discoveryTask = scheduler.scheduleWithFixedDelay(this::discoverThings, 0, DISCOVERY_INTERVAL_SECONDS,
157                     TimeUnit.SECONDS);
158         }
159     }
160
161     @Override
162     protected void startScan() {
163         scheduler.execute(this::discoverThings);
164     }
165
166     @Override
167     protected void stopBackgroundDiscovery() {
168         ScheduledFuture<?> discoveryTask = this.discoveryTask;
169         if (Objects.nonNull(discoveryTask)) {
170             discoveryTask.cancel(true);
171             this.discoveryTask = null;
172         }
173     }
174 }