]> git.basschouten.com Git - openhab-addons.git/blob
34b6a91506aa65ae2c808e4dffb94749cf1aa767
[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) {
102                     if ((OnOffType) command == 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) {
111                     Duration duration = Duration.between(ZonedDateTime.now(),
112                             ((DateTimeType) command).getZonedDateTime());
113                     executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.ON, duration));
114                     delayedRefresh();
115                 }
116             } else if (CHANNEL_MAXIMUM_TEMPERATURE.equals(channelUID.getId())) {
117                 if (command instanceof QuantityType) {
118                     BigDecimal minTemperature = getMinTemperature();
119                     if (minTemperature != null) {
120                         setTargetTemperature(new QuantityType<>(minTemperature, CELSIUS),
121                                 (QuantityType<Temperature>) command);
122                         delayedRefresh();
123                     }
124                 }
125             } else if (CHANNEL_MINIMUM_TEMPERATURE.equals(channelUID.getId())) {
126                 if (command instanceof QuantityType) {
127                     BigDecimal maxTemperature = getMaxTemperature();
128                     if (maxTemperature != null) {
129                         setTargetTemperature((QuantityType<Temperature>) command,
130                                 new QuantityType<>(maxTemperature, CELSIUS));
131                         delayedRefresh();
132                     }
133                 }
134             } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
135                 if (command instanceof QuantityType) {
136                     setTargetTemperature((QuantityType<Temperature>) command);
137                     delayedRefresh();
138                 }
139             }
140         } catch (FailedSendingSDMDataException | InvalidSDMAccessTokenException e) {
141             logger.debug("Exception while handling {} command for {}: {}", command, thing.getUID(), e.getMessage());
142         }
143     }
144
145     @Override
146     protected void updateStateWithTraits(SDMTraits traits) {
147         logger.debug("Refreshing channels for: {}", thing.getUID());
148         super.updateStateWithTraits(traits);
149
150         SDMHumidityTrait humidity = traits.humidity;
151         if (humidity != null) {
152             updateState(CHANNEL_AMBIENT_HUMIDITY, new QuantityType<>(humidity.ambientHumidityPercent, PERCENT));
153         }
154
155         SDMTemperatureTrait temperature = traits.temperature;
156         if (temperature != null) {
157             updateState(CHANNEL_AMBIENT_TEMPERATURE, temperatureToState(temperature.ambientTemperatureCelsius));
158         }
159
160         SDMThermostatModeTrait thermostatMode = traits.thermostatMode;
161         if (thermostatMode != null) {
162             updateState(CHANNEL_CURRENT_MODE, new StringType(thermostatMode.mode.name()));
163         }
164
165         SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
166         if (thermostatEco != null) {
167             updateState(CHANNEL_CURRENT_ECO_MODE, new StringType(thermostatEco.mode.name()));
168         }
169
170         SDMFanTrait fan = traits.fan;
171         if (fan != null) {
172             updateState(CHANNEL_FAN_TIMER_MODE, fan.timerMode == SDMFanTimerMode.ON ? OnOffType.ON : OnOffType.OFF);
173             updateState(CHANNEL_FAN_TIMER_TIMEOUT, fan.timerTimeout == null ? UnDefType.NULL
174                     : new DateTimeType(fan.timerTimeout.withZoneSameInstant(timeZoneProvider.getTimeZone())));
175         }
176
177         SDMThermostatHvacTrait thermostatHvac = traits.thermostatHvac;
178         if (thermostatHvac != null) {
179             updateState(CHANNEL_HVAC_STATUS, new StringType(thermostatHvac.status.name()));
180         }
181
182         BigDecimal maxTemperature = getMaxTemperature();
183         if (maxTemperature != null) {
184             updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(getMaxTemperature()));
185         }
186
187         BigDecimal minTemperature = getMinTemperature();
188         if (minTemperature != null) {
189             updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(minTemperature));
190         }
191
192         BigDecimal targetTemperature = getTargetTemperature();
193         if (targetTemperature != null) {
194             updateState(CHANNEL_TARGET_TEMPERATURE, temperatureToState(targetTemperature));
195         }
196     }
197
198     private Duration getFanTimerDuration() {
199         long seconds = 900;
200
201         Channel channel = getThing().getChannel(SDMBindingConstants.CHANNEL_FAN_TIMER_MODE);
202         if (channel != null) {
203             Configuration configuration = channel.getConfiguration();
204             Object fanTimerDuration = configuration.get(SDMBindingConstants.CONFIG_PROPERTY_FAN_TIMER_DURATION);
205             if (fanTimerDuration instanceof BigDecimal) {
206                 seconds = ((BigDecimal) fanTimerDuration).longValue();
207             }
208         }
209
210         return Duration.ofSeconds(seconds);
211     }
212
213     private @Nullable BigDecimal getMinTemperature() {
214         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
215         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
216             return thermostatEco.heatCelsius;
217         }
218
219         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
220         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
221         if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
222             return thermostatTemperatureSetpoint.heatCelsius;
223         }
224
225         return null;
226     }
227
228     private @Nullable BigDecimal getMaxTemperature() {
229         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
230         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
231             return thermostatEco.coolCelsius;
232         }
233
234         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
235         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
236         if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
237             return thermostatTemperatureSetpoint.coolCelsius;
238         }
239
240         return null;
241     }
242
243     private @Nullable BigDecimal getTargetTemperature() {
244         SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
245         if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
246             return null;
247         }
248
249         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
250         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
251         if (thermostatMode != null) {
252             if (thermostatMode.mode == SDMThermostatMode.COOL) {
253                 return thermostatTemperatureSetpoint.coolCelsius;
254             }
255             if (thermostatMode.mode == SDMThermostatMode.HEAT) {
256                 return thermostatTemperatureSetpoint.heatCelsius;
257             }
258         }
259
260         return null;
261     }
262
263     @Override
264     public void onEvent(SDMEvent event) {
265         super.onEvent(event);
266
267         SDMTraits traits = getTraitsForUpdate(event);
268         if (traits == null) {
269             return;
270         }
271
272         updateStateWithTraits(traits);
273
274         SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = traits.thermostatTemperatureSetpoint;
275         if (thermostatTemperatureSetpoint != null) {
276             BigDecimal coolCelsius = thermostatTemperatureSetpoint.coolCelsius;
277             BigDecimal heatCelsius = thermostatTemperatureSetpoint.heatCelsius;
278             if (coolCelsius != null && heatCelsius != null) {
279                 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(heatCelsius));
280                 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(coolCelsius));
281             }
282         }
283
284         SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
285         if (thermostatEco != null) {
286             if (thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
287                 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(thermostatEco.heatCelsius));
288                 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(thermostatEco.coolCelsius));
289             }
290         }
291     }
292
293     private void setTargetTemperature(QuantityType<Temperature> value)
294             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
295         logger.debug("setThermostatTargetTemperature value={}", value);
296         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
297         if (thermostatMode.mode == SDMThermostatMode.COOL) {
298             executeDeviceCommand(new SDMSetThermostatCoolSetpointRequest(toCelsiusBigDecimal(value)));
299         } else if (thermostatMode.mode == SDMThermostatMode.HEAT) {
300             executeDeviceCommand(new SDMSetThermostatHeatSetpointRequest(toCelsiusBigDecimal(value)));
301         } else {
302             throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
303         }
304     }
305
306     private void setTargetTemperature(QuantityType<Temperature> minValue, QuantityType<Temperature> maxValue)
307             throws FailedSendingSDMDataException, InvalidSDMAccessTokenException {
308         logger.debug("setThermostatTargetTemperature minValue={} maxValue={}", minValue, maxValue);
309         SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
310         if (thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
311             executeDeviceCommand(new SDMSetThermostatRangeSetpointRequest(toCelsiusBigDecimal(minValue),
312                     toCelsiusBigDecimal(maxValue)));
313         } else {
314             throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
315         }
316     }
317
318     protected State temperatureToState(@Nullable BigDecimal value) {
319         if (value == null) {
320             return UnDefType.NULL;
321         }
322
323         QuantityType<Temperature> temperature = new QuantityType<>(value, CELSIUS);
324
325         if (getDeviceTemperatureUnit() == FAHRENHEIT) {
326             QuantityType<Temperature> converted = temperature.toUnit(FAHRENHEIT);
327             return converted == null ? UnDefType.NULL : converted;
328         }
329
330         return temperature;
331     }
332
333     private Unit<Temperature> getDeviceTemperatureUnit() {
334         SDMDeviceSettingsTrait deviceSettings = device.traits.deviceSettings;
335         if (deviceSettings == null) {
336             return CELSIUS;
337         }
338
339         switch (deviceSettings.temperatureScale) {
340             case CELSIUS:
341                 return CELSIUS;
342             case FAHRENHEIT:
343                 return FAHRENHEIT;
344             default:
345                 return CELSIUS;
346         }
347     }
348
349     private BigDecimal toCelsiusBigDecimal(QuantityType<Temperature> temperature) {
350         QuantityType<Temperature> celsiusTemperature = temperature.toUnit(CELSIUS);
351         if (celsiusTemperature == null) {
352             throw new IllegalArgumentException(
353                     String.format("Temperature '%s' cannot be converted to Celsius unit", temperature));
354         }
355         return celsiusTemperature.toBigDecimal();
356     }
357 }