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