]> git.basschouten.com Git - openhab-addons.git/blob
9c0d1aa198bb460324d708a72fc39ab53f2594a0
[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.hue.internal.handler;
14
15 import static org.openhab.binding.hue.internal.FullSensor.*;
16 import static org.openhab.binding.hue.internal.HueBindingConstants.*;
17 import static org.openhab.core.thing.Thing.*;
18
19 import java.time.LocalDateTime;
20 import java.time.ZoneId;
21 import java.time.ZoneOffset;
22 import java.time.ZonedDateTime;
23 import java.time.format.DateTimeFormatter;
24 import java.time.format.DateTimeParseException;
25 import java.util.Map;
26 import java.util.Objects;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.hue.internal.FullSensor;
31 import org.openhab.binding.hue.internal.SensorConfigUpdate;
32 import org.openhab.binding.hue.internal.StateUpdate;
33 import org.openhab.core.config.core.Configuration;
34 import org.openhab.core.library.types.DateTimeType;
35 import org.openhab.core.library.types.DecimalType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.ThingStatusInfo;
43 import org.openhab.core.thing.binding.BaseThingHandler;
44 import org.openhab.core.thing.binding.ThingHandler;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Abstract Sensor Handler
51  *
52  * @author Samuel Leisering - Initial contribution
53  * @author Christoph Weitkamp - Initial contribution
54  */
55 @NonNullByDefault
56 public abstract class HueSensorHandler extends BaseThingHandler implements SensorStatusListener {
57
58     private final Logger logger = LoggerFactory.getLogger(HueSensorHandler.class);
59
60     private @NonNullByDefault({}) String sensorId;
61
62     private boolean configInitializedSuccessfully;
63     private boolean propertiesInitializedSuccessfully;
64
65     private @Nullable HueClient hueClient;
66
67     private @Nullable FullSensor lastFullSensor;
68
69     public HueSensorHandler(Thing thing) {
70         super(thing);
71     }
72
73     @Override
74     public void initialize() {
75         logger.debug("Initializing hue sensor handler.");
76         Bridge bridge = getBridge();
77         initializeThing((bridge == null) ? null : bridge.getStatus());
78     }
79
80     @Override
81     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
82         logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
83         initializeThing(bridgeStatusInfo.getStatus());
84     }
85
86     private void initializeThing(@Nullable ThingStatus bridgeStatus) {
87         logger.debug("initializeThing thing {} bridge status {}", getThing().getUID(), bridgeStatus);
88         final String configSensorId = (String) getConfig().get(SENSOR_ID);
89         if (configSensorId != null) {
90             sensorId = configSensorId;
91             // note: this call implicitly registers our handler as a listener on the bridge
92             HueClient bridgeHandler = getHueClient();
93             if (bridgeHandler != null) {
94                 if (bridgeStatus == ThingStatus.ONLINE) {
95                     initializeProperties(bridgeHandler.getSensorById(sensorId));
96                     updateStatus(ThingStatus.ONLINE);
97                 } else {
98                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
99                 }
100             } else {
101                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
102             }
103         } else {
104             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
105                     "@text/offline.conf-error-no-sensor-id");
106         }
107     }
108
109     private synchronized void initializeProperties(@Nullable FullSensor fullSensor) {
110         if (!propertiesInitializedSuccessfully && fullSensor != null) {
111             Map<String, String> properties = editProperties();
112             String softwareVersion = fullSensor.getSoftwareVersion();
113             if (softwareVersion != null) {
114                 properties.put(PROPERTY_FIRMWARE_VERSION, softwareVersion);
115             }
116             String modelId = fullSensor.getNormalizedModelID();
117             if (modelId != null) {
118                 properties.put(PROPERTY_MODEL_ID, modelId);
119             }
120             properties.put(PROPERTY_VENDOR, fullSensor.getManufacturerName());
121             properties.put(PRODUCT_NAME, fullSensor.getProductName());
122             String uniqueID = fullSensor.getUniqueID();
123             if (uniqueID != null) {
124                 properties.put(UNIQUE_ID, uniqueID);
125             }
126             updateProperties(properties);
127             propertiesInitializedSuccessfully = true;
128         }
129     }
130
131     @Override
132     public void dispose() {
133         logger.debug("Hue sensor handler disposes. Unregistering listener.");
134         if (sensorId != null) {
135             HueClient bridgeHandler = getHueClient();
136             if (bridgeHandler != null) {
137                 bridgeHandler.unregisterSensorStatusListener(this);
138                 hueClient = null;
139             }
140             sensorId = null;
141         }
142     }
143
144     protected synchronized @Nullable HueClient getHueClient() {
145         if (hueClient == null) {
146             Bridge bridge = getBridge();
147             if (bridge == null) {
148                 return null;
149             }
150             ThingHandler handler = bridge.getHandler();
151             if (handler instanceof HueBridgeHandler) {
152                 HueClient bridgeHandler = (HueClient) handler;
153                 hueClient = bridgeHandler;
154                 bridgeHandler.registerSensorStatusListener(this);
155             } else {
156                 return null;
157             }
158         }
159         return hueClient;
160     }
161
162     @Override
163     public void handleCommand(ChannelUID channelUID, Command command) {
164         handleCommand(channelUID.getId(), command);
165     }
166
167     public void handleCommand(String channel, Command command) {
168         HueClient bridgeHandler = getHueClient();
169         if (bridgeHandler == null) {
170             logger.warn("hue bridge handler not found. Cannot handle command without bridge.");
171             return;
172         }
173
174         final FullSensor sensor = lastFullSensor;
175
176         if (sensor == null) {
177             logger.debug("hue sensor not known on bridge. Cannot handle command.");
178             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
179                     "@text/offline.conf-error-wrong-sensor-id");
180             return;
181         }
182
183         StateUpdate sensorState = new StateUpdate();
184         switch (channel) {
185             case STATE_STATUS:
186                 sensorState = sensorState.setStatus(((DecimalType) command).intValue());
187                 break;
188             case STATE_FLAG:
189                 sensorState = sensorState.setFlag(OnOffType.ON.equals(command));
190                 break;
191         }
192
193         if (sensorState != null) {
194             bridgeHandler.updateSensorState(sensor, sensorState);
195         } else {
196             logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel);
197         }
198     }
199
200     @Override
201     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
202         SensorConfigUpdate configUpdate = doConfigurationUpdate(configurationParameters);
203         if (configurationParameters.containsKey(CONFIG_ON)) {
204             configUpdate.setOn(Boolean.TRUE.equals(configurationParameters.get(CONFIG_ON)));
205         }
206
207         if (!configUpdate.isEmpty()) {
208             HueClient hueBridge = getHueClient();
209             if (hueBridge == null) {
210                 logger.warn("hue bridge handler not found. Cannot handle configuration update without bridge.");
211                 return;
212             }
213
214             final FullSensor sensor = lastFullSensor;
215             if (sensor == null) {
216                 logger.debug("hue sensor not known on bridge. Cannot handle command.");
217                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
218                         "@text/offline.conf-error-wrong-sensor-id");
219                 return;
220             }
221
222             hueBridge.updateSensorConfig(sensor, configUpdate);
223         }
224
225         super.handleConfigurationUpdate(configurationParameters);
226     }
227
228     @Override
229     public boolean onSensorStateChanged(FullSensor sensor) {
230         logger.trace("onSensorStateChanged() was called");
231
232         final FullSensor lastSensor = lastFullSensor;
233         if (lastSensor == null || !Objects.equals(lastSensor.getState(), sensor.getState())) {
234             lastFullSensor = sensor;
235         } else {
236             return true;
237         }
238
239         logger.trace("New state for sensor {}", sensorId);
240
241         initializeProperties(sensor);
242
243         if (Boolean.TRUE.equals(sensor.getConfig().get(CONFIG_REACHABLE))) {
244             updateStatus(ThingStatus.ONLINE);
245         } else {
246             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
247         }
248
249         // update generic sensor config
250         final Configuration config = !configInitializedSuccessfully ? editConfiguration() : getConfig();
251         if (sensor.getConfig().containsKey(CONFIG_ON)) {
252             config.put(CONFIG_ON, sensor.getConfig().get(CONFIG_ON));
253         }
254
255         // update specific sensor config
256         doSensorStateChanged(sensor, config);
257
258         Object lastUpdated = sensor.getState().get(STATE_LAST_UPDATED);
259         if (lastUpdated != null) {
260             try {
261                 updateState(CHANNEL_LAST_UPDATED,
262                         new DateTimeType(ZonedDateTime.ofInstant(
263                                 LocalDateTime.parse(String.valueOf(lastUpdated), DateTimeFormatter.ISO_LOCAL_DATE_TIME),
264                                 ZoneOffset.UTC, ZoneId.systemDefault())));
265             } catch (DateTimeParseException e) {
266                 // do nothing
267             }
268         }
269
270         Object status = sensor.getState().get(STATE_STATUS);
271         if (status != null) {
272             try {
273                 DecimalType value = new DecimalType(String.valueOf(status));
274                 updateState(STATE_STATUS, value);
275             } catch (DateTimeParseException e) {
276                 // do nothing
277             }
278         }
279         Object flag = sensor.getState().get(STATE_FLAG);
280         if (flag != null) {
281             try {
282                 boolean value = Boolean.parseBoolean(String.valueOf(flag));
283                 updateState(CHANNEL_FLAG, value ? OnOffType.ON : OnOffType.OFF);
284             } catch (DateTimeParseException e) {
285                 // do nothing
286             }
287         }
288
289         Object battery = sensor.getConfig().get(CONFIG_BATTERY);
290         if (battery != null) {
291             DecimalType batteryLevel = DecimalType.valueOf(String.valueOf(battery));
292             updateState(CHANNEL_BATTERY_LEVEL, batteryLevel);
293             updateState(CHANNEL_BATTERY_LOW, batteryLevel.intValue() <= 10 ? OnOffType.ON : OnOffType.OFF);
294         }
295
296         if (!configInitializedSuccessfully) {
297             updateConfiguration(config);
298             configInitializedSuccessfully = true;
299         }
300
301         return true;
302     }
303
304     @Override
305     public void channelLinked(ChannelUID channelUID) {
306         final FullSensor sensor = lastFullSensor;
307         if (sensor != null) {
308             onSensorStateChanged(sensor);
309         }
310     }
311
312     /**
313      * Handles the sensors configuration change.
314      *
315      * @param configurationParameters
316      * @return
317      */
318     protected abstract SensorConfigUpdate doConfigurationUpdate(Map<String, Object> configurationParameters);
319
320     /**
321      * Handles the sensor change. Implementation should also update sensor-specific configuration that changed since the
322      * last update.
323      *
324      * @param bridge the bridge
325      * @param sensor the sensor
326      * @param config the configuration in which to update the config states of the sensor
327      */
328     protected abstract void doSensorStateChanged(FullSensor sensor, Configuration config);
329
330     @Override
331     public void onSensorRemoved() {
332         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
333     }
334
335     @Override
336     public void onSensorGone() {
337         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/offline.conf-error-wrong-sensor-id");
338     }
339
340     @Override
341     public void onSensorAdded(FullSensor sensor) {
342         onSensorStateChanged(sensor);
343     }
344
345     @Override
346     public String getSensorId() {
347         return sensorId;
348     }
349 }