2 * Copyright (c) 2010-2020 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.netatmo.internal.discovery;
15 import static org.openhab.binding.netatmo.internal.APIUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
18 import java.util.HashMap;
19 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
25 import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener;
26 import org.openhab.core.config.discovery.AbstractDiscoveryService;
27 import org.openhab.core.config.discovery.DiscoveryResult;
28 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
29 import org.openhab.core.i18n.LocaleProvider;
30 import org.openhab.core.i18n.TranslationProvider;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingTypeUID;
33 import org.openhab.core.thing.ThingUID;
34 import org.osgi.framework.Bundle;
35 import org.osgi.framework.FrameworkUtil;
37 import io.swagger.client.model.*;
40 * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo
41 * devices and modules connected to the API console
43 * @author Gaƫl L'hopital - Initial contribution
44 * @author Ing. Peter Weiss - Welcome camera implementation
48 public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener {
49 private static final int SEARCH_TIME = 5;
50 private final NetatmoBridgeHandler netatmoBridgeHandler;
52 public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider,
53 TranslationProvider translationProvider) {
54 super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME);
55 this.netatmoBridgeHandler = netatmoBridgeHandler;
56 this.localeProvider = localeProvider;
57 this.i18nProvider = translationProvider;
61 public void activate(@Nullable Map<String, @Nullable Object> configProperties) {
62 super.activate(configProperties);
63 netatmoBridgeHandler.registerDataListener(this);
67 public void deactivate() {
68 netatmoBridgeHandler.unregisterDataListener(this);
73 public void startScan() {
74 if (netatmoBridgeHandler.configuration.readStation) {
75 netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> {
76 nonNullList(dataBody.getDevices()).forEach(station -> {
77 discoverWeatherStation(station);
81 if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
82 netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
83 nonNullList(dataBody.getDevices()).forEach(homecoach -> {
84 discoverHomeCoach(homecoach);
88 if (netatmoBridgeHandler.configuration.readThermostat) {
89 netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
90 nonNullList(dataBody.getDevices()).forEach(plug -> {
91 discoverThermostat(plug);
95 if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
96 netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
97 nonNullList(dataBody.getHomes()).forEach(home -> {
98 discoverWelcomeHome(home);
105 protected synchronized void stopScan() {
107 removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID());
111 public void onDataRefreshed(Object data) {
112 if (!isBackgroundDiscoveryEnabled()) {
115 if (data instanceof NAMain) {
116 discoverWeatherStation((NAMain) data);
117 } else if (data instanceof NAPlug) {
118 discoverThermostat((NAPlug) data);
119 } else if (data instanceof NAHealthyHomeCoach) {
120 discoverHomeCoach((NAHealthyHomeCoach) data);
121 } else if (data instanceof NAWelcomeHome) {
122 discoverWelcomeHome((NAWelcomeHome) data);
126 private void discoverThermostat(NAPlug plug) {
127 onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware());
128 nonNullList(plug.getModules()).forEach(thermostat -> {
129 onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(),
130 thermostat.getFirmware());
134 private void discoverHomeCoach(NAHealthyHomeCoach homecoach) {
135 onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(),
136 homecoach.getFirmware());
139 private void discoverWeatherStation(NAMain station) {
140 final boolean isFavorite = station.isFavorite() != null && station.isFavorite();
141 final String weatherStationName = createWeatherStationName(station, isFavorite);
143 onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
144 nonNullList(station.getModules()).forEach(module -> {
145 onDeviceAddedInternal(module.getId(), station.getId(), module.getType(),
146 createWeatherModuleName(station, module, isFavorite), module.getFirmware());
150 private void discoverWelcomeHome(NAWelcomeHome home) {
151 // I observed that Thermostat homes are also reported here by Netatmo API
152 // So I ignore homes that have an empty list of cameras
153 List<NAWelcomeCamera> cameras = nonNullList(home.getCameras());
154 if (!cameras.isEmpty()) {
155 onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
157 cameras.forEach(camera -> {
158 onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
161 // Discover Known Persons
162 nonNullStream(home.getPersons()).filter(person -> person.getPseudo() != null).forEach(person -> {
163 onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(),
164 person.getPseudo(), null);
169 private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name,
170 @Nullable Integer firmwareVersion) {
171 ThingUID thingUID = findThingUID(type, id);
172 Map<String, Object> properties = new HashMap<>();
174 properties.put(EQUIPMENT_ID, id);
175 if (parentId != null) {
176 properties.put(PARENT_ID, parentId);
178 if (firmwareVersion != null) {
179 properties.put(Thing.PROPERTY_VENDOR, VENDOR);
180 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
181 properties.put(Thing.PROPERTY_MODEL_ID, type);
182 properties.put(Thing.PROPERTY_SERIAL_NUMBER, id);
184 addDiscoveredThing(thingUID, properties, name);
187 private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
188 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
189 .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel)
190 .withRepresentationProperty(EQUIPMENT_ID).build();
192 thingDiscovered(discoveryResult);
195 private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException {
196 for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) {
197 String uid = supportedThingTypeUID.getId();
199 if (uid.equalsIgnoreCase(thingType)) {
200 return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(),
201 thingId.replaceAll("[^a-zA-Z0-9_]", ""));
205 throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
208 private String createWeatherStationName(NAMain station, boolean isFavorite) {
209 StringBuilder nameBuilder = new StringBuilder();
210 nameBuilder.append(localizeType(station.getType()));
211 if (station.getStationName() != null) {
212 nameBuilder.append(' ');
213 nameBuilder.append(station.getStationName());
216 nameBuilder.append(" (favorite)");
218 return nameBuilder.toString();
221 private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) {
222 StringBuilder nameBuilder = new StringBuilder();
223 if (module.getModuleName() != null) {
224 nameBuilder.append(module.getModuleName());
226 nameBuilder.append(localizeType(module.getType()));
228 if (station.getStationName() != null) {
229 nameBuilder.append(' ');
230 nameBuilder.append(station.getStationName());
233 nameBuilder.append(" (favorite)");
235 return nameBuilder.toString();
238 private String localizeType(String typeName) {
239 Bundle bundle = FrameworkUtil.getBundle(this.getClass());
241 String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName,
242 localeProvider.getLocale());
243 if (localizedType != null) {
244 return localizedType;