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.hue.internal.handler;
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.*;
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;
26 import java.util.Objects;
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;
50 * Abstract Sensor Handler
52 * @author Samuel Leisering - Initial contribution
53 * @author Christoph Weitkamp - Initial contribution
56 public abstract class HueSensorHandler extends BaseThingHandler implements SensorStatusListener {
58 private final Logger logger = LoggerFactory.getLogger(HueSensorHandler.class);
60 private @NonNullByDefault({}) String sensorId;
62 private boolean configInitializedSuccessfully;
63 private boolean propertiesInitializedSuccessfully;
65 private @Nullable HueClient hueClient;
67 private @Nullable FullSensor lastFullSensor;
69 public HueSensorHandler(Thing thing) {
74 public void initialize() {
75 logger.debug("Initializing hue sensor handler.");
76 Bridge bridge = getBridge();
77 initializeThing((bridge == null) ? null : bridge.getStatus());
81 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
82 logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
83 initializeThing(bridgeStatusInfo.getStatus());
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);
98 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
104 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
105 "@text/offline.conf-error-no-sensor-id");
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);
116 String modelId = fullSensor.getNormalizedModelID();
117 if (modelId != null) {
118 properties.put(PROPERTY_MODEL_ID, modelId);
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);
126 updateProperties(properties);
127 propertiesInitializedSuccessfully = true;
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);
144 protected synchronized @Nullable HueClient getHueClient() {
145 if (hueClient == null) {
146 Bridge bridge = getBridge();
147 if (bridge == null) {
150 ThingHandler handler = bridge.getHandler();
151 if (handler instanceof HueBridgeHandler) {
152 HueClient bridgeHandler = (HueClient) handler;
153 hueClient = bridgeHandler;
154 bridgeHandler.registerSensorStatusListener(this);
163 public void handleCommand(ChannelUID channelUID, Command command) {
164 handleCommand(channelUID.getId(), command);
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.");
174 final FullSensor sensor = lastFullSensor;
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");
183 StateUpdate sensorState = new StateUpdate();
186 sensorState = sensorState.setStatus(((DecimalType) command).intValue());
189 sensorState = sensorState.setFlag(OnOffType.ON.equals(command));
193 if (sensorState != null) {
194 bridgeHandler.updateSensorState(sensor, sensorState);
196 logger.warn("Command sent to an unknown channel id: {}:{}", getThing().getUID(), channel);
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)));
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.");
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");
222 hueBridge.updateSensorConfig(sensor, configUpdate);
225 super.handleConfigurationUpdate(configurationParameters);
229 public boolean onSensorStateChanged(FullSensor sensor) {
230 logger.trace("onSensorStateChanged() was called");
232 final FullSensor lastSensor = lastFullSensor;
233 if (lastSensor == null || !Objects.equals(lastSensor.getState(), sensor.getState())) {
234 lastFullSensor = sensor;
239 logger.trace("New state for sensor {}", sensorId);
241 initializeProperties(sensor);
243 if (Boolean.TRUE.equals(sensor.getConfig().get(CONFIG_REACHABLE))) {
244 updateStatus(ThingStatus.ONLINE);
246 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
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));
255 // update specific sensor config
256 doSensorStateChanged(sensor, config);
258 Object lastUpdated = sensor.getState().get(STATE_LAST_UPDATED);
259 if (lastUpdated != null) {
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) {
270 Object status = sensor.getState().get(STATE_STATUS);
271 if (status != null) {
273 DecimalType value = new DecimalType(String.valueOf(status));
274 updateState(STATE_STATUS, value);
275 } catch (DateTimeParseException e) {
279 Object flag = sensor.getState().get(STATE_FLAG);
282 boolean value = Boolean.parseBoolean(String.valueOf(flag));
283 updateState(CHANNEL_FLAG, value ? OnOffType.ON : OnOffType.OFF);
284 } catch (DateTimeParseException e) {
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);
296 if (!configInitializedSuccessfully) {
297 updateConfiguration(config);
298 configInitializedSuccessfully = true;
305 public void channelLinked(ChannelUID channelUID) {
306 final FullSensor sensor = lastFullSensor;
307 if (sensor != null) {
308 onSensorStateChanged(sensor);
313 * Handles the sensors configuration change.
315 * @param configurationParameters
318 protected abstract SensorConfigUpdate doConfigurationUpdate(Map<String, Object> configurationParameters);
321 * Handles the sensor change. Implementation should also update sensor-specific configuration that changed since the
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
328 protected abstract void doSensorStateChanged(FullSensor sensor, Configuration config);
331 public void onSensorRemoved() {
332 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.sensor-not-reachable");
336 public void onSensorGone() {
337 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/offline.conf-error-wrong-sensor-id");
341 public void onSensorAdded(FullSensor sensor) {
342 onSensorStateChanged(sensor);
346 public String getSensorId() {