]> git.basschouten.com Git - openhab-addons.git/blob
8d3210a57ed0cc314a06b4dfcde4311132fe3a26
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.HueBindingConstants.*;
16 import static org.openhab.binding.hue.internal.dto.FullSensor.*;
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.dto.FullSensor;
31 import org.openhab.binding.hue.internal.dto.SensorConfigUpdate;
32 import org.openhab.binding.hue.internal.dto.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     protected @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(PROPERTY_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     protected 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         if (sensor == null) {
176             logger.debug("Hue sensor not known on bridge. Cannot handle command.");
177             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
178                     "@text/offline.conf-error-wrong-sensor-id");
179             return;
180         }
181
182         StateUpdate sensorState = new StateUpdate();
183         switch (channel) {
184             case STATE_STATUS:
185                 sensorState = sensorState.setStatus(((DecimalType) command).intValue());
186                 break;
187             case STATE_FLAG:
188                 sensorState = sensorState.setFlag(OnOffType.ON.equals(command));
189                 break;
190         }
191
192         if (sensorState != null) {
193             bridgeHandler.updateSensorState(sensor, sensorState);
194         } else {
195             logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel);
196         }
197     }
198
199     @Override
200     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
201         SensorConfigUpdate configUpdate = doConfigurationUpdate(configurationParameters);
202         if (configurationParameters.containsKey(CONFIG_ON)) {
203             configUpdate.setOn(Boolean.TRUE.equals(configurationParameters.get(CONFIG_ON)));
204         }
205
206         if (!configUpdate.isEmpty()) {
207             HueClient hueBridge = getHueClient();
208             if (hueBridge == null) {
209                 logger.warn("Hue Bridge handler not found. Cannot handle configuration update without bridge.");
210                 return;
211             }
212
213             final FullSensor sensor = lastFullSensor;
214             if (sensor == null) {
215                 logger.debug("Hue sensor not known on bridge. Cannot handle configuration update.");
216                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
217                         "@text/offline.conf-error-wrong-sensor-id");
218                 return;
219             }
220
221             hueBridge.updateSensorConfig(sensor, configUpdate);
222         }
223
224         super.handleConfigurationUpdate(configurationParameters);
225     }
226
227     @Override
228     public boolean onSensorStateChanged(FullSensor sensor) {
229         logger.trace("onSensorStateChanged() was called");
230
231         final FullSensor lastSensor = lastFullSensor;
232         if (lastSensor == null || !Objects.equals(lastSensor.getState(), sensor.getState())) {
233             lastFullSensor = sensor;
234         } else {
235             return true;
236         }
237
238         logger.trace("New state for sensor {}", sensorId);
239
240         initializeProperties(sensor);
241
242         if (Boolean.TRUE.equals(sensor.getConfig().get(CONFIG_REACHABLE))) {
243             updateStatus(ThingStatus.ONLINE);
244         } else {
245             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
246         }
247
248         // update generic sensor config
249         final Configuration config = !configInitializedSuccessfully ? editConfiguration() : getConfig();
250         if (sensor.getConfig().containsKey(CONFIG_ON)) {
251             config.put(CONFIG_ON, sensor.getConfig().get(CONFIG_ON));
252         }
253
254         // update specific sensor config
255         doSensorStateChanged(sensor, config);
256
257         Object lastUpdated = sensor.getState().get(STATE_LAST_UPDATED);
258         if (lastUpdated != null) {
259             try {
260                 updateState(CHANNEL_LAST_UPDATED,
261                         new DateTimeType(ZonedDateTime.ofInstant(
262                                 LocalDateTime.parse(String.valueOf(lastUpdated), DateTimeFormatter.ISO_LOCAL_DATE_TIME),
263                                 ZoneOffset.UTC, ZoneId.systemDefault())));
264             } catch (DateTimeParseException e) {
265                 // do nothing
266             }
267         }
268
269         Object status = sensor.getState().get(STATE_STATUS);
270         if (status != null) {
271             try {
272                 DecimalType value = new DecimalType(String.valueOf(status));
273                 updateState(STATE_STATUS, value);
274             } catch (DateTimeParseException e) {
275                 // do nothing
276             }
277         }
278         Object flag = sensor.getState().get(STATE_FLAG);
279         if (flag != null) {
280             try {
281                 boolean value = Boolean.parseBoolean(String.valueOf(flag));
282                 updateState(CHANNEL_FLAG, value ? OnOffType.ON : OnOffType.OFF);
283             } catch (DateTimeParseException e) {
284                 // do nothing
285             }
286         }
287
288         Object battery = sensor.getConfig().get(CONFIG_BATTERY);
289         if (battery != null) {
290             DecimalType batteryLevel = DecimalType.valueOf(String.valueOf(battery));
291             updateState(CHANNEL_BATTERY_LEVEL, batteryLevel);
292             updateState(CHANNEL_BATTERY_LOW, batteryLevel.intValue() <= 10 ? OnOffType.ON : OnOffType.OFF);
293         }
294
295         if (!configInitializedSuccessfully) {
296             updateConfiguration(config);
297             configInitializedSuccessfully = true;
298         }
299
300         return true;
301     }
302
303     @Override
304     public void channelLinked(ChannelUID channelUID) {
305         final FullSensor sensor = lastFullSensor;
306         if (sensor != null) {
307             onSensorStateChanged(sensor);
308         }
309     }
310
311     /**
312      * Handles the sensors configuration change.
313      *
314      * @param configurationParameters
315      * @return
316      */
317     protected abstract SensorConfigUpdate doConfigurationUpdate(Map<String, Object> configurationParameters);
318
319     /**
320      * Handles the sensor change. Implementation should also update sensor-specific configuration that changed since the
321      * last update.
322      *
323      * @param sensor the sensor
324      * @param config the configuration in which to update the config states of the sensor
325      */
326     protected abstract void doSensorStateChanged(FullSensor sensor, Configuration config);
327
328     @Override
329     public void onSensorRemoved() {
330         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
331     }
332
333     @Override
334     public void onSensorGone() {
335         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/offline.conf-error-wrong-sensor-id");
336     }
337
338     @Override
339     public void onSensorAdded(FullSensor sensor) {
340         onSensorStateChanged(sensor);
341     }
342
343     @Override
344     public String getSensorId() {
345         return sensorId;
346     }
347 }