]> git.basschouten.com Git - openhab-addons.git/blob
563d5aca502c98393b019c623b6ce5bf08defa52
[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.deconz.internal.handler;
14
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.*;
19
20 import java.util.List;
21 import java.util.Set;
22
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;
39
40 import com.google.gson.Gson;
41
42 /**
43  * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
44  *
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.
47  *
48  * A REST API call is made to get the initial sensor state.
49  *
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.
53  *
54  * @author David Graeff - Initial contribution
55  */
56 @NonNullByDefault
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);
65
66     private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
67             CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
68
69     public SensorThingHandler(Thing thing, Gson gson) {
70         super(thing, gson);
71     }
72
73     @Override
74     public void handleCommand(ChannelUID channelUID, Command command) {
75         if (command instanceof RefreshType) {
76             sensorState.buttonevent = null;
77             valueUpdated(channelUID, sensorState, false);
78             return;
79         }
80         switch (channelUID.getId()) {
81             case CHANNEL_ENABLED:
82                 if (command instanceof OnOffType) {
83                     SensorUpdateConfig newConfig = new SensorUpdateConfig();
84                     newConfig.on = OnOffType.ON.equals(command);
85                     sendCommand(newConfig, command, channelUID, null);
86                 }
87                 break;
88         }
89     }
90
91     @Override
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));
100                 }
101             }
102         }
103     }
104
105     @Override
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.xyToHsb(xy));
121                 }
122             }
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;
137                 if (open != null) {
138                     updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
139                 }
140             }
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);
158         }
159     }
160
161     @Override
162     protected boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorConfig,
163             SensorState sensorState) {
164         boolean thingEdited = false;
165
166         // some Xiaomi sensors
167         if (sensorConfig.temperature != null && createChannel(thingBuilder, CHANNEL_TEMPERATURE, ChannelKind.STATE)) {
168             thingEdited = true;
169         }
170
171         // ZHAPresence - e.g. IKEA TRÅDFRI motion sensor
172         if (sensorState.dark != null && createChannel(thingBuilder, CHANNEL_DARK, ChannelKind.STATE)) {
173             thingEdited = true;
174         }
175
176         // ZHAConsumption - e.g Bitron 902010/25 or Heiman SmartPlug
177         if (sensorState.power != null && createChannel(thingBuilder, CHANNEL_POWER, ChannelKind.STATE)) {
178             thingEdited = true;
179         }
180         // ZHAConsumption - e.g. Linky devices second channel
181         if (sensorState.consumption2 != null && createChannel(thingBuilder, CHANNEL_CONSUMPTION_2, ChannelKind.STATE)) {
182             thingEdited = true;
183         }
184
185         // ZHAPower - e.g. Heiman SmartPlug
186         if (sensorState.voltage != null && createChannel(thingBuilder, CHANNEL_VOLTAGE, ChannelKind.STATE)) {
187             thingEdited = true;
188         }
189         if (sensorState.current != null && createChannel(thingBuilder, CHANNEL_CURRENT, ChannelKind.STATE)) {
190             thingEdited = true;
191         }
192
193         // IAS Zone sensor - e.g. Heiman HS1MS motion sensor
194         if (sensorState.tampered != null && createChannel(thingBuilder, CHANNEL_TAMPERED, ChannelKind.STATE)) {
195             thingEdited = true;
196         }
197
198         // e.g. Aqara Cube
199         if (sensorState.gesture != null && (createChannel(thingBuilder, CHANNEL_GESTURE, ChannelKind.STATE)
200                 || createChannel(thingBuilder, CHANNEL_GESTUREEVENT, ChannelKind.TRIGGER))) {
201             thingEdited = true;
202         }
203
204         // vibration sensors
205         if (sensorState.tiltangle != null && createChannel(thingBuilder, CHANNEL_TILTANGLE, ChannelKind.STATE)) {
206             thingEdited = true;
207         }
208         if (sensorState.vibrationstrength != null
209                 && createChannel(thingBuilder, CHANNEL_VIBRATION_STRENGTH, ChannelKind.STATE)) {
210             thingEdited = true;
211         }
212         if (sensorState.orientation != null) {
213             if (createChannel(thingBuilder, CHANNEL_ORIENTATION_X, ChannelKind.STATE)) {
214                 thingEdited = true;
215             }
216             if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Y, ChannelKind.STATE)) {
217                 thingEdited = true;
218             }
219             if (createChannel(thingBuilder, CHANNEL_ORIENTATION_Z, ChannelKind.STATE)) {
220                 thingEdited = true;
221             }
222         }
223
224         return thingEdited;
225     }
226
227     @Override
228     protected List<String> getConfigChannels() {
229         return CONFIG_CHANNELS;
230     }
231
232     /**
233      * Determine the light state from a state message
234      *
235      * @param newState the {@link SensorState} message
236      * @return <code>Dark</code>, <code>Daylight</code>, <code>Sunset</code>
237      */
238     private @Nullable String getLightState(SensorState newState) {
239         Boolean dark = newState.dark;
240         if (dark == null) {
241             return null;
242         }
243         Boolean daylight = newState.daylight;
244         if (dark) { // if it's dark, it's dark ;)
245             return "Dark";
246         } else if (daylight != null) { // if its not dark, it might be between darkness and daylight
247             if (daylight) {
248                 return "Daylight";
249             } else {
250                 return "Sunset";
251             }
252         } else { // if no daylight value is known, we assume !dark means daylight
253             return "Daylight";
254         }
255     }
256
257     private void triggerChannel(ChannelUID channelUID, @Nullable Integer value, boolean initializing) {
258         if (value == null || initializing) {
259             return;
260         }
261         triggerChannel(channelUID, String.valueOf(value));
262     }
263 }