]> git.basschouten.com Git - openhab-addons.git/blob
7454cb92ac54043c280e40de0317de56d690a2ed
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.netatmo.internal.discovery;
14
15 import static org.openhab.binding.netatmo.internal.APIUtils.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
17
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21
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;
36
37 import io.swagger.client.model.NAHealthyHomeCoach;
38 import io.swagger.client.model.NAMain;
39 import io.swagger.client.model.NAPlug;
40 import io.swagger.client.model.NAStationModule;
41 import io.swagger.client.model.NAWelcomeCamera;
42 import io.swagger.client.model.NAWelcomeHome;
43
44 /**
45  * The {@link NetatmoModuleDiscoveryService} searches for available Netatmo
46  * devices and modules connected to the API console
47  *
48  * @author GaĆ«l L'hopital - Initial contribution
49  * @author Ing. Peter Weiss - Welcome camera implementation
50  *
51  */
52 @NonNullByDefault
53 public class NetatmoModuleDiscoveryService extends AbstractDiscoveryService implements NetatmoDataListener {
54     private static final int SEARCH_TIME = 5;
55     private final NetatmoBridgeHandler netatmoBridgeHandler;
56
57     public NetatmoModuleDiscoveryService(NetatmoBridgeHandler netatmoBridgeHandler, LocaleProvider localeProvider,
58             TranslationProvider translationProvider) {
59         super(SUPPORTED_DEVICE_THING_TYPES_UIDS, SEARCH_TIME);
60         this.netatmoBridgeHandler = netatmoBridgeHandler;
61         this.localeProvider = localeProvider;
62         this.i18nProvider = translationProvider;
63     }
64
65     @Override
66     public void activate(@Nullable Map<String, Object> configProperties) {
67         super.activate(configProperties);
68         netatmoBridgeHandler.registerDataListener(this);
69     }
70
71     @Override
72     public void deactivate() {
73         netatmoBridgeHandler.unregisterDataListener(this);
74         super.deactivate();
75     }
76
77     @Override
78     public void startScan() {
79         if (netatmoBridgeHandler.configuration.readStation) {
80             netatmoBridgeHandler.getStationsDataBody(null).ifPresent(dataBody -> {
81                 nonNullList(dataBody.getDevices()).forEach(station -> {
82                     discoverWeatherStation(station);
83                 });
84             });
85         }
86         if (netatmoBridgeHandler.configuration.readHealthyHomeCoach) {
87             netatmoBridgeHandler.getHomecoachDataBody(null).ifPresent(dataBody -> {
88                 nonNullList(dataBody.getDevices()).forEach(homecoach -> {
89                     discoverHomeCoach(homecoach);
90                 });
91             });
92         }
93         if (netatmoBridgeHandler.configuration.readThermostat) {
94             netatmoBridgeHandler.getThermostatsDataBody(null).ifPresent(dataBody -> {
95                 nonNullList(dataBody.getDevices()).forEach(plug -> {
96                     discoverThermostat(plug);
97                 });
98             });
99         }
100         if (netatmoBridgeHandler.configuration.readWelcome || netatmoBridgeHandler.configuration.readPresence) {
101             netatmoBridgeHandler.getWelcomeDataBody(null).ifPresent(dataBody -> {
102                 nonNullList(dataBody.getHomes()).forEach(home -> {
103                     discoverWelcomeHome(home);
104                 });
105             });
106         }
107     }
108
109     @Override
110     protected synchronized void stopScan() {
111         super.stopScan();
112         removeOlderResults(getTimestampOfLastScan(), netatmoBridgeHandler.getThing().getUID());
113     }
114
115     @Override
116     public void onDataRefreshed(Object data) {
117         if (!isBackgroundDiscoveryEnabled()) {
118             return;
119         }
120         if (data instanceof NAMain) {
121             discoverWeatherStation((NAMain) data);
122         } else if (data instanceof NAPlug) {
123             discoverThermostat((NAPlug) data);
124         } else if (data instanceof NAHealthyHomeCoach) {
125             discoverHomeCoach((NAHealthyHomeCoach) data);
126         } else if (data instanceof NAWelcomeHome) {
127             discoverWelcomeHome((NAWelcomeHome) data);
128         }
129     }
130
131     private void discoverThermostat(NAPlug plug) {
132         onDeviceAddedInternal(plug.getId(), null, plug.getType(), plug.getStationName(), plug.getFirmware());
133         nonNullList(plug.getModules()).forEach(thermostat -> {
134             onDeviceAddedInternal(thermostat.getId(), plug.getId(), thermostat.getType(), thermostat.getModuleName(),
135                     thermostat.getFirmware());
136         });
137     }
138
139     private void discoverHomeCoach(NAHealthyHomeCoach homecoach) {
140         onDeviceAddedInternal(homecoach.getId(), null, homecoach.getType(), homecoach.getName(),
141                 homecoach.getFirmware());
142     }
143
144     private void discoverWeatherStation(NAMain station) {
145         final boolean isFavorite = station.isFavorite() != null && station.isFavorite();
146         final String weatherStationName = createWeatherStationName(station, isFavorite);
147
148         onDeviceAddedInternal(station.getId(), null, station.getType(), weatherStationName, station.getFirmware());
149         nonNullList(station.getModules()).forEach(module -> {
150             onDeviceAddedInternal(module.getId(), station.getId(), module.getType(),
151                     createWeatherModuleName(station, module, isFavorite), module.getFirmware());
152         });
153     }
154
155     private void discoverWelcomeHome(NAWelcomeHome home) {
156         // I observed that Thermostat homes are also reported here by Netatmo API
157         // So I ignore homes that have an empty list of cameras
158         List<NAWelcomeCamera> cameras = nonNullList(home.getCameras());
159         if (!cameras.isEmpty()) {
160             onDeviceAddedInternal(home.getId(), null, WELCOME_HOME_THING_TYPE.getId(), home.getName(), null);
161             // Discover Cameras
162             cameras.forEach(camera -> {
163                 onDeviceAddedInternal(camera.getId(), home.getId(), camera.getType(), camera.getName(), null);
164             });
165
166             // Discover Known Persons
167             nonNullStream(home.getPersons()).filter(person -> person.getPseudo() != null).forEach(person -> {
168                 onDeviceAddedInternal(person.getId(), home.getId(), WELCOME_PERSON_THING_TYPE.getId(),
169                         person.getPseudo(), null);
170             });
171         }
172     }
173
174     private void onDeviceAddedInternal(String id, @Nullable String parentId, String type, String name,
175             @Nullable Integer firmwareVersion) {
176         ThingUID thingUID = findThingUID(type, id);
177         Map<String, Object> properties = new HashMap<>();
178
179         properties.put(EQUIPMENT_ID, id);
180         if (parentId != null) {
181             properties.put(PARENT_ID, parentId);
182         }
183         if (firmwareVersion != null) {
184             properties.put(Thing.PROPERTY_VENDOR, VENDOR);
185             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion);
186             properties.put(Thing.PROPERTY_MODEL_ID, type);
187             properties.put(Thing.PROPERTY_SERIAL_NUMBER, id);
188         }
189         addDiscoveredThing(thingUID, properties, name);
190     }
191
192     private void addDiscoveredThing(ThingUID thingUID, Map<String, Object> properties, String displayLabel) {
193         DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
194                 .withBridge(netatmoBridgeHandler.getThing().getUID()).withLabel(displayLabel)
195                 .withRepresentationProperty(EQUIPMENT_ID).build();
196
197         thingDiscovered(discoveryResult);
198     }
199
200     private ThingUID findThingUID(String thingType, String thingId) throws IllegalArgumentException {
201         for (ThingTypeUID supportedThingTypeUID : getSupportedThingTypes()) {
202             String uid = supportedThingTypeUID.getId();
203
204             if (uid.equalsIgnoreCase(thingType)) {
205                 return new ThingUID(supportedThingTypeUID, netatmoBridgeHandler.getThing().getUID(),
206                         thingId.replaceAll("[^a-zA-Z0-9_]", ""));
207             }
208         }
209
210         throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
211     }
212
213     private String createWeatherStationName(NAMain station, boolean isFavorite) {
214         StringBuilder nameBuilder = new StringBuilder();
215         nameBuilder.append(localizeType(station.getType()));
216         if (station.getStationName() != null) {
217             nameBuilder.append(' ');
218             nameBuilder.append(station.getStationName());
219         }
220         if (isFavorite) {
221             nameBuilder.append(" (favorite)");
222         }
223         return nameBuilder.toString();
224     }
225
226     private String createWeatherModuleName(NAMain station, NAStationModule module, boolean isFavorite) {
227         StringBuilder nameBuilder = new StringBuilder();
228         if (module.getModuleName() != null) {
229             nameBuilder.append(module.getModuleName());
230         } else {
231             nameBuilder.append(localizeType(module.getType()));
232         }
233         if (station.getStationName() != null) {
234             nameBuilder.append(' ');
235             nameBuilder.append(station.getStationName());
236         }
237         if (isFavorite) {
238             nameBuilder.append(" (favorite)");
239         }
240         return nameBuilder.toString();
241     }
242
243     private String localizeType(String typeName) {
244         Bundle bundle = FrameworkUtil.getBundle(this.getClass());
245         @Nullable
246         String localizedType = i18nProvider.getText(bundle, "thing-type.netatmo." + typeName + ".label", typeName,
247                 localeProvider.getLocale());
248         if (localizedType != null) {
249             return localizedType;
250         }
251         return typeName;
252     }
253 }