]> git.basschouten.com Git - openhab-addons.git/blob
b067f3f65d64c9168efb848a7baa231bd0e3ddd4
[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.nest.internal.sdm.handler;
14
15 import static org.openhab.binding.nest.internal.sdm.SDMBindingConstants.*;
16 import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
17 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
18 import static org.openhab.core.library.unit.Units.PERCENT;
19
20 import java.math.BigDecimal;
21 import java.time.Duration;
22 import java.time.ZonedDateTime;
23
24 import javax.measure.Unit;
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.nest.internal.sdm.SDMBindingConstants;
30 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetFanTimerRequest;
31 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetThermostatCoolSetpointRequest;
32 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetThermostatEcoModeRequest;
33 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetThermostatHeatSetpointRequest;
34 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetThermostatModeRequest;
35 import org.openhab.binding.nest.internal.sdm.dto.SDMCommands.SDMSetThermostatRangeSetpointRequest;
36 import org.openhab.binding.nest.internal.sdm.dto.SDMEvent;
37 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits;
38 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMDeviceSettingsTrait;
39 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMFanTimerMode;
40 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMFanTrait;
41 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMHumidityTrait;
42 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMTemperatureTrait;
43 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatEcoMode;
44 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatEcoTrait;
45 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatHvacTrait;
46 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatMode;
47 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatModeTrait;
48 import org.openhab.binding.nest.internal.sdm.dto.SDMTraits.SDMThermostatTemperatureSetpointTrait;
49 import org.openhab.binding.nest.internal.sdm.exception.FailedSendingSDMDataException;
50 import org.openhab.binding.nest.internal.sdm.exception.InvalidSDMAccessTokenException;
51 import org.openhab.core.config.core.Configuration;
52 import org.openhab.core.i18n.TimeZoneProvider;
53 import org.openhab.core.library.types.DateTimeType;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.library.types.QuantityType;
56 import org.openhab.core.library.types.StringType;
57 import org.openhab.core.thing.Channel;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.types.Command;
61 import org.openhab.core.types.RefreshType;
62 import org.openhab.core.types.State;
63 import org.openhab.core.types.UnDefType;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 /**
68  * The {@link SDMThermostatHandler} handles state updates and commands for SDM thermostat devices.
69  *
70  * @author Brian Higginbotham - Initial contribution
71  * @author Wouter Born - Initial contribution
72  */
73 @NonNullByDefault
74 public class SDMThermostatHandler extends SDMBaseHandler {
75
76     private final Logger logger = LoggerFactory.getLogger(SDMThermostatHandler.class);
77
78     public SDMThermostatHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
79         super(thing, timeZoneProvider);
80     }
81
82     @SuppressWarnings("unchecked")
83     @Override
84     public void handleCommand(ChannelUID channelUID, Command command) {
85         try {
86             if (command instanceof RefreshType) {
87                 delayedRefresh();
88             } else if (CHANNEL_CURRENT_ECO_MODE.equals(channelUID.getId())) {
89                 if (command instanceof StringType) {
90                     SDMThermostatEcoMode mode = SDMThermostatEcoMode.valueOf(command.toString());
91                     executeDeviceCommand(new SDMSetThermostatEcoModeRequest(mode));
92                     delayedRefresh();
93                 }
94             } else if (CHANNEL_CURRENT_MODE.equals(channelUID.getId())) {
95                 if (command instanceof StringType) {
96                     SDMThermostatMode mode = SDMThermostatMode.valueOf(command.toString());
97                     executeDeviceCommand(new SDMSetThermostatModeRequest(mode));
98                     delayedRefresh();
99                 }
100             } else if (CHANNEL_FAN_TIMER_MODE.equals(channelUID.getId())) {
101                 if (command instanceof OnOffType onOffCommand) {
102                     if (onOffCommand == OnOffType.ON) {
103                         executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.ON, getFanTimerDuration()));
104                     } else {
105                         executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.OFF));
106                     }
107                     delayedRefresh();
108                 }
109             } else if (CHANNEL_FAN_TIMER_TIMEOUT.equals(channelUID.getId())) {
110                 if (command instanceof DateTimeType dateTimeCommand) {
111                     Duration duration = Duration.between(ZonedDateTime.now(), dateTimeCommand.getZonedDateTime());
112                     executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.ON, duration));
113                     delayedRefresh();
114                 }
115             } else if (CHANNEL_MAXIMUM_TEMPERATURE.equals(channelUID.getId())) {
116                 if (command instanceof QuantityType) {
117                     BigDecimal minTemperature = getMinTemperature();
118                     if (minTemperature != null) {
119                         setTargetTemperature(new QuantityType<>(minTemperature, CELSIUS),
120                                 (QuantityType<Temperature>) command);
121                         delayedRefresh();
122                     }
123                 }
124             } else if (CHANNEL_MINIMUM_TEMPERATURE.equals(channelUID.getId())) {
125                 if (command instanceof QuantityType) {
126                     BigDecimal maxTemperature = getMaxTemperature();
127                     if (maxTemperature != null) {
128                         setTargetTemperature((QuantityType<Temperature>) command,
129                                 new QuantityType<>(maxTemperature, CELSIUS));
130                         delayedRefresh();
131                     }
132                 }
133             } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
134                 if (command instanceof QuantityType) {
135                     setTargetTemperature((QuantityType<Temperature>) command);
136                     delayedRefresh();
137                 }
138             }
139         } catch (FailedSendingSDMDataException | InvalidSDMAccessTokenException e) {
140             logger.debug("Exception while handling {} command for {}: {}", command, thing.getUID(), e.getMessage());
141         }
142     }
143
144     @Override
145     protected void updateStateWithTraits(SDMTraits traits) {
146         logger.debug("Refreshing channels for: {}", thing.getUID());
147         super.updateStateWithTraits(traits);
148
149         SDMHumidityTrait humidity = traits.humidity;
150         if (humidity != null) {
151             updateState(CHANNEL_AMBIENT_HUMIDITY, new QuantityType<>(humidity.ambientHumidityPercent, PERCENT));
152         }
153
154         SDMTemperatureTrait temperature = traits.temperature;
155         if (temperature != null) {
156             updateState(CHANNEL_AMBIENT_TEMPERATURE, temperatureToState(temperature.ambientTemperatureCelsius));
157         }
158
159         SDMThermostatModeTrait thermostatMode = traits.thermostatMode;
160         if (thermostatMode != null) {
161             updateState(CHANNEL_CURRENT_MODE, new StringType(thermostatMode.mode.name()));
162         }
163
164         SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
165         if (thermostatEco != null) {
166             updateState(CHANNEL_CURRENT_ECO_MODE, new StringType(thermostatEco.mode.name()));
167         }
168
169         SDMFanTrait fan = traits.fan;
170         if (fan != null) {
171             updateState(CHANNEL_FAN_TIMER_MODE, OnOffType.from(fan.timerMode == SDMFanTimerMode.ON));
172             updateState(CHANNEL_FAN_TIMER_TIMEOUT, fan.timerTimeout == null ? UnDefType.NULL
173                     : new DateTimeType(fan.timerTimeout.withZoneSameInstant(timeZoneProvider.getTimeZone())));
174         }
175
176         SDMThermostatHvacTrait thermostatHvac = traits.thermostatHvac;
177         if (thermostatHvac != null) {
178             updateState(CHANNEL_HVAC_STATUS, new StringType(thermostatHvac.status.name()));
179         }
180
181         BigDecimal maxTemperature = getMaxTemperature();
182         if (maxTemperature != null) {
183             updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(getMaxTemperature()));
184         }
185
186         BigDecimal minTemperature = getMinTemperature();
187         if (minTemperature != null) {
188             updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(minTemperature));
189         }
190
191         BigDecimal targetTemperature = getTargetTemperature();
192         if (targetTemperature != null) {
193             updateState(CHANNEL_TARGET_TEMPERATURE, temperatureToState(targetTemperature));
194         }
195     }
196
197     private Duration getFanTimerDuration() {
198         long seconds = 900;
199
200         Channel channel = getThing().getChannel(SDMBindingConstants.CHANNEL_FAN_TIMER_MODE);
201         if (channel != null) {
202             Configuration configuration = channel.getConfiguration();
203             Object fanTimerDuration = configuration.get(SDMBindingConstants.CONFIG_PROPERTY_FAN_TIMER_DURATION);
204             if (fanTimerDuration instanceof BigDecimal decimalValue) {
205                 seconds = decimalValue.longValue();
206             }
207         }
208
209         return Duration.ofSeconds(seconds);
210     }
211
212     private @Nullable BigDecimal getMinTemperature() {
213         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
214         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
215             return thermostatEco.heatCelsius;
216         }
217
218         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
219         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
220         if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
221             return thermostatTemperatureSetpoint.heatCelsius;
222         }
223
224         return null;
225     }
226
227     private @Nullable BigDecimal getMaxTemperature() {
228         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
229         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
230             return thermostatEco.coolCelsius;
231         }
232
233         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
234         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
235         if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
236             return thermostatTemperatureSetpoint.coolCelsius;
237         }
238
239         return null;
240     }
241
242     private @Nullable BigDecimal getTargetTemperature() {
243         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
244         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
245             return null;
246         }
247
248         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
249         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
250         if (thermostatMode != null) {
251             if (thermostatMode.mode == SDMThermostatMode.COOL) {
252                 return thermostatTemperatureSetpoint.coolCelsius;
253             }
254             if (thermostatMode.mode == SDMThermostatMode.HEAT) {
255                 return thermostatTemperatureSetpoint.heatCelsius;
256             }
257         }
258
259         return null;
260     }
261
262     @Override
263     public void onEvent(SDMEvent event) {
264         super.onEvent(event);
265
266         SDMTraits traits = getTraitsForUpdate(event);
267         if (traits == null) {
268             return;
269         }
270
271         updateStateWithTraits(traits);
272
273         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = traits.thermostatTemperatureSetpoint;
274         if (thermostatTemperatureSetpoint != null) {
275             BigDecimal coolCelsius = thermostatTemperatureSetpoint.coolCelsius;
276             BigDecimal heatCelsius = thermostatTemperatureSetpoint.heatCelsius;
277             if (coolCelsius != null && heatCelsius != null) {
278                 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(heatCelsius));
279                 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(coolCelsius));
280             }
281         }
282
283         SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
284         if (thermostatEco != null) {
285             if (thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
286                 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(thermostatEco.heatCelsius));
287                 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(thermostatEco.coolCelsius));
288             }
289         }
290     }
291
292     private void setTargetTemperature(QuantityType<Temperature> value)
293             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
294         logger.debug("setThermostatTargetTemperature value={}", value);
295         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
296         if (thermostatMode.mode == SDMThermostatMode.COOL) {
297             executeDeviceCommand(new SDMSetThermostatCoolSetpointRequest(toCelsiusBigDecimal(value)));
298         } else if (thermostatMode.mode == SDMThermostatMode.HEAT) {
299             executeDeviceCommand(new SDMSetThermostatHeatSetpointRequest(toCelsiusBigDecimal(value)));
300         } else {
301             throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
302         }
303     }
304
305     private void setTargetTemperature(QuantityType<Temperature> minValue, QuantityType<Temperature> maxValue)
306             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
307         logger.debug("setThermostatTargetTemperature minValue={} maxValue={}", minValue, maxValue);
308         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
309         if (thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
310             executeDeviceCommand(new SDMSetThermostatRangeSetpointRequest(toCelsiusBigDecimal(minValue),
311                     toCelsiusBigDecimal(maxValue)));
312         } else {
313             throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
314         }
315     }
316
317     protected State temperatureToState(@Nullable BigDecimal value) {
318         if (value == null) {
319             return UnDefType.NULL;
320         }
321
322         QuantityType<Temperature> temperature = new QuantityType<>(value, CELSIUS);
323
324         if (getDeviceTemperatureUnit() == FAHRENHEIT) {
325             QuantityType<Temperature> converted = temperature.toUnit(FAHRENHEIT);
326             return converted == null ? UnDefType.NULL : converted;
327         }
328
329         return temperature;
330     }
331
332     private Unit<Temperature> getDeviceTemperatureUnit() {
333         SDMDeviceSettingsTrait deviceSettings = device.traits.deviceSettings;
334         if (deviceSettings == null) {
335             return CELSIUS;
336         }
337
338         switch (deviceSettings.temperatureScale) {
339             case CELSIUS:
340                 return CELSIUS;
341             case FAHRENHEIT:
342                 return FAHRENHEIT;
343             default:
344                 return CELSIUS;
345         }
346     }
347
348     private BigDecimal toCelsiusBigDecimal(QuantityType<Temperature> temperature) {
349         QuantityType<Temperature> celsiusTemperature = temperature.toUnit(CELSIUS);
350         if (celsiusTemperature == null) {
351             throw new IllegalArgumentException(
352                     String.format("Temperature '%s' cannot be converted to Celsius unit", temperature));
353         }
354         return celsiusTemperature.toBigDecimal();
355     }
356 }