]> git.basschouten.com Git - openhab-addons.git/blob
53e26aa058d37a8c419cf6d9a1afc1bf1b3b5cd0
[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.binding.netatmo.internal.NetatmoBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
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.binding.netatmo.internal.channelhelper.BatteryHelper;
33 import org.openhab.binding.netatmo.internal.channelhelper.RadioHelper;
34 import org.openhab.core.config.core.Configuration;
35 import org.openhab.core.i18n.TimeZoneProvider;
36 import org.openhab.core.library.unit.SIUnits;
37 import org.openhab.core.library.unit.SmartHomeUnits;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.thing.binding.BridgeHandler;
46 import org.openhab.core.thing.type.ChannelKind;
47 import org.openhab.core.types.Command;
48 import org.openhab.core.types.RefreshType;
49 import org.openhab.core.types.State;
50 import org.openhab.core.types.UnDefType;
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                 String signalLevel = thing.getProperties().get(PROPERTY_SIGNAL_LEVELS);
109                 radioHelper = signalLevel != null ? new RadioHelper(signalLevel) : null;
110                 String batteryLevel = thing.getProperties().get(PROPERTY_BATTERY_LEVELS);
111                 batteryHelper = batteryLevel != null ? new BatteryHelper(batteryLevel) : null;
112                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Pending parent object initialization");
113
114                 initializeThing();
115             } else {
116                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
117             }
118         } else {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
120         }
121     }
122
123     protected abstract void initializeThing();
124
125     protected State getNAThingProperty(String channelId) {
126         Optional<State> result;
127
128         result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
129         if (result.isPresent()) {
130             return result.get();
131         }
132         result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
133         if (result.isPresent()) {
134             return result.get();
135         }
136         result = measurableChannels.getNAThingProperty(channelId);
137
138         return result.orElse(UnDefType.UNDEF);
139     }
140
141     protected void updateChannels() {
142         if (thing.getStatus() != ThingStatus.ONLINE) {
143             return;
144         }
145
146         updateDataChannels();
147
148         triggerEventChannels();
149     }
150
151     private void updateDataChannels() {
152         getThing().getChannels().stream().filter(channel -> !channel.getKind().equals(ChannelKind.TRIGGER))
153                 .forEach(channel -> {
154
155                     String channelId = channel.getUID().getId();
156                     if (isLinked(channelId)) {
157                         State state = getNAThingProperty(channelId);
158                         updateState(channel.getUID(), state);
159                     }
160                 });
161     }
162
163     /**
164      * Triggers all event/trigger channels
165      * (when a channel is triggered, a rule can get all other information from the updated non-trigger channels)
166      */
167     private void triggerEventChannels() {
168         getThing().getChannels().stream().filter(channel -> channel.getKind().equals(ChannelKind.TRIGGER))
169                 .forEach(channel -> triggerChannelIfRequired(channel.getUID().getId()));
170     }
171
172     /**
173      * Triggers the trigger channel with the given channel id when required (when an update is available)
174      *
175      * @param channelId channel id
176      */
177     protected void triggerChannelIfRequired(String channelId) {
178     }
179
180     @Override
181     public void channelLinked(ChannelUID channelUID) {
182         super.channelLinked(channelUID);
183         measurableChannels.addChannel(channelUID);
184     }
185
186     @Override
187     public void channelUnlinked(ChannelUID channelUID) {
188         super.channelUnlinked(channelUID);
189         measurableChannels.removeChannel(channelUID);
190     }
191
192     @Override
193     public void handleCommand(ChannelUID channelUID, Command command) {
194         if (command == RefreshType.REFRESH) {
195             logger.debug("Refreshing {}", channelUID);
196             updateChannels();
197         }
198     }
199
200     protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
201         if (bridgeHandler == null) {
202             Bridge bridge = getBridge();
203             if (bridge != null) {
204                 bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
205             }
206         }
207         NetatmoBridgeHandler handler = bridgeHandler;
208         return handler != null ? Optional.of(handler) : Optional.empty();
209     }
210
211     protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
212         return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
213     }
214
215     public boolean matchesId(@Nullable String searchedId) {
216         return searchedId != null && searchedId.equalsIgnoreCase(getId());
217     }
218
219     protected @Nullable String getId() {
220         Configuration conf = config;
221         Object equipmentId = conf != null ? conf.get(EQUIPMENT_ID) : null;
222         if (equipmentId instanceof String) {
223             return ((String) equipmentId).toLowerCase();
224         }
225         return null;
226     }
227
228     protected void updateProperties(@Nullable Integer firmware, @Nullable String modelId) {
229         Map<String, String> properties = editProperties();
230         if (firmware != null || modelId != null) {
231             properties.put(Thing.PROPERTY_VENDOR, VENDOR);
232         }
233         if (firmware != null) {
234             properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
235         }
236         if (modelId != null) {
237             properties.put(Thing.PROPERTY_MODEL_ID, modelId);
238         }
239         updateProperties(properties);
240     }
241
242     protected Optional<RadioHelper> getRadioHelper() {
243         RadioHelper helper = radioHelper;
244         return helper != null ? Optional.of(helper) : Optional.empty();
245     }
246
247     protected Optional<BatteryHelper> getBatteryHelper() {
248         BatteryHelper helper = batteryHelper;
249         return helper != null ? Optional.of(helper) : Optional.empty();
250     }
251
252     public void updateMeasurements() {
253     }
254
255     public void getMeasurements(@Nullable String device, @Nullable String module, String scale, List<String> types,
256             List<String> channels, Map<String, Float> channelMeasurements) {
257         Optional<NetatmoBridgeHandler> handler = getBridgeHandler();
258         if (!handler.isPresent() || device == null) {
259             return;
260         }
261
262         if (types.size() != channels.size()) {
263             throw new IllegalArgumentException("types and channels lists are different sizes.");
264         }
265
266         List<Float> measurements = handler.get().getStationMeasureResponses(device, module, scale, types);
267         if (measurements.size() != types.size()) {
268             throw new IllegalArgumentException("types and measurements lists are different sizes.");
269         }
270
271         int i = 0;
272         for (Float measurement : measurements) {
273             channelMeasurements.put(channels.get(i++), measurement);
274         }
275     }
276
277     public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
278         if (isLinked(channel)) {
279             channels.add(channel);
280             types.add(type);
281         }
282     }
283
284     protected boolean isReachable() {
285         return true;
286     }
287 }