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.handler;
15 import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
18 import java.util.List;
20 import java.util.Optional;
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;
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;
55 * {@link AbstractNetatmoThingHandler} is the abstract class that handles
56 * common behaviors of all netatmo things
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
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;
74 private final Logger logger = LoggerFactory.getLogger(AbstractNetatmoThingHandler.class);
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;
83 AbstractNetatmoThingHandler(Thing thing, final TimeZoneProvider timeZoneProvider) {
85 this.timeZoneProvider = timeZoneProvider;
89 public void initialize() {
90 logger.debug("initializing handler for thing {}", getThing().getUID());
91 Bridge bridge = getBridge();
92 initializeThing(bridge != null ? bridge.getStatus() : null);
96 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
97 logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
98 initializeThing(bridgeStatusInfo.getStatus());
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();
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");
116 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
123 protected abstract void initializeThing();
125 protected State getNAThingProperty(String channelId) {
126 Optional<State> result;
128 result = getBatteryHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
129 if (result.isPresent()) {
132 result = getRadioHelper().flatMap(helper -> helper.getNAThingProperty(channelId));
133 if (result.isPresent()) {
136 result = measurableChannels.getNAThingProperty(channelId);
138 return result.orElse(UnDefType.UNDEF);
141 protected void updateChannels() {
142 if (thing.getStatus() != ThingStatus.ONLINE) {
146 updateDataChannels();
148 triggerEventChannels();
151 private void updateDataChannels() {
152 getThing().getChannels().stream().filter(channel -> !channel.getKind().equals(ChannelKind.TRIGGER))
153 .forEach(channel -> {
155 String channelId = channel.getUID().getId();
156 if (isLinked(channelId)) {
157 State state = getNAThingProperty(channelId);
158 updateState(channel.getUID(), state);
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)
167 private void triggerEventChannels() {
168 getThing().getChannels().stream().filter(channel -> channel.getKind().equals(ChannelKind.TRIGGER))
169 .forEach(channel -> triggerChannelIfRequired(channel.getUID().getId()));
173 * Triggers the trigger channel with the given channel id when required (when an update is available)
175 * @param channelId channel id
177 protected void triggerChannelIfRequired(String channelId) {
181 public void channelLinked(ChannelUID channelUID) {
182 super.channelLinked(channelUID);
183 measurableChannels.addChannel(channelUID);
187 public void channelUnlinked(ChannelUID channelUID) {
188 super.channelUnlinked(channelUID);
189 measurableChannels.removeChannel(channelUID);
193 public void handleCommand(ChannelUID channelUID, Command command) {
194 if (command == RefreshType.REFRESH) {
195 logger.debug("Refreshing {}", channelUID);
200 protected Optional<NetatmoBridgeHandler> getBridgeHandler() {
201 if (bridgeHandler == null) {
202 Bridge bridge = getBridge();
203 if (bridge != null) {
204 bridgeHandler = (NetatmoBridgeHandler) bridge.getHandler();
207 NetatmoBridgeHandler handler = bridgeHandler;
208 return handler != null ? Optional.of(handler) : Optional.empty();
211 protected Optional<AbstractNetatmoThingHandler> findNAThing(@Nullable String searchedId) {
212 return getBridgeHandler().flatMap(handler -> handler.findNAThing(searchedId));
215 public boolean matchesId(@Nullable String searchedId) {
216 return searchedId != null && searchedId.equalsIgnoreCase(getId());
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();
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);
233 if (firmware != null) {
234 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
236 if (modelId != null) {
237 properties.put(Thing.PROPERTY_MODEL_ID, modelId);
239 updateProperties(properties);
242 protected Optional<RadioHelper> getRadioHelper() {
243 RadioHelper helper = radioHelper;
244 return helper != null ? Optional.of(helper) : Optional.empty();
247 protected Optional<BatteryHelper> getBatteryHelper() {
248 BatteryHelper helper = batteryHelper;
249 return helper != null ? Optional.of(helper) : Optional.empty();
252 public void updateMeasurements() {
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) {
262 if (types.size() != channels.size()) {
263 throw new IllegalArgumentException("types and channels lists are different sizes.");
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.");
272 for (Float measurement : measurements) {
273 channelMeasurements.put(channels.get(i++), measurement);
277 public void addMeasurement(List<String> channels, List<String> types, String channel, String type) {
278 if (isLinked(channel)) {
279 channels.add(channel);
284 protected boolean isReachable() {