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 onOffCommand) {
102 if (onOffCommand == 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 dateTimeCommand) {
111 Duration duration = Duration.between(ZonedDateTime.now(), dateTimeCommand.getZonedDateTime());
112 executeDeviceCommand(new SDMSetFanTimerRequest(SDMFanTimerMode.ON, duration));
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);
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));
133 } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
134 if (command instanceof QuantityType) {
135 setTargetTemperature((QuantityType<Temperature>) command);
139 } catch (FailedSendingSDMDataException | InvalidSDMAccessTokenException e) {
140 logger.debug("Exception while handling {} command for {}: {}", command, thing.getUID(), e.getMessage());
145 protected void updateStateWithTraits(SDMTraits traits) {
146 logger.debug("Refreshing channels for: {}", thing.getUID());
147 super.updateStateWithTraits(traits);
149 SDMHumidityTrait humidity = traits.humidity;
150 if (humidity != null) {
151 updateState(CHANNEL_AMBIENT_HUMIDITY, new QuantityType<>(humidity.ambientHumidityPercent, PERCENT));
154 SDMTemperatureTrait temperature = traits.temperature;
155 if (temperature != null) {
156 updateState(CHANNEL_AMBIENT_TEMPERATURE, temperatureToState(temperature.ambientTemperatureCelsius));
159 SDMThermostatModeTrait thermostatMode = traits.thermostatMode;
160 if (thermostatMode != null) {
161 updateState(CHANNEL_CURRENT_MODE, new StringType(thermostatMode.mode.name()));
164 SDMThermostatEcoTrait thermostatEco = traits.thermostatEco;
165 if (thermostatEco != null) {
166 updateState(CHANNEL_CURRENT_ECO_MODE, new StringType(thermostatEco.mode.name()));
169 SDMFanTrait fan = traits.fan;
171 updateState(CHANNEL_FAN_TIMER_MODE, fan.timerMode == SDMFanTimerMode.ON ? OnOffType.ON : OnOffType.OFF);
172 updateState(CHANNEL_FAN_TIMER_TIMEOUT, fan.timerTimeout == null ? UnDefType.NULL
173 : new DateTimeType(fan.timerTimeout.withZoneSameInstant(timeZoneProvider.getTimeZone())));
176 SDMThermostatHvacTrait thermostatHvac = traits.thermostatHvac;
177 if (thermostatHvac != null) {
178 updateState(CHANNEL_HVAC_STATUS, new StringType(thermostatHvac.status.name()));
181 BigDecimal maxTemperature = getMaxTemperature();
182 if (maxTemperature != null) {
183 updateState(CHANNEL_MAXIMUM_TEMPERATURE, temperatureToState(getMaxTemperature()));
186 BigDecimal minTemperature = getMinTemperature();
187 if (minTemperature != null) {
188 updateState(CHANNEL_MINIMUM_TEMPERATURE, temperatureToState(minTemperature));
191 BigDecimal targetTemperature = getTargetTemperature();
192 if (targetTemperature != null) {
193 updateState(CHANNEL_TARGET_TEMPERATURE, temperatureToState(targetTemperature));
197 private Duration getFanTimerDuration() {
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();
209 return Duration.ofSeconds(seconds);
212 private @Nullable BigDecimal getMinTemperature() {
213 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
214 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
215 return thermostatEco.heatCelsius;
218 SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
219 SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
220 if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
221 return thermostatTemperatureSetpoint.heatCelsius;
227 private @Nullable BigDecimal getMaxTemperature() {
228 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
229 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
230 return thermostatEco.coolCelsius;
233 SDMThermostatTemperatureSetpointTrait thermostatTemperatureSetpoint = device.traits.thermostatTemperatureSetpoint;
234 SDMThermostatModeTrait thermostatMode = device.traits.thermostatMode;
235 if (thermostatMode != null && thermostatMode.mode == SDMThermostatMode.HEATCOOL) {
236 return thermostatTemperatureSetpoint.coolCelsius;
242 private @Nullable BigDecimal getTargetTemperature() {
243 SDMThermostatEcoTrait thermostatEco = device.traits.thermostatEco;
244 if (thermostatEco != null && thermostatEco.mode == SDMThermostatEcoMode.MANUAL_ECO) {
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;
254 if (thermostatMode.mode == SDMThermostatMode.HEAT) {
255 return thermostatTemperatureSetpoint.heatCelsius;
263 public void onEvent(SDMEvent event) {
264 super.onEvent(event);
266 SDMTraits traits = getTraitsForUpdate(event);
267 if (traits == null) {
271 updateStateWithTraits(traits);
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));
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));
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)));
301 throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
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)));
313 throw new IllegalStateException("INVALID use case for setThermostatTargetTemperature");
317 protected State temperatureToState(@Nullable BigDecimal value) {
319 return UnDefType.NULL;
322 QuantityType<Temperature> temperature = new QuantityType<>(value, CELSIUS);
324 if (getDeviceTemperatureUnit() == FAHRENHEIT) {
325 QuantityType<Temperature> converted = temperature.toUnit(FAHRENHEIT);
326 return converted == null ? UnDefType.NULL : converted;
332 private Unit<Temperature> getDeviceTemperatureUnit() {
333 SDMDeviceSettingsTrait deviceSettings = device.traits.deviceSettings;
334 if (deviceSettings == null) {
338 switch (deviceSettings.temperatureScale) {
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));
354 return celsiusTemperature.toBigDecimal();