]> git.basschouten.com Git - openhab-addons.git/blob
f533aeb9bdc17d20979bff42256411c564bad924
[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.netatmo.internal.handler;
14
15 import static org.openhab.core.library.unit.MetricPrefix.*;
16 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
17
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Optional;
21
22 import javax.measure.Unit;
23 import javax.measure.quantity.Angle;
24 import javax.measure.quantity.Dimensionless;
25 import javax.measure.quantity.Length;
26 import javax.measure.quantity.Pressure;
27 import javax.measure.quantity.Speed;
28 import javax.measure.quantity.Temperature;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.core.config.core.Configuration;
33 import org.openhab.core.i18n.TimeZoneProvider;
34 import org.openhab.core.library.unit.SIUnits;
35 import org.openhab.core.library.unit.SmartHomeUnits;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.ThingStatusInfo;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.BridgeHandler;
44 import org.openhab.core.thing.type.ChannelKind;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.openhab.core.types.State;
48 import org.openhab.core.types.UnDefType;
49 import org.openhab.binding.netatmo.internal.channelhelper.BatteryHelper;
50 import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * {@link AbstractNetatmoThingHandler} is the abstract class that handles
56  * common behaviors of all netatmo things
57  *
58  * @author GaĆ«l L'hopital - Initial contribution OH2 version
59  * @author Rob Nielsen - Added day, week, and month measurements to the weather station and modules
60  *
61  */
62 @NonNullByDefault
63 public abstract class AbstractNetatmoThingHandler extends BaseThingHandler {
64     // Units of measurement of the data delivered by the API
65     public static final Unit<Temperature> API_TEMPERATURE_UNIT = SIUnits.CELSIUS;
66     public static final Unit<Dimensionless> API_HUMIDITY_UNIT = SmartHomeUnits.PERCENT;
67     public static final Unit<Pressure> API_PRESSURE_UNIT = HECTO(SIUnits.PASCAL);
68     public static final Unit<Speed> API_WIND_SPEED_UNIT = SIUnits.KILOMETRE_PER_HOUR;
69     public static final Unit<Angle> API_WIND_DIRECTION_UNIT = SmartHomeUnits.DEGREE_ANGLE;
70     public static final Unit<Length> API_RAIN_UNIT = MILLI(SIUnits.METRE);
71     public static final Unit<Dimensionless> API_CO2_UNIT = SmartHomeUnits.PARTS_PER_MILLION;
72     public static final Unit<Dimensionless> API_NOISE_UNIT = SmartHomeUnits.DECIBEL;
73
74     private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
75
76     protected final TimeZoneProvider timeZoneProvider;
77     protected final MeasurableChannels measurableChannels = new MeasurableChannels();
78     private @Nullable RadioHelper radioHelper;
79     private @Nullable BatteryHelper batteryHelper;
80     protected @Nullable Configuration config;
81     private @Nullable NetatmoBridgeHandler bridgeHandler;
82
83     AbstractNetatmoThingHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
84         super(thing);
85         this.timeZoneProvider = timeZoneProvider;
86     }
87
88     @Override
89     public void initialize() {
90         logger.debug("initializing handler for thing {}", getThing().getUID());
91         Bridge bridge = getBridge();
92         initializeThing(bridge != null ? bridge.getStatus() : null);
93     }
94
95     @Override
96     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
97         logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
98         initializeThing(bridgeStatusInfo.getStatus());
99     }
100
101     private void initializeThing(@Nullable ThingStatus bridgeStatus) {
102         Bridge bridge = getBridge();
103         BridgeHandler bridgeHandler = bridge != null ? bridge.getHandler() : null;
104         if (bridgeHandler != null && bridgeStatus != null) {
105             if (bridgeStatus == ThingStatus.ONLINE) {
106                 config = getThing().getConfiguration();
107
108                 radioHelper = thing.getProperties().containsKey(PROPERTY_SIGNAL_LEVELS)
109                         ? new RadioHelper(thing.getProperties().get(PROPERTY_SIGNAL_LEVELS))
110                         : null;
111                 batteryHelper = thing.getProperties().containsKey(PROPERTY_BATTERY_LEVELS)
112                         ? new BatteryHelper(thing.getProperties().get(PROPERTY_BATTERY_LEVELS))
113                         : null;
114                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Pending parent object initialization");
115
116                 initializeThing();
117             } else {
118                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
119             }
120         } else {
121             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
122         }
123     }
124
125     protected abstract void initializeThing();
126
127     protected State getNAThingProperty(String channelId) {
128         Optional<State> result;
129
130         result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
131         if (result.isPresent()) {
132             return result.get();
133         }
134         result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
135         if (result.isPresent()) {
136             return result.get();
137         }
138         result = measurableChannels.getNAThingProperty(channelId);
139
140         return result.orElse(UnDefType.UNDEF);
141     }
142
143     protected void updateChannels() {
144         if (thing.getStatus() != ThingStatus.ONLINE) {
145             return;
146         }
147
148         updateDataChannels();
149
150         triggerEventChannels();
151     }
152
153     private void updateDataChannels() {
154         getThing().getChannels().stream().filter(channel -> !channel.getKind().equals(ChannelKind.TRIGGER))
155                 .forEach(channel -> {
156
157                     String channelId = channel.getUID().getId();
158                     if (isLinked(channelId)) {
159                         State state = getNAThingProperty(channelId);
160                         updateState(channel.getUID(), state);
161                     }
162                 });
163     }
164
165     /**
166      * Triggers all event/trigger channels
167      * (when a channel is triggered, a rule can get all other information from the updated non-trigger channels)
168      */
169     private void triggerEventChannels() {
170         getThing().getChannels().stream().filter(channel -> channel.getKind().equals(ChannelKind.TRIGGER))
171                 .forEach(channel -> triggerChannelIfRequired(channel.getUID().getId()));
172     }
173
174     /**
175      * Triggers the trigger channel with the given channel id when required (when an update is available)
176      *
177      * @param channelId channel id
178      */
179     protected void triggerChannelIfRequired(String channelId) {
180     }
181
182     @Override
183     public void channelLinked(ChannelUID channelUID) {
184         super.channelLinked(channelUID);
185         measurableChannels.addChannel(channelUID);
186     }
187
188     @Override
189     public void channelUnlinked(ChannelUID channelUID) {
190         super.channelUnlinked(channelUID);
191         measurableChannels.removeChannel(channelUID);
192     }
193
194     @Override
195     public void handleCommand(ChannelUID channelUID, Command command) {
196         if (command == RefreshType.REFRESH) {
197             logger.debug("Refreshing {}", channelUID);
198             updateChannels();
199         }
200     }
201
202     protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
203         if (bridgeHandler == null) {
204             Bridge bridge = getBridge();
205             if (bridge != null) {
206                 bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
207             }
208         }
209         NetatmoBridgeHandler handler = bridgeHandler;
210         return handler != null ? Optional.of(handler) : Optional.empty();
211     }
212
213     protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
214         return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
215     }
216
217     public boolean matchesId(@Nullable String searchedId) {
218         return searchedId != null && searchedId.equalsIgnoreCase(getId());
219     }
220
221     protected @Nullable String getId() {
222         Configuration conf = config;
223         Object equipmentId = conf != null ? conf.get(EQUIPMENT_ID) : null;
224         if (equipmentId instanceof String) {
225             return ((String) equipmentId).toLowerCase();
226         }
227         return null;
228     }
229
230     protected void updateProperties(@Nullable Integer firmware, @Nullable String modelId) {
231         Map<String, String> properties = editProperties();
232         if (firmware != null || modelId != null) {
233             properties.put(Thing.PROPERTY_VENDOR, VENDOR);
234         }
235         if (firmware != null) {
236             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
237         }
238         if (modelId != null) {
239             properties.put(Thing.PROPERTY_MODEL_ID, modelId);
240         }
241         updateProperties(properties);
242     }
243
244     protected Optional<RadioHelper> getRadioHelper() {
245         RadioHelper helper = radioHelper;
246         return helper != null ? Optional.of(helper) : Optional.empty();
247     }
248
249     protected Optional<BatteryHelper> getBatteryHelper() {
250         BatteryHelper helper = batteryHelper;
251         return helper != null ? Optional.of(helper) : Optional.empty();
252     }
253
254     public void updateMeasurements() {
255     }
256
257     public void getMeasurements(@Nullable String device, @Nullable String module, String scale, List<String> types,
258             List<String> channels, Map<String, Float> channelMeasurements) {
259         Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
260         if (!handler.isPresent() || device == null) {
261             return;
262         }
263
264         if (types.size() != channels.size()) {
265             throw new IllegalArgumentException("types and channels lists are different sizes.");
266         }
267
268         List<Float> measurements = handler.get().getStationMeasureResponses(device, module, scale, types);
269         if (measurements.size() != types.size()) {
270             throw new IllegalArgumentException("types and measurements lists are different sizes.");
271         }
272
273         int i = 0;
274         for (Float measurement : measurements) {
275             channelMeasurements.put(channels.get(i++), measurement);
276         }
277     }
278
279     public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
280         if (isLinked(channel)) {
281             channels.add(channel);
282             types.add(type);
283         }
284     }
285
286     protected boolean isReachable() {
287         return true;
288     }
289 }