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