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.io.IOException;
18 import java.util.Dictionary;
19 import java.util.Objects;
20 import java.util.Optional;
23 import javax.jmdns.ServiceInfo;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.hue.internal.connection.Clip2Bridge;
28 import org.openhab.core.config.discovery.DiscoveryResult;
29 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
30 import org.openhab.core.config.discovery.DiscoveryService;
31 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingRegistry;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.osgi.service.component.ComponentContext;
37 import org.osgi.service.component.annotations.Activate;
38 import org.osgi.service.component.annotations.Component;
39 import org.osgi.service.component.annotations.Modified;
40 import org.osgi.service.component.annotations.Reference;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link HueBridgeMDNSDiscoveryParticipant} is responsible for discovering new and removed Hue Bridges. It uses the
46 * central MDNSDiscoveryService.
48 * @author Kai Kreuzer - Initial contribution
49 * @author Thomas Höfer - Added representation
50 * @author Christoph Weitkamp - Change discovery protocol to mDNS
51 * @author Andrew Fiddian-Green - Added support for CLIP 2 bridge discovery
53 @Component(configurationPid = "discovery.hue")
55 public class HueBridgeMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
57 private static final String SERVICE_TYPE = "_hue._tcp.local.";
58 private static final String MDNS_PROPERTY_BRIDGE_ID = "bridgeid";
59 private static final String MDNS_PROPERTY_MODEL_ID = "modelid";
61 private final Logger logger = LoggerFactory.getLogger(HueBridgeMDNSDiscoveryParticipant.class);
62 protected final ThingRegistry thingRegistry;
64 private long removalGracePeriod = 0L;
65 private boolean isAutoDiscoveryEnabled = true;
68 public HueBridgeMDNSDiscoveryParticipant(final @Reference ThingRegistry thingRegistry) {
69 this.thingRegistry = thingRegistry;
73 protected void activate(ComponentContext componentContext) {
74 activateOrModifyService(componentContext);
78 protected void modified(ComponentContext componentContext) {
79 activateOrModifyService(componentContext);
82 private void activateOrModifyService(ComponentContext componentContext) {
83 Dictionary<String, @Nullable Object> properties = componentContext.getProperties();
84 String autoDiscoveryPropertyValue = (String) properties
85 .get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY);
86 if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isBlank()) {
87 isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
89 String removalGracePeriodPropertyValue = (String) properties.get(REMOVAL_GRACE_PERIOD);
90 if (removalGracePeriodPropertyValue != null && !removalGracePeriodPropertyValue.isBlank()) {
92 removalGracePeriod = Long.parseLong(removalGracePeriodPropertyValue);
93 } catch (NumberFormatException e) {
94 logger.warn("Configuration property '{}' has invalid value: {}", REMOVAL_GRACE_PERIOD,
95 removalGracePeriodPropertyValue);
101 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
102 return Set.of(THING_TYPE_BRIDGE, THING_TYPE_BRIDGE_API2);
106 public String getServiceType() {
111 public @Nullable DiscoveryResult createResult(ServiceInfo service) {
112 if (isAutoDiscoveryEnabled) {
113 ThingUID uid = getThingUID(service);
114 if (Objects.nonNull(uid)) {
115 String host = service.getHostAddresses()[0];
116 String serial = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
117 String label = String.format(DISCOVERY_LABEL_PATTERN, host);
118 String legacyThingUID = null;
120 if (new ThingUID(THING_TYPE_BRIDGE_API2, uid.getId()).equals(uid)) {
121 Optional<Thing> legacyThingOptional = getLegacyBridge(host);
122 if (legacyThingOptional.isPresent()) {
123 Thing legacyThing = legacyThingOptional.get();
124 legacyThingUID = legacyThing.getUID().getAsString();
125 String label2 = legacyThing.getLabel();
126 label = Objects.nonNull(label2) ? label2 : label;
130 DiscoveryResultBuilder builder = DiscoveryResultBuilder.create(uid) //
132 .withProperty(HOST, host) //
133 .withProperty(Thing.PROPERTY_MODEL_ID, service.getPropertyString(MDNS_PROPERTY_MODEL_ID)) //
134 .withProperty(Thing.PROPERTY_SERIAL_NUMBER, serial.toLowerCase()) //
135 .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) //
138 if (Objects.nonNull(legacyThingUID)) {
139 builder = builder.withProperty(PROPERTY_LEGACY_THING_UID, legacyThingUID);
141 return builder.build();
148 * Get the legacy Hue bridge (if any) on the given IP address.
150 * @param ipAddress the IP address.
151 * @return Optional of a legacy bridge thing.
153 private Optional<Thing> getLegacyBridge(String ipAddress) {
154 return thingRegistry.getAll().stream().filter(thing -> THING_TYPE_BRIDGE.equals(thing.getThingTypeUID())
155 && ipAddress.equals(thing.getConfiguration().get(HOST))).findFirst();
159 public @Nullable ThingUID getThingUID(ServiceInfo service) {
160 String id = service.getPropertyString(MDNS_PROPERTY_BRIDGE_ID);
161 if (id != null && !id.isBlank()) {
162 id = id.toLowerCase();
164 return Clip2Bridge.isClip2Supported(service.getHostAddresses()[0])
165 ? new ThingUID(THING_TYPE_BRIDGE_API2, id)
166 : new ThingUID(THING_TYPE_BRIDGE, id);
167 } catch (IOException e) {
175 public long getRemovalGracePeriodSeconds(ServiceInfo service) {
176 return removalGracePeriod;