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.shelly.internal.discovery;
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18 import java.io.IOException;
19 import java.net.Inet4Address;
22 import javax.jmdns.ServiceInfo;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
28 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
29 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
30 import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
31 import org.openhab.core.config.discovery.DiscoveryResult;
32 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
33 import org.openhab.core.i18n.LocaleProvider;
34 import org.openhab.core.io.net.http.HttpClientFactory;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.ThingUID;
37 import org.osgi.service.cm.Configuration;
38 import org.osgi.service.cm.ConfigurationAdmin;
39 import org.osgi.service.component.ComponentContext;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Modified;
43 import org.osgi.service.component.annotations.Reference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * This class identifies Shelly devices by their mDNS service information.
50 * @author Markus Michels - Initial contribution
53 @Component(service = MDNSDiscoveryParticipant.class)
54 public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
55 private final Logger logger = LoggerFactory.getLogger(ShellyDiscoveryParticipant.class);
56 private final ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
57 private final ShellyTranslationProvider messages;
58 private final HttpClient httpClient;
59 private final ConfigurationAdmin configurationAdmin;
62 public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
63 @Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
64 @Reference ShellyTranslationProvider translationProvider, ComponentContext componentContext) {
65 logger.debug("Activating ShellyDiscovery service");
66 this.configurationAdmin = configurationAdmin;
67 this.messages = translationProvider;
68 this.httpClient = httpClientFactory.getCommonHttpClient();
69 bindingConfig.updateFromProperties(componentContext.getProperties());
73 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
74 return SUPPORTED_THING_TYPES_UIDS;
78 public String getServiceType() {
83 * Process updates to Binding Config
85 * @param componentContext
88 protected void modified(final ComponentContext componentContext) {
89 logger.debug("Shelly Binding Configuration refreshed");
90 bindingConfig.updateFromProperties(componentContext.getProperties());
95 public DiscoveryResult createResult(final ServiceInfo service) {
96 String name = service.getName().toLowerCase(); // Shelly Duo: Name starts with" Shelly" rather than "shelly"
97 if (!name.startsWith("shelly")) {
103 Inet4Address[] hostAddresses = service.getInet4Addresses();
104 if ((hostAddresses != null) && (hostAddresses.length > 0)) {
105 address = substringAfter(hostAddresses[0].toString(), "/");
107 if (address.isEmpty()) {
108 logger.trace("{}: Shelly device discovered with empty IP address (service-name={})", name, service);
111 String thingType = service.getQualifiedName().contains(SERVICE_TYPE) && name.contains("-")
112 ? substringBeforeLast(name, "-")
114 logger.debug("{}: Shelly device discovered: IP-Adress={}, type={}", name, address, thingType);
116 // Get device settings
117 Configuration serviceConfig = configurationAdmin.getConfiguration("binding.shelly");
118 if (serviceConfig.getProperties() != null) {
119 bindingConfig.updateFromProperties(serviceConfig.getProperties());
122 ShellyThingConfiguration config = new ShellyThingConfiguration();
123 config.deviceIp = address;
124 config.userId = bindingConfig.defaultUserId;
125 config.password = bindingConfig.defaultPassword;
127 String gen = getString(service.getPropertyString("gen"));
128 boolean gen2 = "2".equals(gen) || "3".equals(gen) || ShellyDeviceProfile.isGeneration2(name);
129 return ShellyBasicDiscoveryService.createResult(gen2, name, address, bindingConfig, httpClient, messages);
130 } catch (IOException | NullPointerException e) {
131 // maybe some format description was buggy
132 logger.debug("{}: Exception on processing serviceInfo '{}'", name, service.getNiceTextString(), e);
139 public ThingUID getThingUID(@Nullable ServiceInfo service) throws IllegalArgumentException {
140 logger.debug("ServiceInfo {}", service);
141 if (service == null) {
142 throw new IllegalArgumentException("service must not be null!");
144 String serviceName = service.getName();
145 if (serviceName == null) {
146 throw new IllegalArgumentException("serviceName must not be null!");
148 serviceName = serviceName.toLowerCase();
149 if (!serviceName.contains(VENDOR.toLowerCase())) {
150 logger.debug("Not a " + VENDOR + " device!");
153 return ShellyThingCreator.getThingUID(serviceName, "", "", false);