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.NetatmoBindingConstants.*;
17 import java.util.HashMap;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.core.config.discovery.AbstractDiscoveryService;
23 import org.openhab.core.config.discovery.DiscoveryResult;
24 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
25 import org.openhab.core.i18n.LocaleProvider;
26 import org.openhab.core.i18n.TranslationProvider;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingTypeUID;
29 import org.openhab.core.thing.ThingUID;
30 import org.openhab.binding.netatmo.internal.handler.NetatmoBridgeHandler;
31 import org.openhab.binding.netatmo.internal.handler.NetatmoDataListener;
32 import org.osgi.framework.Bundle;
33 import org.osgi.framework.FrameworkUtil;
35 import io.swagger.client.model.*;
38 * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo
39 * devices and modules connected to the API console
41 * @author Gaƫl L'hopital - Initial contribution
42 * @author Ing. Peter Weiss - Welcome camera implementation
46 public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener {
47 private static final int SEARCH_TIME = 5;
48 private final NetatmoBridgeHandler netatmoBridgeHandler;
50 public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider,
51 TranslationProvider translationProvider) {
52 super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME);
53 this.netatmoBridgeHandler = netatmoBridgeHandler;
54 this.localeProvider = localeProvider;
55 this.i18nProvider = translationProvider;
59 public void activate(@Nullable Map<String, @Nullable Object> configProperties) {
60 super.activate(configProperties);
61 netatmoBridgeHandler.registerDataListener(this);
65 public void deactivate() {
66 netatmoBridgeHandler.unregisterDataListener(this);
71 public void startScan() {
72 if (netatmoBridgeHandler.configuration.readStation) {
73 netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> {
74 dataBody.getDevices().forEach(station -> {
75 discoverWeatherStation(station);
79 if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
80 netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
81 dataBody.getDevices().forEach(homecoach -> {
82 discoverHomeCoach(homecoach);
86 if (netatmoBridgeHandler.configuration.readThermostat) {
87 netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
88 dataBody.getDevices().forEach(plug -> {
89 discoverThermostat(plug);
93 if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
94 netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
95 dataBody.getHomes().forEach(home -> {
96 discoverWelcomeHome(home);
103 protected synchronized void stopScan() {
105 removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID());
109 public void onDataRefreshed(Object data) {
110 if (!isBackgroundDiscoveryEnabled()) {
113 if (data instanceof NAMain) {
114 discoverWeatherStation((NAMain) data);
115 } else if (data instanceof NAPlug) {
116 discoverThermostat((NAPlug) data);
117 } else if (data instanceof NAHealthyHomeCoach) {
118 discoverHomeCoach((NAHealthyHomeCoach) data);
119 } else if (data instanceof NAWelcomeHome) {
120 discoverWelcomeHome((NAWelcomeHome) data);
124 private void discoverThermostat(NAPlug plug) {
125 onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware());
126 plug.getModules().forEach(thermostat -> {
127 onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(),
128 thermostat.getFirmware());
132 private void discoverHomeCoach(NAHealthyHomeCoach homecoach) {
133 onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(),
134 homecoach.getFirmware());
137 private void discoverWeatherStation(NAMain station) {
138 final boolean isFavorite = station.getFavorite() != null && station.getFavorite();
139 final String weatherStationName = createWeatherStationName(station, isFavorite);
141 onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
142 station.getModules().forEach(module -> {
143 onDeviceAddedInternal(module.getId(), station.getId(), module.getType(),
144 createWeatherModuleName(station, module, isFavorite), module.getFirmware());
148 private void discoverWelcomeHome(NAWelcomeHome home) {
149 // I observed that Thermostat homes are also reported here by Netatmo API
150 // So I ignore homes that have an empty list of cameras
151 if (!home.getCameras().isEmpty()) {
152 onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
154 home.getCameras().forEach(camera -> {
155 onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
158 // Discover Known Persons
159 home.getPersons().stream().filter(person -> person.getPseudo() != null).forEach(person -> {
160 onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(),
161 person.getPseudo(), null);
166 private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name,
167 @Nullable Integer firmwareVersion) {
168 ThingUID thingUID = findThingUID(type, id);
169 Map<String, Object> properties = new HashMap<>();
171 properties.put(EQUIPMENT_ID, id);
172 if (parentId != null) {
173 properties.put(PARENT_ID, parentId);
175 if (firmwareVersion != null) {
176 properties.put(Thing.PROPERTY_VENDOR, VENDOR);
177 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
178 properties.put(Thing.PROPERTY_MODEL_ID, type);
179 properties.put(Thing.PROPERTY_SERIAL_NUMBER, id);
181 addDiscoveredThing(thingUID, properties, name);
184 private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
185 DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
186 .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel)
187 .withRepresentationProperty(EQUIPMENT_ID).build();
189 thingDiscovered(discoveryResult);
192 private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException {
193 for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) {
194 String uid = supportedThingTypeUID.getId();
196 if (uid.equalsIgnoreCase(thingType)) {
197 return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(),
198 thingId.replaceAll("[^a-zA-Z0-9_]", ""));
202 throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
205 private String createWeatherStationName(NAMain station, boolean isFavorite) {
206 StringBuilder nameBuilder = new StringBuilder();
207 nameBuilder.append(localizeType(station.getType()));
208 if (station.getStationName() != null) {
209 nameBuilder.append(' ');
210 nameBuilder.append(station.getStationName());
213 nameBuilder.append(" (favorite)");
215 return nameBuilder.toString();
218 private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) {
219 StringBuilder nameBuilder = new StringBuilder();
220 if (module.getModuleName() != null) {
221 nameBuilder.append(module.getModuleName());
223 nameBuilder.append(localizeType(module.getType()));
225 if (station.getStationName() != null) {
226 nameBuilder.append(' ');
227 nameBuilder.append(station.getStationName());
230 nameBuilder.append(" (favorite)");
232 return nameBuilder.toString();
235 private String localizeType(String typeName) {
236 Bundle bundle = FrameworkUtil.getBundle(this.getClass());
238 String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName,
239 localeProvider.getLocale());
240 if (localizedType != null) {
241 return localizedType;