2 * Copyright (c) 2010-2023 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.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;
48 * Discovery service to find resource things on a Hue Bridge that is running CLIP 2.
50 * @author Andrew Fiddian-Green - Initial Contribution
53 public class Clip2ThingDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
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 Clip2BridgeHandler bridgeHandler;
70 private @Nullable ScheduledFuture<?> discoveryTask;
72 public Clip2ThingDiscoveryService() {
73 super(Set.of(THING_TYPE_DEVICE, THING_TYPE_ROOM, THING_TYPE_ZONE), DISCOVERY_TIMEOUT_SECONDS, true);
77 public void activate() {
78 Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
79 if (Objects.nonNull(bridgeHandler)) {
80 bridgeHandler.registerDiscoveryService(this);
86 public void 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;
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.
100 private synchronized void discoverThings() {
101 Clip2BridgeHandler bridgeHandler = this.bridgeHandler;
102 if (Objects.nonNull(bridgeHandler) && bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
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()))
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
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;
123 // special zone 'all lights'
124 if (resource.getType() == ResourceType.BRIDGE_HOME) {
125 thingLabel = bridgeHandler.getLocalizedText(ALL_LIGHTS_KEY);
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;
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);
146 if (Objects.nonNull(legacyThingUID)) {
147 builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
149 thingDiscovered(builder.build());
152 } catch (ApiException | AssetNotLoadedException e) {
153 logger.debug("discoverThings() bridge is offline or in a bad state");
154 } catch (InterruptedException e) {
161 public @Nullable ThingHandler getThingHandler() {
162 return bridgeHandler;
166 public void setThingHandler(ThingHandler handler) {
167 if (handler instanceof Clip2BridgeHandler) {
168 bridgeHandler = (Clip2BridgeHandler) handler;
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,
182 protected void startScan() {
183 scheduler.execute(this::discoverThings);
187 protected void stopBackgroundDiscovery() {
188 ScheduledFuture<?> discoveryTask = this.discoveryTask;
189 if (Objects.nonNull(discoveryTask)) {
190 discoveryTask.cancel(true);
191 this.discoveryTask = null;