2 * Copyright (c) 2010-2020 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.deconz.internal.handler;
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;
19 import java.math.BigDecimal;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.List;
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.deconz.internal.dto.*;
30 import org.openhab.binding.deconz.internal.types.ThermostatMode;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.OpenClosedType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.binding.builder.ChannelBuilder;
39 import org.openhab.core.thing.binding.builder.ThingBuilder;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 import com.google.gson.Gson;
49 * This sensor Thermostat Thing doesn't establish any connections, that is done by the bridge Thing.
51 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
52 * and registers to the websocket connection as a listener.
54 * A REST API call is made to get the initial sensor state.
56 * Only the Thermostat is supported by this Thing, because a unified state is kept
57 * in {@link #sensorState}. Every field that got received by the REST API for this specific
58 * sensor is published to the framework.
60 * @author Lukas Agethen - Initial contribution
63 public class SensorThermostatThingHandler extends SensorBaseThingHandler {
64 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_THERMOSTAT);
66 private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
67 CHANNEL_HEATSETPOINT, CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE);
69 private final Logger logger = LoggerFactory.getLogger(SensorThermostatThingHandler.class);
71 public SensorThermostatThingHandler(Thing thing, Gson gson) {
76 public void handleCommand(ChannelUID channelUID, Command command) {
77 if (command instanceof RefreshType) {
78 sensorState.buttonevent = null;
79 valueUpdated(channelUID.getId(), sensorState, false);
82 ThermostatConfig newConfig = new ThermostatConfig();
83 String channelId = channelUID.getId();
85 case CHANNEL_HEATSETPOINT:
86 Integer newHeatsetpoint = getTemperatureFromCommand(command);
87 if (newHeatsetpoint == null) {
88 logger.warn("Heatsetpoint must not be null.");
91 newConfig.heatsetpoint = newHeatsetpoint;
93 case CHANNEL_TEMPERATURE_OFFSET:
94 Integer newOffset = getTemperatureFromCommand(command);
95 if (newOffset == null) {
96 logger.warn("Offset must not be null.");
99 newConfig.offset = newOffset;
101 case CHANNEL_THERMOSTAT_MODE:
102 if (command instanceof StringType) {
103 String thermostatMode = ((StringType) command).toString();
105 newConfig.mode = ThermostatMode.valueOf(thermostatMode);
106 } catch (IllegalArgumentException ex) {
107 logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
108 ThermostatMode.values());
111 if (newConfig.mode == ThermostatMode.UNKNOWN) {
112 logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
113 ThermostatMode.values());
121 // no supported command
126 sendCommand(newConfig, command, channelUID, null);
130 protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
131 super.valueUpdated(channelUID, newConfig);
132 ThermostatMode thermostatMode = newConfig.mode;
133 String mode = thermostatMode != null ? thermostatMode.name() : ThermostatMode.UNKNOWN.name();
134 String channelID = channelUID.getId();
136 case CHANNEL_HEATSETPOINT:
137 updateQuantityTypeChannel(channelID, newConfig.heatsetpoint, CELSIUS, 1.0 / 100);
139 case CHANNEL_TEMPERATURE_OFFSET:
140 updateQuantityTypeChannel(channelID, newConfig.offset, CELSIUS, 1.0 / 100);
142 case CHANNEL_THERMOSTAT_MODE:
143 updateState(channelUID, new StringType(mode));
149 protected void valueUpdated(String channelID, SensorState newState, boolean initializing) {
150 super.valueUpdated(channelID, newState, initializing);
152 case CHANNEL_TEMPERATURE:
153 updateQuantityTypeChannel(channelID, newState.temperature, CELSIUS, 1.0 / 100);
155 case CHANNEL_VALVE_POSITION:
156 updateQuantityTypeChannel(channelID, newState.valve, PERCENT, 100.0 / 255);
158 case CHANNEL_WINDOWOPEN:
159 String open = newState.windowopen;
161 updateState(channelID, "Closed".equals(open) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
168 protected void createTypeSpecificChannels(SensorConfig sensorConfig, SensorState sensorState) {
172 protected List<String> getConfigChannels() {
173 return CONFIG_CHANNELS;
176 private @Nullable Integer getTemperatureFromCommand(Command command) {
177 BigDecimal newTemperature;
178 if (command instanceof DecimalType) {
179 newTemperature = ((DecimalType) command).toBigDecimal();
180 } else if (command instanceof QuantityType) {
181 @SuppressWarnings("unchecked")
182 QuantityType<Temperature> temperatureCelsius = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
183 if (temperatureCelsius != null) {
184 newTemperature = temperatureCelsius.toBigDecimal();
191 return newTemperature.scaleByPowerOfTen(2).intValue();
195 protected void processStateResponse(DeconzBaseMessage stateResponse) {
196 if (!(stateResponse instanceof SensorMessage)) {
200 SensorMessage sensorMessage = (SensorMessage) stateResponse;
201 if (sensorMessage.state.windowopen != null && thing.getChannel(CHANNEL_WINDOWOPEN) == null) {
202 ThingBuilder thingBuilder = editThing();
203 thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), CHANNEL_WINDOWOPEN), "String")
204 .withType(new ChannelTypeUID(BINDING_ID, "open")).build());
205 updateThing(thingBuilder.build());
208 super.processStateResponse(stateResponse);