2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.nest.internal.sdm.handler;
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;
20 import java.math.BigDecimal;
21 import java.time.Duration;
22 import java.time.ZonedDateTime;
24 import javax.measure.Unit;
25 import javax.measure.quantity.Temperature;
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;
68 * The {@link SDMThermostatHandler} handles state updates and commands for SDM thermostat devices.
70 * @author Brian Higginbotham - Initial contribution
71 * @author Wouter Born - Initial contribution
74 public class SDMThermostatHandler extends SDMBaseHandler {
76 private final Logger logger = LoggerFactory.getLogger(SDMThermostatHandler.class);
78 public SDMThermostatHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
79 super(thing, timeZoneProvider);
82 @SuppressWarnings("unchecked")
84 public void handleCommand(ChannelUID channelUID, Command command) {
86 if (command instanceof RefreshType) {
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));
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));
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()));
105 executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.OFF));
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));
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);
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));
134 } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
135 if (command instanceof QuantityType) {
136 setTargetTemperature((QuantityType<Temperature>) command);
140 } catch (FailedSendingSDMDataException | InvalidSDMAccessTokenException e) {
141 logger.debug("Exception while handling {} command for {}: {}", command, thing.getUID(), e.getMessage());
146 protected void updateStateWithTraits(SDMTraits traits) {
147 logger.debug("Refreshing channels for: {}", thing.getUID());
148 super.updateStateWithTraits(traits);
150 SDMHumidityTrait humidity = traits.humidity;
151 if (humidity != null) {
152 updateState(CHANNEL_AMBIENT_HUMIDITY, new QuantityType<>(humidity.ambientHumidityPercent, PERCENT));
155 SDMTemperatureTrait temperature = traits.temperature;
156 if (temperature != null) {
157 updateState(CHANNEL_AMBIENT_TEMPERATURE, temperatureToState(temperature.ambientTemperatureCelsius));
160 SDMThermostatModeTrait thermostatMode = traits.thermostatMode;
161 if (thermostatMode != null) {
162 updateState(CHANNEL_CURRENT_MODE, new StringType(thermostatMode.mode.name()));
165 SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
166 if (thermostatEco != null) {
167 updateState(CHANNEL_CURRENT_ECO_MODE, new StringType(thermostatEco.mode.name()));
170 SDMFanTrait fan = traits.fan;
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())));
177 SDMThermostatHvacTrait thermostatHvac = traits.thermostatHvac;
178 if (thermostatHvac != null) {
179 updateState(CHANNEL_HVAC_STATUS, new StringType(thermostatHvac.status.name()));
182 BigDecimal maxTemperature = getMaxTemperature();
183 if (maxTemperature != null) {
184 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(getMaxTemperature()));
187 BigDecimal minTemperature = getMinTemperature();
188 if (minTemperature != null) {
189 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(minTemperature));
192 BigDecimal targetTemperature = getTargetTemperature();
193 if (targetTemperature != null) {
194 updateState(CHANNEL_TARGET_TEMPERATURE, temperatureToState(targetTemperature));
198 private Duration getFanTimerDuration() {
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();
210 return Duration.ofSeconds(seconds);
213 private @Nullable BigDecimal getMinTemperature() {
214 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
215 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
216 return thermostatEco.heatCelsius;
219 SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
220 SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
221 if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
222 return thermostatTemperatureSetpoint.heatCelsius;
228 private @Nullable BigDecimal getMaxTemperature() {
229 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
230 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
231 return thermostatEco.coolCelsius;
234 SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
235 SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
236 if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
237 return thermostatTemperatureSetpoint.coolCelsius;
243 private @Nullable BigDecimal getTargetTemperature() {
244 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
245 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
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;
255 if (thermostatMode.mode == SDMThermostatMode.HEAT) {
256 return thermostatTemperatureSetpoint.heatCelsius;
264 public void onEvent(SDMEvent event) {
265 super.onEvent(event);
267 SDMTraits traits = getTraitsForUpdate(event);
268 if (traits == null) {
272 updateStateWithTraits(traits);
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));
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));
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)));
302 throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
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)));
314 throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
318 protected State temperatureToState(@Nullable BigDecimal value) {
320 return UnDefType.NULL;
323 QuantityType<Temperature> temperature = new QuantityType<>(value, CELSIUS);
325 if (getDeviceTemperatureUnit() == FAHRENHEIT) {
326 QuantityType<Temperature> converted = temperature.toUnit(FAHRENHEIT);
327 return converted == null ? UnDefType.NULL : converted;
333 private Unit<Temperature> getDeviceTemperatureUnit() {
334 SDMDeviceSettingsTrait deviceSettings = device.traits.deviceSettings;
335 if (deviceSettings == null) {
339 switch (deviceSettings.temperatureScale) {
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));
355 return celsiusTemperature.toBigDecimal();