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.binding.deconz.internal.Util.buildUrl;
17 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
18 import static org.openhab.core.library.unit.SmartHomeUnits.PERCENT;
20 import java.math.BigDecimal;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.deconz.internal.dto.SensorConfig;
29 import org.openhab.binding.deconz.internal.dto.SensorState;
30 import org.openhab.binding.deconz.internal.dto.ThermostatConfig;
31 import org.openhab.binding.deconz.internal.netutils.AsyncHttpClient;
32 import org.openhab.binding.deconz.internal.types.ThermostatMode;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.Gson;
47 * This sensor Thermostat Thing doesn't establish any connections, that is done by the bridge Thing.
49 * It waits for the bridge to come online, grab the websocket connection and bridge configuration
50 * and registers to the websocket connection as a listener.
52 * A REST API call is made to get the initial sensor state.
54 * Only the Thermostat is supported by this Thing, because a unified state is kept
55 * in {@link #sensorState}. Every field that got received by the REST API for this specific
56 * sensor is published to the framework.
58 * @author Lukas Agethen - Initial contribution
61 public class SensorThermostatThingHandler extends SensorBaseThingHandler {
62 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_THERMOSTAT);
64 private static final List<String> CONFIG_CHANNELS = Arrays.asList(CHANNEL_BATTERY_LEVEL, CHANNEL_BATTERY_LOW,
65 CHANNEL_HEATSETPOINT, CHANNEL_TEMPERATURE_OFFSET, CHANNEL_THERMOSTAT_MODE);
67 private final Logger logger = LoggerFactory.getLogger(SensorThermostatThingHandler.class);
69 public SensorThermostatThingHandler(Thing thing, Gson gson) {
74 public void handleCommand(ChannelUID channelUID, Command command) {
75 if (command instanceof RefreshType) {
76 sensorState.buttonevent = null;
77 valueUpdated(channelUID.getId(), sensorState, false);
80 ThermostatConfig newConfig = new ThermostatConfig();
81 String channelId = channelUID.getId();
83 case CHANNEL_HEATSETPOINT:
84 Integer newHeatsetpoint = getTemperatureFromCommand(command);
85 if (newHeatsetpoint == null) {
86 logger.warn("Heatsetpoint must not be null.");
89 newConfig.heatsetpoint = newHeatsetpoint;
91 case CHANNEL_TEMPERATURE_OFFSET:
92 Integer newOffset = getTemperatureFromCommand(command);
93 if (newOffset == null) {
94 logger.warn("Offset must not be null.");
97 newConfig.offset = newOffset;
99 case CHANNEL_THERMOSTAT_MODE:
100 if (command instanceof StringType) {
101 String thermostatMode = ((StringType) command).toString();
103 newConfig.mode = ThermostatMode.valueOf(thermostatMode);
104 } catch (IllegalArgumentException ex) {
105 logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
106 ThermostatMode.values());
109 if (newConfig.mode == ThermostatMode.UNKNOWN) {
110 logger.warn("Invalid thermostat mode: {}. Valid values: {}", thermostatMode,
111 ThermostatMode.values());
119 // no supported command
124 AsyncHttpClient asyncHttpClient = http;
125 if (asyncHttpClient == null) {
128 String url = buildUrl(bridgeConfig.host, bridgeConfig.httpPort, bridgeConfig.apikey, "sensors", config.id,
131 String json = gson.toJson(newConfig);
132 logger.trace("Sending {} to sensor {} via {}", json, config.id, url);
133 asyncHttpClient.put(url, json, bridgeConfig.timeout).thenAccept(v -> {
134 String bodyContent = v.getBody();
135 logger.trace("Result code={}, body={}", v.getResponseCode(), bodyContent);
136 if (!bodyContent.contains("success")) {
137 logger.debug("Sending command {} to channel {} failed: {}", command, channelUID, bodyContent);
140 }).exceptionally(e -> {
141 logger.debug("Sending command {} to channel {} failed:", command, channelUID, e);
147 protected void valueUpdated(ChannelUID channelUID, SensorConfig newConfig) {
148 super.valueUpdated(channelUID, newConfig);
149 String mode = newConfig.mode != null ? newConfig.mode.name() : ThermostatMode.UNKNOWN.name();
150 String channelID = channelUID.getId();
152 case CHANNEL_HEATSETPOINT:
153 updateQuantityTypeChannel(channelID, newConfig.heatsetpoint, CELSIUS, 1.0 / 100);
155 case CHANNEL_TEMPERATURE_OFFSET:
156 updateQuantityTypeChannel(channelID, newConfig.offset, CELSIUS, 1.0 / 100);
158 case CHANNEL_THERMOSTAT_MODE:
160 updateState(channelUID, new StringType(mode));
167 protected void valueUpdated(String channelID, SensorState newState, boolean initializing) {
168 super.valueUpdated(channelID, newState, initializing);
170 case CHANNEL_TEMPERATURE:
171 updateQuantityTypeChannel(channelID, newState.temperature, CELSIUS, 1.0 / 100);
173 case CHANNEL_VALVE_POSITION:
174 updateQuantityTypeChannel(channelID, newState.valve, PERCENT, 100.0 / 255);
180 protected void createTypeSpecificChannels(SensorConfig sensorConfig, SensorState sensorState) {
184 protected List<String> getConfigChannels() {
185 return CONFIG_CHANNELS;
188 private @Nullable Integer getTemperatureFromCommand(Command command) {
189 BigDecimal newTemperature;
190 if (command instanceof DecimalType) {
191 newTemperature = ((DecimalType) command).toBigDecimal();
192 } else if (command instanceof QuantityType) {
193 newTemperature = ((QuantityType) command).toUnit(CELSIUS).toBigDecimal();
197 return newTemperature.scaleByPowerOfTen(2).intValue();