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.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.thing.binding.builder.ThingBuilder;
35 import org.openhab.core.thing.type.ChannelKind;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.openhab.core.util.ColorUtil;
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,
64 THING_TYPE_MOISTURE_SENSOR);
66 private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
67 CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
69 public SensorThingHandler(Thing thing, Gson gson) {
74 public void handleCommand(ChannelUID channelUID, Command command) {
75 if (command instanceof RefreshType) {
76 sensorState.buttonevent = null;
77 valueUpdated(channelUID, sensorState, false);
80 switch (channelUID.getId()) {
82 if (command instanceof OnOffType) {
83 SensorUpdateConfig newConfig = new SensorUpdateConfig();
84 newConfig.on = OnOffType.ON.equals(command);
85 sendCommand(newConfig, command, channelUID, null);
92 protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
93 super.valueUpdated(channelUID, newConfig);
94 switch (channelUID.getId()) {
95 case CHANNEL_ENABLED -> updateState(channelUID, OnOffType.from(newConfig.on));
96 case CHANNEL_TEMPERATURE -> {
97 Float temperature = newConfig.temperature;
98 if (temperature != null) {
99 updateState(channelUID, new QuantityType<>(temperature / 100, CELSIUS));
106 protected void valueUpdated(ChannelUID channelUID, SensorState newState, boolean initializing) {
107 super.valueUpdated(channelUID, newState, initializing);
108 switch (channelUID.getId()) {
109 case CHANNEL_AIRQUALITY -> updateStringChannel(channelUID, newState.airquality);
110 case CHANNEL_AIRQUALITYPPB ->
111 updateQuantityTypeChannel(channelUID, newState.airqualityppb, PARTS_PER_BILLION);
112 case CHANNEL_ALARM -> updateSwitchChannel(channelUID, newState.alarm);
113 case CHANNEL_BATTERY_LEVEL -> updateDecimalTypeChannel(channelUID, newState.battery);
114 case CHANNEL_BUTTON -> updateDecimalTypeChannel(channelUID, newState.buttonevent);
115 case CHANNEL_BUTTONEVENT -> triggerChannel(channelUID, newState.buttonevent, initializing);
116 case CHANNEL_CARBONMONOXIDE -> updateSwitchChannel(channelUID, newState.carbonmonoxide);
117 case CHANNEL_COLOR -> {
118 final double @Nullable [] xy = newState.xy;
119 if (xy != null && xy.length == 2) {
120 updateState(channelUID, ColorUtil.xyToHsv(xy));
123 case CHANNEL_CONSUMPTION -> updateQuantityTypeChannel(channelUID, newState.consumption, WATT_HOUR);
124 case CHANNEL_CURRENT -> updateQuantityTypeChannel(channelUID, newState.current, MILLI(AMPERE));
125 case CHANNEL_DARK -> updateSwitchChannel(channelUID, newState.dark);
126 case CHANNEL_DAYLIGHT -> updateSwitchChannel(channelUID, newState.daylight);
127 case CHANNEL_FIRE -> updateSwitchChannel(channelUID, newState.fire);
128 case CHANNEL_GESTURE -> updateDecimalTypeChannel(channelUID, newState.gesture);
129 case CHANNEL_GESTUREEVENT -> triggerChannel(channelUID, newState.gesture, initializing);
130 case CHANNEL_HUMIDITY -> updateQuantityTypeChannel(channelUID, newState.humidity, PERCENT, 1.0 / 100);
131 case CHANNEL_LIGHT -> updateStringChannel(channelUID, getLightState(newState));
132 case CHANNEL_LIGHT_LEVEL -> updateDecimalTypeChannel(channelUID, newState.lightlevel);
133 case CHANNEL_LIGHT_LUX -> updateQuantityTypeChannel(channelUID, newState.lux, LUX);
134 case CHANNEL_MOISTURE -> updateQuantityTypeChannel(channelUID, newState.moisture, PERCENT);
135 case CHANNEL_OPENCLOSE -> {
136 Boolean open = newState.open;
138 updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
141 case CHANNEL_ORIENTATION_X ->
142 updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[0] : null);
143 case CHANNEL_ORIENTATION_Y ->
144 updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[1] : null);
145 case CHANNEL_ORIENTATION_Z ->
146 updateDecimalTypeChannel(channelUID, newState.orientation != null ? newState.orientation[2] : null);
147 case CHANNEL_POWER -> updateQuantityTypeChannel(channelUID, newState.power, WATT);
148 case CHANNEL_PRESENCE -> updateSwitchChannel(channelUID, newState.presence);
149 case CHANNEL_PRESSURE -> updateQuantityTypeChannel(channelUID, newState.pressure, HECTO(PASCAL));
150 case CHANNEL_TAMPERED -> updateSwitchChannel(channelUID, newState.tampered);
151 case CHANNEL_TEMPERATURE -> updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
152 case CHANNEL_TILTANGLE -> updateQuantityTypeChannel(channelUID, newState.tiltangle, DEGREE_ANGLE);
153 case CHANNEL_VALUE -> updateDecimalTypeChannel(channelUID, newState.status);
154 case CHANNEL_VIBRATION -> updateSwitchChannel(channelUID, newState.vibration);
155 case CHANNEL_VIBRATION_STRENGTH -> updateDecimalTypeChannel(channelUID, newState.vibrationstrength);
156 case CHANNEL_VOLTAGE -> updateQuantityTypeChannel(channelUID, newState.voltage, VOLT);
157 case CHANNEL_WATERLEAKAGE -> updateSwitchChannel(channelUID, newState.water);
162 protected boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorConfig,
163 SensorState sensorState) {
164 boolean thingEdited = false;
166 // some Xiaomi sensors
167 if (sensorConfig.temperature != null && createChannel(thingBuilder, CHANNEL_TEMPERATURE, ChannelKind.STATE)) {
171 // ZHAPresence - e.g. IKEA TRÅDFRI motion sensor
172 if (sensorState.dark != null && createChannel(thingBuilder, CHANNEL_DARK, ChannelKind.STATE)) {
176 // ZHAConsumption - e.g Bitron 902010/25 or Heiman SmartPlug
177 if (sensorState.power != null && createChannel(thingBuilder, CHANNEL_POWER, ChannelKind.STATE)) {
180 // ZHAConsumption - e.g. Linky devices second channel
181 if (sensorState.consumption2 != null && createChannel(thingBuilder, CHANNEL_CONSUMPTION_2, ChannelKind.STATE)) {
185 // ZHAPower - e.g. Heiman SmartPlug
186 if (sensorState.voltage != null && createChannel(thingBuilder, CHANNEL_VOLTAGE, ChannelKind.STATE)) {
189 if (sensorState.current != null && createChannel(thingBuilder, CHANNEL_CURRENT, ChannelKind.STATE)) {
193 // IAS Zone sensor - e.g. Heiman HS1MS motion sensor
194 if (sensorState.tampered != null && createChannel(thingBuilder, CHANNEL_TAMPERED, ChannelKind.STATE)) {
199 if (sensorState.gesture != null && (createChannel(thingBuilder, CHANNEL_GESTURE, ChannelKind.STATE)
200 || createChannel(thingBuilder, CHANNEL_GESTUREEVENT, ChannelKind.TRIGGER))) {
205 if (sensorState.tiltangle != null && createChannel(thingBuilder, CHANNEL_TILTANGLE, ChannelKind.STATE)) {
208 if (sensorState.vibrationstrength != null
209 && createChannel(thingBuilder, CHANNEL_VIBRATION_STRENGTH, ChannelKind.STATE)) {
212 if (sensorState.orientation != null) {
213 if (createChannel(thingBuilder, CHANNEL_ORIENTATION_X, ChannelKind.STATE)) {
216 if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Y, ChannelKind.STATE)) {
219 if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Z, ChannelKind.STATE)) {
228 protected List<String> getConfigChannels() {
229 return CONFIG_CHANNELS;
233 * Determine the light state from a state message
235 * @param newState the {@link SensorState} message
236 * @return <code>Dark</code>, <code>Daylight</code>, <code>Sunset</code>
238 private @Nullable String getLightState(SensorState newState) {
239 Boolean dark = newState.dark;
243 Boolean daylight = newState.daylight;
244 if (dark) { // if it's dark, it's dark ;)
246 } else if (daylight != null) { // if its not dark, it might be between darkness and daylight
252 } else { // if no daylight value is known, we assume !dark means daylight
257 private void triggerChannel(ChannelUID channelUID, @Nullable Integer value, boolean initializing) {
258 if (value == null || initializing) {
261 triggerChannel(channelUID, String.valueOf(value));