2 * Copyright (c) 2010-2023 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.deconz.internal.handler;
15 import static org.openhab.binding.deconz.internal.BindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.*;
17 import static org.openhab.core.library.unit.SIUnits.*;
18 import static org.openhab.core.library.unit.Units.*;
20 import java.util.List;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.deconz.internal.dto.SensorConfig;
26 import org.openhab.binding.deconz.internal.dto.SensorState;
27 import org.openhab.binding.deconz.internal.dto.SensorUpdateConfig;
28 import org.openhab.core.library.types.HSBType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.OpenClosedType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.type.ChannelKind;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
40 import com.google.gson.Gson;
43 * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
45 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
46 * and registers to the websocket connection as a listener.
48 * A REST API call is made to get the initial sensor state.
50 * Every sensor and switch is supported by this Thing, because a unified state is kept
51 * in {@link #sensorState}. Every field that got received by the REST API for this specific
52 * sensor is published to the framework.
54 * @author David Graeff - Initial contribution
57 public class SensorThingHandler extends SensorBaseThingHandler {
58 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR,
59 THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR, THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR,
60 THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
61 THING_TYPE_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
62 THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
63 THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_AIRQUALITY_SENSOR, THING_TYPE_COLOR_CONTROL);
65 private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
66 CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
68 public SensorThingHandler(Thing thing, Gson gson) {
73 public void handleCommand(ChannelUID channelUID, Command command) {
74 if (command instanceof RefreshType) {
75 sensorState.buttonevent = null;
76 valueUpdated(channelUID, sensorState, false);
79 switch (channelUID.getId()) {
81 if (command instanceof OnOffType) {
82 SensorUpdateConfig newConfig = new SensorUpdateConfig();
83 newConfig.on = OnOffType.ON.equals(command);
84 sendCommand(newConfig, command, channelUID, null);
91 protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
92 super.valueUpdated(channelUID, newConfig);
93 switch (channelUID.getId()) {
95 updateState(channelUID, OnOffType.from(newConfig.on));
97 case CHANNEL_TEMPERATURE:
98 Float temperature = newConfig.temperature;
99 if (temperature != null) {
100 updateState(channelUID, new QuantityType<>(temperature / 100, CELSIUS));
107 protected void valueUpdated(ChannelUID channelUID, SensorState newState, boolean initializing) {
108 super.valueUpdated(channelUID, newState, initializing);
109 switch (channelUID.getId()) {
110 case CHANNEL_BATTERY_LEVEL:
111 updateDecimalTypeChannel(channelUID, newState.battery);
114 Boolean dark = newState.dark;
116 Boolean daylight = newState.daylight;
117 if (dark) { // if it's dark, it's dark ;)
118 updateState(channelUID, new StringType("Dark"));
119 } else if (daylight != null) { // if its not dark, it might be between darkness and daylight
121 updateState(channelUID, new StringType("Daylight"));
123 updateState(channelUID, new StringType("Sunset"));
125 } else { // if no daylight value is known, we assume !dark means daylight
126 updateState(channelUID, new StringType("Daylight"));
131 updateQuantityTypeChannel(channelUID, newState.power, WATT);
133 case CHANNEL_CONSUMPTION:
134 updateQuantityTypeChannel(channelUID, newState.consumption, WATT_HOUR);
136 case CHANNEL_VOLTAGE:
137 updateQuantityTypeChannel(channelUID, newState.voltage, VOLT);
139 case CHANNEL_CURRENT:
140 updateQuantityTypeChannel(channelUID, newState.current, MILLI(AMPERE));
142 case CHANNEL_LIGHT_LUX:
143 updateQuantityTypeChannel(channelUID, newState.lux, LUX);
146 final double @Nullable [] xy = newState.xy;
147 if (xy != null && xy.length == 2) {
148 updateState(channelUID, HSBType.fromXY((float) xy[0], (float) xy[1]));
151 case CHANNEL_LIGHT_LEVEL:
152 updateDecimalTypeChannel(channelUID, newState.lightlevel);
155 updateSwitchChannel(channelUID, newState.dark);
157 case CHANNEL_DAYLIGHT:
158 updateSwitchChannel(channelUID, newState.daylight);
160 case CHANNEL_TEMPERATURE:
161 updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
163 case CHANNEL_HUMIDITY:
164 updateQuantityTypeChannel(channelUID, newState.humidity, PERCENT, 1.0 / 100);
166 case CHANNEL_PRESSURE:
167 updateQuantityTypeChannel(channelUID, newState.pressure, HECTO(PASCAL));
169 case CHANNEL_PRESENCE:
170 updateSwitchChannel(channelUID, newState.presence);
173 updateDecimalTypeChannel(channelUID, newState.status);
175 case CHANNEL_OPENCLOSE:
176 Boolean open = newState.open;
178 updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
181 case CHANNEL_WATERLEAKAGE:
182 updateSwitchChannel(channelUID, newState.water);
185 updateSwitchChannel(channelUID, newState.fire);
188 updateSwitchChannel(channelUID, newState.alarm);
190 case CHANNEL_TAMPERED:
191 updateSwitchChannel(channelUID, newState.tampered);
193 case CHANNEL_VIBRATION:
194 updateSwitchChannel(channelUID, newState.vibration);
196 case CHANNEL_CARBONMONOXIDE:
197 updateSwitchChannel(channelUID, newState.carbonmonoxide);
199 case CHANNEL_AIRQUALITY:
200 updateStringChannel(channelUID, newState.airquality);
202 case CHANNEL_AIRQUALITYPPB:
203 updateDecimalTypeChannel(channelUID, newState.airqualityppb);
206 updateDecimalTypeChannel(channelUID, newState.buttonevent);
208 case CHANNEL_BUTTONEVENT:
209 Integer buttonevent = newState.buttonevent;
210 if (buttonevent != null && !initializing) {
211 triggerChannel(channelUID, String.valueOf(buttonevent));
214 case CHANNEL_GESTURE:
215 updateDecimalTypeChannel(channelUID, newState.gesture);
217 case CHANNEL_GESTUREEVENT:
218 Integer gesture = newState.gesture;
219 if (gesture != null && !initializing) {
220 triggerChannel(channelUID, String.valueOf(gesture));
227 protected void createTypeSpecificChannels(SensorConfig sensorConfig, SensorState sensorState) {
228 // some Xiaomi sensors
229 if (sensorConfig.temperature != null) {
230 createChannel(CHANNEL_TEMPERATURE, ChannelKind.STATE);
233 // ZHAPresence - e.g. IKEA TRÅDFRI motion sensor
234 if (sensorState.dark != null) {
235 createChannel(CHANNEL_DARK, ChannelKind.STATE);
238 // ZHAConsumption - e.g Bitron 902010/25 or Heiman SmartPlug
239 if (sensorState.power != null) {
240 createChannel(CHANNEL_POWER, ChannelKind.STATE);
243 // ZHAPower - e.g. Heiman SmartPlug
244 if (sensorState.voltage != null) {
245 createChannel(CHANNEL_VOLTAGE, ChannelKind.STATE);
247 if (sensorState.current != null) {
248 createChannel(CHANNEL_CURRENT, ChannelKind.STATE);
251 // IAS Zone sensor - e.g. Heiman HS1MS motion sensor
252 if (sensorState.tampered != null) {
253 createChannel(CHANNEL_TAMPERED, ChannelKind.STATE);
257 if (sensorState.gesture != null) {
258 createChannel(CHANNEL_GESTURE, ChannelKind.STATE);
259 createChannel(CHANNEL_GESTUREEVENT, ChannelKind.TRIGGER);
264 protected List<String> getConfigChannels() {
265 return CONFIG_CHANNELS;