]> git.basschouten.com Git - openhab-addons.git/blob
e0f4b455f876a30598d684df7796516e902545bc
[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.SIUnits.CELSIUS;
17 import static org.openhab.core.library.unit.Units.PERCENT;
18
19 import java.math.BigDecimal;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Set;
23
24 import javax.measure.quantity.Temperature;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.deconz.internal.dto.SensorConfig;
29 import org.openhab.binding.deconz.internal.dto.SensorState;
30 import org.openhab.binding.deconz.internal.dto.ThermostatUpdateConfig;
31 import org.openhab.binding.deconz.internal.types.ThermostatMode;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.OpenClosedType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingTypeUID;
40 import org.openhab.core.thing.binding.builder.ThingBuilder;
41 import org.openhab.core.thing.type.ChannelKind;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.UnDefType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.google.gson.Gson;
49
50 /**
51  * This sensor Thermostat Thing doesn't establish any connections, that is done by the bridge Thing.
52  *
53  * It waits for the bridge to come online, grab the websocket connection and bridge configuration
54  * and registers to the websocket connection as a listener.
55  *
56  * A REST API call is made to get the initial sensor state.
57  *
58  * Only the Thermostat is supported by this Thing, because a unified state is kept
59  * in {@link #sensorState}. Every field that got received by the REST API for this specific
60  * sensor is published to the framework.
61  *
62  * @author Lukas Agethen - Initial contribution
63  */
64 @NonNullByDefault
65 public class SensorThermostatThingHandler extends SensorBaseThingHandler {
66     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_THERMOSTAT);
67
68     private static final List<String> CONFIG_CHANNELS = List.of(CHANNEL_EXTERNAL_WINDOW_OPEN, CHANNEL_BATTERY_LEVEL,
69             CHANNEL_BATTERY_LOW, CHANNEL_HEATSETPOINT, CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE,
70             CHANNEL_THERMOSTAT_LOCKED);
71
72     private final Logger logger = LoggerFactory.getLogger(SensorThermostatThingHandler.class);
73
74     public SensorThermostatThingHandler(Thing thing, Gson gson) {
75         super(thing, gson);
76     }
77
78     @Override
79     public void handleCommand(ChannelUID channelUID, Command command) {
80         if (command instanceof RefreshType) {
81             sensorState.buttonevent = null;
82             valueUpdated(channelUID, sensorState, false);
83             return;
84         }
85         ThermostatUpdateConfig newConfig = new ThermostatUpdateConfig();
86         switch (channelUID.getId()) {
87             case CHANNEL_THERMOSTAT_LOCKED -> newConfig.locked = OnOffType.ON.equals(command);
88             case CHANNEL_HEATSETPOINT -> {
89                 Integer newHeatsetpoint = getTemperatureFromCommand(command);
90                 if (newHeatsetpoint == null) {
91                     logger.warn("Heatsetpoint must not be null.");
92                     return;
93                 }
94                 newConfig.heatsetpoint = newHeatsetpoint;
95             }
96             case CHANNEL_TEMPERATURE_OFFSET -> {
97                 Integer newOffset = getTemperatureFromCommand(command);
98                 if (newOffset == null) {
99                     logger.warn("Offset must not be null.");
100                     return;
101                 }
102                 newConfig.offset = newOffset;
103             }
104             case CHANNEL_THERMOSTAT_MODE -> {
105                 if (command instanceof StringType) {
106                     String thermostatMode = ((StringType) command).toString();
107                     try {
108                         newConfig.mode = ThermostatMode.valueOf(thermostatMode);
109                     } catch (IllegalArgumentException ex) {
110                         logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
111                                 ThermostatMode.values());
112                         return;
113                     }
114                     if (newConfig.mode == ThermostatMode.UNKNOWN) {
115                         logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
116                                 ThermostatMode.values());
117                         return;
118                     }
119                 } else {
120                     return;
121                 }
122             }
123             case CHANNEL_EXTERNAL_WINDOW_OPEN -> newConfig.externalwindowopen = OpenClosedType.OPEN.equals(command);
124             default -> {
125                 // no supported command
126                 return;
127             }
128         }
129
130         sendCommand(newConfig, command, channelUID, null);
131     }
132
133     @Override
134     protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
135         super.valueUpdated(channelUID, newConfig);
136         ThermostatMode thermostatMode = newConfig.mode;
137         String mode = thermostatMode != null ? thermostatMode.name() : ThermostatMode.UNKNOWN.name();
138         switch (channelUID.getId()) {
139             case CHANNEL_THERMOSTAT_LOCKED -> updateSwitchChannel(channelUID, newConfig.locked);
140             case CHANNEL_HEATSETPOINT ->
141                 updateQuantityTypeChannel(channelUID, newConfig.heatsetpoint, CELSIUS, 1.0 / 100);
142             case CHANNEL_TEMPERATURE_OFFSET ->
143                 updateQuantityTypeChannel(channelUID, newConfig.offset, CELSIUS, 1.0 / 100);
144             case CHANNEL_THERMOSTAT_MODE -> updateState(channelUID, new StringType(mode));
145             case CHANNEL_EXTERNAL_WINDOW_OPEN -> {
146                 Boolean open = newConfig.externalwindowopen;
147                 if (open != null) {
148                     updateState(channelUID, open ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
149                 }
150             }
151         }
152     }
153
154     @Override
155     protected void valueUpdated(ChannelUID channelUID, SensorState newState, boolean initializing) {
156         super.valueUpdated(channelUID, newState, initializing);
157         switch (channelUID.getId()) {
158             case CHANNEL_TEMPERATURE -> updateQuantityTypeChannel(channelUID, newState.temperature, CELSIUS, 1.0 / 100);
159             case CHANNEL_VALVE_POSITION -> {
160                 Integer valve = newState.valve;
161                 if (valve == null || valve < 0 || valve > 100) {
162                     updateState(channelUID, UnDefType.UNDEF);
163                 } else {
164                     updateQuantityTypeChannel(channelUID, valve, PERCENT, 1.0);
165                 }
166             }
167             case CHANNEL_WINDOW_OPEN -> {
168                 String open = newState.windowopen;
169                 if (open != null) {
170                     updateState(channelUID, "Closed".equals(open) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
171                 }
172             }
173             case CHANNEL_THERMOSTAT_ON -> updateSwitchChannel(channelUID, newState.on);
174         }
175     }
176
177     @Override
178     protected boolean createTypeSpecificChannels(ThingBuilder thingBuilder, SensorConfig sensorConfig,
179             SensorState sensorState) {
180         boolean thingEdited = false;
181         if (sensorConfig.locked != null && createChannel(thingBuilder, CHANNEL_THERMOSTAT_LOCKED, ChannelKind.STATE)) {
182             thingEdited = true;
183         }
184         if (sensorState.valve != null && createChannel(thingBuilder, CHANNEL_VALVE_POSITION, ChannelKind.STATE)) {
185             thingEdited = true;
186         }
187         if (sensorState.on != null && createChannel(thingBuilder, CHANNEL_THERMOSTAT_ON, ChannelKind.STATE)) {
188             thingEdited = true;
189         }
190         if (sensorState.windowopen != null && createChannel(thingBuilder, CHANNEL_WINDOW_OPEN, ChannelKind.STATE)) {
191             thingEdited = true;
192         }
193         if (sensorConfig.externalwindowopen != null
194                 && createChannel(thingBuilder, CHANNEL_EXTERNAL_WINDOW_OPEN, ChannelKind.STATE)) {
195             thingEdited = true;
196         }
197
198         return thingEdited;
199     }
200
201     @Override
202     protected List<String> getConfigChannels() {
203         return CONFIG_CHANNELS;
204     }
205
206     private @Nullable Integer getTemperatureFromCommand(Command command) {
207         BigDecimal newTemperature;
208         if (command instanceof DecimalType) {
209             newTemperature = ((DecimalType) command).toBigDecimal();
210         } else if (command instanceof QuantityType) {
211             @SuppressWarnings("unchecked")
212             QuantityType<Temperature> temperatureCelsius = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
213             if (temperatureCelsius != null) {
214                 newTemperature = temperatureCelsius.toBigDecimal();
215             } else {
216                 return null;
217             }
218         } else {
219             return null;
220         }
221         return newTemperature.scaleByPowerOfTen(2).intValue();
222     }
223 }