2 * Copyright (c) 2010-2024 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.hue.internal.discovery;
15 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
17 import java.util.Date;
19 import java.util.Map.Entry;
20 import java.util.Objects;
21 import java.util.Optional;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
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;
48 * Discovery service to find resource things on a Hue Bridge that is running CLIP 2.
50 * @author Andrew Fiddian-Green - Initial Contribution
52 @Component(scope = ServiceScope.PROTOTYPE, service = Clip2ThingDiscoveryService.class)
54 public class Clip2ThingDiscoveryService extends AbstractThingHandlerDiscoveryService<Clip2BridgeHandler> {
55 private final Logger logger = LoggerFactory.getLogger(Clip2ThingDiscoveryService.class);
57 private static final int DISCOVERY_TIMEOUT_SECONDS = 20;
58 private static final int DISCOVERY_INTERVAL_SECONDS = 600;
61 * Map of resource types and respective thing types that shall be discovered.
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);
69 private @Nullable ScheduledFuture<?> discoveryTask;
71 public Clip2ThingDiscoveryService() {
72 super(Clip2BridgeHandler.class, Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM, THING_TYPE_ZONE),
73 DISCOVERY_TIMEOUT_SECONDS, true);
77 public void initialize() {
78 thingHandler.registerDiscoveryService(this);
83 public void dispose() {
85 thingHandler.unregisterDiscoveryService();
86 removeOlderResults(new Date().getTime(), thingHandler.getThing().getBridgeUID());
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.
93 private synchronized void discoverThings() {
94 if (thingHandler.getThing().getStatus() == ThingStatus.ONLINE) {
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()))
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
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;
115 // special zone 'all lights'
116 if (resource.getType() == ResourceType.BRIDGE_HOME) {
117 thingLabel = thingHandler.getLocalizedText(ALL_LIGHTS_KEY);
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;
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);
138 if (Objects.nonNull(legacyThingUID)) {
139 builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
141 thingDiscovered(builder.build());
144 } catch (ApiException | AssetNotLoadedException e) {
145 logger.debug("discoverThings() bridge is offline or in a bad state");
146 } catch (InterruptedException e) {
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,
162 protected void startScan() {
163 scheduler.execute(this::discoverThings);
167 protected void stopBackgroundDiscovery() {
168 ScheduledFuture<?> discoveryTask = this.discoveryTask;
169 if (Objects.nonNull(discoveryTask)) {
170 discoveryTask.cancel(true);
171 this.discoveryTask = null;