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.OnOffType;
29 import org.openhab.core.library.types.OpenClosedType;
30 import org.openhab.core.library.types.QuantityType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.binding.builder.ThingBuilder;
36 import org.openhab.core.thing.type.ChannelKind;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.util.ColorUtil;
41 import com.google.gson.Gson;
44 * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
46 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
47 * and registers to the websocket connection as a listener.
49 * A REST API call is made to get the initial sensor state.
51 * Every sensor and switch is supported by this Thing, because a unified state is kept
52 * in {@link #sensorState}. Every field that got received by the REST API for this specific
53 * sensor is published to the framework.
55 * @author David Graeff - Initial contribution
58 public class SensorThingHandler extends SensorBaseThingHandler {
59 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PRESENCE_SENSOR,
60 THING_TYPE_DAYLIGHT_SENSOR, THING_TYPE_POWER_SENSOR, THING_TYPE_CONSUMPTION_SENSOR, THING_TYPE_LIGHT_SENSOR,
61 THING_TYPE_TEMPERATURE_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_PRESSURE_SENSOR, THING_TYPE_SWITCH,
62 THING_TYPE_OPENCLOSE_SENSOR, THING_TYPE_WATERLEAKAGE_SENSOR, THING_TYPE_FIRE_SENSOR,
63 THING_TYPE_ALARM_SENSOR, THING_TYPE_VIBRATION_SENSOR, THING_TYPE_BATTERY_SENSOR,
64 THING_TYPE_CARBONMONOXIDE_SENSOR, THING_TYPE_AIRQUALITY_SENSOR, THING_TYPE_COLOR_CONTROL,
65 THING_TYPE_MOISTURE_SENSOR);
67 private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
68 CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
70 public SensorThingHandler(Thing thing, Gson gson) {
75 public void handleCommand(ChannelUID channelUID, Command command) {
76 if (command instanceof RefreshType) {
77 sensorState.buttonevent = null;
78 valueUpdated(channelUID, sensorState, false);
81 switch (channelUID.getId()) {
83 if (command instanceof OnOffType) {
84 SensorUpdateConfig newConfig = new SensorUpdateConfig();
85 newConfig.on = OnOffType.ON.equals(command);
86 sendCommand(newConfig, command, channelUID, null);
93 protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
94 super.valueUpdated(channelUID, newConfig);
95 switch (channelUID.getId()) {
96 case CHANNEL_ENABLED -> 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 -> updateDecimalTypeChannel(channelUID, newState.battery);
111 case CHANNEL_LIGHT -> {
112 Boolean dark = newState.dark;
114 Boolean daylight = newState.daylight;
115 if (dark) { // if it's dark, it's dark ;)
116 updateState(channelUID, new StringType("Dark"));
117 } else if (daylight != null) { // if its not dark, it might be between darkness and daylight
119 updateState(channelUID, new StringType("Daylight"));
121 updateState(channelUID, new StringType("Sunset"));
123 } else { // if no daylight value is known, we assume !dark means daylight
124 updateState(channelUID, new StringType("Daylight"));
128 case CHANNEL_POWER -> updateQuantityTypeChannel(channelUID, newState.power, WATT);
129 case CHANNEL_CONSUMPTION -> updateQuantityTypeChannel(channelUID, newState.consumption, WATT_HOUR);
130 case CHANNEL_VOLTAGE -> updateQuantityTypeChannel(channelUID, newState.voltage, VOLT);
131 case CHANNEL_CURRENT -> updateQuantityTypeChannel(channelUID, newState.current, MILLI(AMPERE));
132 case CHANNEL_LIGHT_LUX -> updateQuantityTypeChannel(channelUID, newState.lux, LUX);
133 case CHANNEL_COLOR -> {
134 final double @Nullable [] xy = newState.xy;
135 if (xy != null && xy.length == 2) {
136 updateState(channelUID, ColorUtil.xyToHsv(xy));
139 case CHANNEL_LIGHT_LEVEL -> updateDecimalTypeChannel(channelUID, newState.lightlevel);
140 case CHANNEL_DARK -> updateSwitchChannel(channelUID, newState.dark);
141 case CHANNEL_DAYLIGHT -> updateSwitchChannel(channelUID, newState.daylight);
142 case CHANNEL_TEMPERATURE -> updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
143 case CHANNEL_HUMIDITY -> updateQuantityTypeChannel(channelUID, newState.humidity, PERCENT, 1.0 / 100);
144 case CHANNEL_PRESSURE -> updateQuantityTypeChannel(channelUID, newState.pressure, HECTO(PASCAL));
145 case CHANNEL_PRESENCE -> updateSwitchChannel(channelUID, newState.presence);
146 case CHANNEL_VALUE -> updateDecimalTypeChannel(channelUID, newState.status);
147 case CHANNEL_OPENCLOSE -> {
148 Boolean open = newState.open;
150 updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
153 case CHANNEL_WATERLEAKAGE -> updateSwitchChannel(channelUID, newState.water);
154 case CHANNEL_FIRE -> updateSwitchChannel(channelUID, newState.fire);
155 case CHANNEL_ALARM -> updateSwitchChannel(channelUID, newState.alarm);
156 case CHANNEL_TAMPERED -> updateSwitchChannel(channelUID, newState.tampered);
157 case CHANNEL_VIBRATION -> updateSwitchChannel(channelUID, newState.vibration);
158 case CHANNEL_CARBONMONOXIDE -> updateSwitchChannel(channelUID, newState.carbonmonoxide);
159 case CHANNEL_AIRQUALITY -> updateStringChannel(channelUID, newState.airquality);
160 case CHANNEL_AIRQUALITYPPB ->
161 updateQuantityTypeChannel(channelUID, newState.airqualityppb, PARTS_PER_BILLION);
162 case CHANNEL_MOISTURE -> updateQuantityTypeChannel(channelUID, newState.moisture, PERCENT);
163 case CHANNEL_BUTTON -> updateDecimalTypeChannel(channelUID, newState.buttonevent);
164 case CHANNEL_BUTTONEVENT -> {
165 Integer buttonevent = newState.buttonevent;
166 if (buttonevent != null && !initializing) {
167 triggerChannel(channelUID, String.valueOf(buttonevent));
170 case CHANNEL_GESTURE -> updateDecimalTypeChannel(channelUID, newState.gesture);
171 case CHANNEL_GESTUREEVENT -> {
172 Integer gesture = newState.gesture;
173 if (gesture != null && !initializing) {
174 triggerChannel(channelUID, String.valueOf(gesture));
181 protected boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorConfig,
182 SensorState sensorState) {
183 boolean thingEdited = false;
185 // some Xiaomi sensors
186 if (sensorConfig.temperature != null && createChannel(thingBuilder, CHANNEL_TEMPERATURE, ChannelKind.STATE)) {
190 // ZHAPresence - e.g. IKEA TRÅDFRI motion sensor
191 if (sensorState.dark != null && createChannel(thingBuilder, CHANNEL_DARK, ChannelKind.STATE)) {
195 // ZHAConsumption - e.g Bitron 902010/25 or Heiman SmartPlug
196 if (sensorState.power != null && createChannel(thingBuilder, CHANNEL_POWER, ChannelKind.STATE)) {
199 // ZHAConsumption - e.g. Linky devices second channel
200 if (sensorState.consumption2 != null && createChannel(thingBuilder, CHANNEL_CONSUMPTION_2, ChannelKind.STATE)) {
204 // ZHAPower - e.g. Heiman SmartPlug
205 if (sensorState.voltage != null && createChannel(thingBuilder, CHANNEL_VOLTAGE, ChannelKind.STATE)) {
208 if (sensorState.current != null && createChannel(thingBuilder, CHANNEL_CURRENT, ChannelKind.STATE)) {
212 // IAS Zone sensor - e.g. Heiman HS1MS motion sensor
213 if (sensorState.tampered != null && createChannel(thingBuilder, CHANNEL_TAMPERED, ChannelKind.STATE)) {
218 if (sensorState.gesture != null && (createChannel(thingBuilder, CHANNEL_GESTURE, ChannelKind.STATE)
219 || createChannel(thingBuilder, CHANNEL_GESTUREEVENT, ChannelKind.TRIGGER))) {
227 protected List<String> getConfigChannels() {
228 return CONFIG_CHANNELS;