]> git.basschouten.com Git - openhab-addons.git/blob
0d2322dcc6eb78972bf901d00ee872b3fb77f1f1
[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.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;
40
41 import com.google.gson.Gson;
42
43 /**
44  * This sensor Thing doesn't establish any connections, that is done by the bridge Thing.
45  *
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.
48  *
49  * A REST API call is made to get the initial sensor state.
50  *
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.
54  *
55  * @author David Graeff - Initial contribution
56  */
57 @NonNullByDefault
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);
66
67     private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
68             CHANNEL_ENABLED, CHANNEL_TEMPERATURE);
69
70     public SensorThingHandler(Thing thing, Gson gson) {
71         super(thing, gson);
72     }
73
74     @Override
75     public void handleCommand(ChannelUID channelUID, Command command) {
76         if (command instanceof RefreshType) {
77             sensorState.buttonevent = null;
78             valueUpdated(channelUID, sensorState, false);
79             return;
80         }
81         switch (channelUID.getId()) {
82             case CHANNEL_ENABLED:
83                 if (command instanceof OnOffType) {
84                     SensorUpdateConfig newConfig = new SensorUpdateConfig();
85                     newConfig.on = OnOffType.ON.equals(command);
86                     sendCommand(newConfig, command, channelUID, null);
87                 }
88                 break;
89         }
90     }
91
92     @Override
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));
101                 }
102             }
103         }
104     }
105
106     @Override
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;
113                 if (dark != null) {
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
118                         if (daylight) {
119                             updateState(channelUID, new StringType("Daylight"));
120                         } else {
121                             updateState(channelUID, new StringType("Sunset"));
122                         }
123                     } else { // if no daylight value is known, we assume !dark means daylight
124                         updateState(channelUID, new StringType("Daylight"));
125                     }
126                 }
127             }
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));
137                 }
138             }
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;
149                 if (open != null) {
150                     updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
151                 }
152             }
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));
168                 }
169             }
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));
175                 }
176             }
177         }
178     }
179
180     @Override
181     protected boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorConfig,
182             SensorState sensorState) {
183         boolean thingEdited = false;
184
185         // some Xiaomi sensors
186         if (sensorConfig.temperature != null && createChannel(thingBuilder, CHANNEL_TEMPERATURE, ChannelKind.STATE)) {
187             thingEdited = true;
188         }
189
190         // ZHAPresence - e.g. IKEA TRÅDFRI motion sensor
191         if (sensorState.dark != null && createChannel(thingBuilder, CHANNEL_DARK, ChannelKind.STATE)) {
192             thingEdited = true;
193         }
194
195         // ZHAConsumption - e.g Bitron 902010/25 or Heiman SmartPlug
196         if (sensorState.power != null && createChannel(thingBuilder, CHANNEL_POWER, ChannelKind.STATE)) {
197             thingEdited = true;
198         }
199         // ZHAConsumption - e.g. Linky devices second channel
200         if (sensorState.consumption2 != null && createChannel(thingBuilder, CHANNEL_CONSUMPTION_2, ChannelKind.STATE)) {
201             thingEdited = true;
202         }
203
204         // ZHAPower - e.g. Heiman SmartPlug
205         if (sensorState.voltage != null && createChannel(thingBuilder, CHANNEL_VOLTAGE, ChannelKind.STATE)) {
206             thingEdited = true;
207         }
208         if (sensorState.current != null && createChannel(thingBuilder, CHANNEL_CURRENT, ChannelKind.STATE)) {
209             thingEdited = true;
210         }
211
212         // IAS Zone sensor - e.g. Heiman HS1MS motion sensor
213         if (sensorState.tampered != null && createChannel(thingBuilder, CHANNEL_TAMPERED, ChannelKind.STATE)) {
214             thingEdited = true;
215         }
216
217         // e.g. Aqara Cube
218         if (sensorState.gesture != null && (createChannel(thingBuilder, CHANNEL_GESTURE, ChannelKind.STATE)
219                 || createChannel(thingBuilder, CHANNEL_GESTUREEVENT, ChannelKind.TRIGGER))) {
220             thingEdited = true;
221         }
222
223         return thingEdited;
224     }
225
226     @Override
227     protected List<String> getConfigChannels() {
228         return CONFIG_CHANNELS;
229     }
230 }