]> git.basschouten.com Git - openhab-addons.git/blob
08febceff6d66af00d92f237a5819599dadf596d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.shelly.internal.discovery;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
17 import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
18
19 import java.io.IOException;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.TreeMap;
23
24 import javax.jmdns.ServiceInfo;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.openhab.binding.shelly.internal.api.ShellyApiException;
30 import org.openhab.binding.shelly.internal.api.ShellyApiResult;
31 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
32 import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
33 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
34 import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
35 import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
36 import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
37 import org.openhab.core.config.discovery.DiscoveryResult;
38 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
39 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
40 import org.openhab.core.i18n.LocaleProvider;
41 import org.openhab.core.i18n.TranslationProvider;
42 import org.openhab.core.io.net.http.HttpClientFactory;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.thing.ThingUID;
45 import org.osgi.service.cm.Configuration;
46 import org.osgi.service.cm.ConfigurationAdmin;
47 import org.osgi.service.component.ComponentContext;
48 import org.osgi.service.component.annotations.Activate;
49 import org.osgi.service.component.annotations.Component;
50 import org.osgi.service.component.annotations.Modified;
51 import org.osgi.service.component.annotations.Reference;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * This class identifies Shelly devices by their mDNS service information.
57  *
58  * @author Markus Michels - Initial contribution
59  */
60 @NonNullByDefault
61 @Component(service = MDNSDiscoveryParticipant.class, immediate = true)
62 public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
63     private final Logger logger = LoggerFactory.getLogger(ShellyDiscoveryParticipant.class);
64     private final ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
65     private final ShellyTranslationProvider messages;
66     private final HttpClient httpClient;
67     private final ConfigurationAdmin configurationAdmin;
68
69     /**
70      * OSGI Service Activation
71      *
72      * @param componentContext
73      * @param localeProvider
74      */
75     @Activate
76     public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
77             @Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
78             @Reference TranslationProvider i18nProvider, ComponentContext componentContext) {
79         logger.debug("Activating ShellyDiscovery service");
80         this.configurationAdmin = configurationAdmin;
81         this.messages = new ShellyTranslationProvider(componentContext.getBundleContext().getBundle(), i18nProvider,
82                 localeProvider);
83         this.httpClient = httpClientFactory.getCommonHttpClient();
84         bindingConfig.updateFromProperties(componentContext.getProperties());
85     }
86
87     @Override
88     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
89         return SUPPORTED_THING_TYPES_UIDS;
90     }
91
92     @Override
93     public String getServiceType() {
94         return SERVICE_TYPE;
95     }
96
97     /**
98      * Process updates to Binding Config
99      *
100      * @param componentContext
101      */
102     @Modified
103     protected void modified(final ComponentContext componentContext) {
104         logger.debug("Shelly Binding Configuration refreshed");
105         bindingConfig.updateFromProperties(componentContext.getProperties());
106     }
107
108     @Nullable
109     @Override
110     public DiscoveryResult createResult(final ServiceInfo service) {
111         String name = service.getName().toLowerCase(); // Duao: Name starts with" Shelly" rather than "shelly"
112         if (!name.startsWith("shelly")) {
113             return null;
114         }
115
116         String address = "";
117         try {
118             String mode = "";
119             String model = "unknown";
120             String deviceName = "";
121             ThingUID thingUID = null;
122             ShellyDeviceProfile profile = null;
123             Map<String, Object> properties = new TreeMap<>();
124
125             name = service.getName().toLowerCase();
126             address = service.getHostAddress();
127             if ((address == null) || address.isEmpty()) {
128                 logger.trace("{}: Shelly device discovered with empty IP address (service-name={})", name, service);
129                 return null;
130             }
131             String thingType = service.getQualifiedName().contains(SERVICE_TYPE) && name.contains("-")
132                     ? substringBeforeLast(name, "-")
133                     : name;
134             logger.debug("{}: Shelly device discovered: IP-Adress={}, type={}", name, address, thingType);
135
136             // Get device settings
137             Configuration serviceConfig = configurationAdmin.getConfiguration("binding.shelly");
138             if (serviceConfig.getProperties() != null) {
139                 bindingConfig.updateFromProperties(serviceConfig.getProperties());
140             }
141
142             ShellyThingConfiguration config = new ShellyThingConfiguration();
143             config.deviceIp = address;
144             config.userId = bindingConfig.defaultUserId;
145             config.password = bindingConfig.defaultPassword;
146
147             try {
148                 ShellyHttpApi api = new ShellyHttpApi(name, config, httpClient);
149
150                 profile = api.getDeviceProfile(thingType);
151                 logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
152                 deviceName = getString(profile.settings.name);
153                 model = getString(profile.settings.device.type);
154                 mode = profile.mode;
155
156                 properties = ShellyBaseHandler.fillDeviceProperties(profile);
157                 logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
158                         profile.deviceType, mode.isEmpty() ? "<standard>" : mode, deviceName);
159
160                 // get thing type from device name
161                 thingUID = ShellyThingCreator.getThingUID(name, model, mode, false);
162             } catch (ShellyApiException e) {
163                 ShellyApiResult result = e.getApiResult();
164                 if (result.isHttpAccessUnauthorized()) {
165                     logger.info("{}: {}", name, messages.get("discovery.protected", address));
166
167                     // create shellyunknown thing - will be changed during thing initialization with valid credentials
168                     thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
169                 } else {
170                     logger.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
171                     logger.debug("{}: Discovery failed", name, e);
172                 }
173             } catch (IllegalArgumentException e) { // maybe some format description was buggy
174                 logger.debug("{}: Discovery failed!", name, e);
175             }
176
177             if (thingUID != null) {
178                 addProperty(properties, CONFIG_DEVICEIP, address);
179                 addProperty(properties, PROPERTY_MODEL_ID, model);
180                 addProperty(properties, PROPERTY_SERVICE_NAME, name);
181                 addProperty(properties, PROPERTY_DEV_NAME, deviceName);
182                 addProperty(properties, PROPERTY_DEV_TYPE, thingType);
183                 addProperty(properties, PROPERTY_DEV_MODE, mode);
184
185                 logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
186                 String thingLabel = deviceName.isEmpty() ? name + " - " + address
187                         : deviceName + " (" + name + "@" + address + ")";
188                 return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(thingLabel)
189                         .withRepresentationProperty(name).build();
190             }
191         } catch (IOException | NullPointerException e) {
192             // maybe some format description was buggy
193             logger.debug("{}: Exception on processing serviceInfo '{}'", name, service.getNiceTextString(), e);
194         }
195         return null;
196     }
197
198     private void addProperty(Map<String, Object> properties, String key, @Nullable String value) {
199         properties.put(key, value != null ? value : "");
200     }
201
202     @Nullable
203     @Override
204     public ThingUID getThingUID(@Nullable ServiceInfo service) throws IllegalArgumentException {
205         logger.debug("ServiceInfo {}", service);
206         if (service == null) {
207             throw new IllegalArgumentException("service must not be null!");
208         }
209         String serviceName = service.getName();
210         if (serviceName == null) {
211             throw new IllegalArgumentException("serviceName must not be null!");
212         }
213         serviceName = serviceName.toLowerCase();
214         if (!serviceName.contains(VENDOR.toLowerCase())) {
215             logger.debug("Not a " + VENDOR + " device!");
216             return null;
217         }
218         return ShellyThingCreator.getThingUID(serviceName, "", "", false);
219     }
220 }