2 * Copyright (c) 2010-2021 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.nikohomecontrol.internal.handler;
15 import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
16 import static org.openhab.core.library.unit.SIUnits.CELSIUS;
17 import static org.openhab.core.types.RefreshType.REFRESH;
19 import java.util.HashMap;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import javax.measure.quantity.Temperature;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
29 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostatEvent;
30 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
31 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.QuantityType;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.types.Command;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
46 * sent to one of the channels.
48 * @author Mark Herwege - Initial Contribution
51 public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
53 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
55 private volatile @Nullable NhcThermostat nhcThermostat;
57 private String thermostatId = "";
58 private int overruleTime;
60 private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
63 public NikoHomeControlThermostatHandler(Thing thing) {
68 public void handleCommand(ChannelUID channelUID, Command command) {
69 NikoHomeControlCommunication nhcComm = getCommunication();
70 if (nhcComm == null) {
71 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
72 "Bridge communication not initialized when trying to execute thermostat command on "
77 // This can be expensive, therefore do it in a job.
78 scheduler.submit(() -> {
79 if (!nhcComm.communicationActive()) {
80 restartCommunication(nhcComm);
83 if (nhcComm.communicationActive()) {
84 handleCommandSelection(channelUID, command);
89 private void handleCommandSelection(ChannelUID channelUID, Command command) {
90 NhcThermostat nhcThermostat = this.nhcThermostat;
91 if (nhcThermostat == null) {
92 logger.debug("thermostat with ID {} not initialized", thermostatId);
96 logger.debug("handle command {} for {}", command, channelUID);
98 if (REFRESH.equals(command)) {
99 thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
100 nhcThermostat.getOverrule(), nhcThermostat.getDemand());
104 switch (channelUID.getId()) {
105 case CHANNEL_MEASURED:
107 updateStatus(ThingStatus.ONLINE);
111 if (command instanceof DecimalType) {
112 nhcThermostat.executeMode(((DecimalType) command).intValue());
114 updateStatus(ThingStatus.ONLINE);
117 case CHANNEL_SETPOINT:
118 QuantityType<Temperature> setpoint = null;
119 if (command instanceof QuantityType) {
120 setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
121 // Always set the new setpoint temperature as an overrule
122 // If no overrule time is given yet, set the overrule time to the configuration parameter
123 int time = nhcThermostat.getOverruletime();
127 if (setpoint != null) {
128 nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
131 updateStatus(ThingStatus.ONLINE);
134 case CHANNEL_OVERRULETIME:
135 if (command instanceof DecimalType) {
136 int overruletime = ((DecimalType) command).intValue();
137 int overrule = nhcThermostat.getOverrule();
138 if (overruletime <= 0) {
142 nhcThermostat.executeOverrule(overrule, overruletime);
144 updateStatus(ThingStatus.ONLINE);
148 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
149 "Channel unknown " + channelUID.getId());
154 public void initialize() {
155 NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
157 thermostatId = config.thermostatId;
158 overruleTime = config.overruleTime;
160 NikoHomeControlCommunication nhcComm = getCommunication();
161 if (nhcComm == null) {
162 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
163 "Connection with controller not started yet, could not initialize thermostat " + thermostatId);
167 // We need to do this in a separate thread because we may have to wait for the
168 // communication to become active
169 scheduler.submit(() -> {
170 if (!nhcComm.communicationActive()) {
171 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
172 "No connection with controller, could not initialize thermostat " + thermostatId);
176 NhcThermostat nhcThermostat = nhcComm.getThermostats().get(thermostatId);
177 if (nhcThermostat == null) {
178 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
179 "Thermostat " + thermostatId + " does not match a thermostat in the controller");
183 nhcThermostat.setEventHandler(this);
187 String thermostatLocation = nhcThermostat.getLocation();
188 if (thing.getLocation() == null) {
189 thing.setLocation(thermostatLocation);
192 thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
193 nhcThermostat.getOverrule(), nhcThermostat.getDemand());
195 this.nhcThermostat = nhcThermostat;
197 logger.debug("thermostat intialized {}", thermostatId);
199 Bridge bridge = getBridge();
200 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
201 updateStatus(ThingStatus.ONLINE);
203 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
208 private void updateProperties() {
209 Map<String, String> properties = new HashMap<>();
211 if (nhcThermostat instanceof NhcThermostat2) {
212 NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
213 properties.put("model", thermostat.getModel());
214 properties.put("technology", thermostat.getTechnology());
217 thing.setProperties(properties);
221 public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
222 NhcThermostat nhcThermostat = this.nhcThermostat;
223 if (nhcThermostat == null) {
224 logger.debug("thermostat with ID {} not initialized", thermostatId);
228 updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
230 int overruletime = nhcThermostat.getRemainingOverruletime();
231 updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
232 // refresh the remaining time every minute
233 scheduleRefreshOverruletime(nhcThermostat);
235 // If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
236 // setpoint temperature
237 if (overruletime == 0) {
238 updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
240 updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
243 updateState(CHANNEL_MODE, new DecimalType(mode));
245 updateState(CHANNEL_DEMAND, new DecimalType(demand));
247 updateStatus(ThingStatus.ONLINE);
251 * Method to update state of overruletime channel every minute with remaining time.
253 * @param NhcThermostat object
256 private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
257 cancelRefreshTimer();
259 if (nhcThermostat.getRemainingOverruletime() <= 0) {
263 refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
264 int remainingTime = nhcThermostat.getRemainingOverruletime();
265 updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
266 if (remainingTime <= 0) {
267 cancelRefreshTimer();
269 }, 1, 1, TimeUnit.MINUTES);
272 private void cancelRefreshTimer() {
273 ScheduledFuture<?> timer = refreshTimer;
281 public void thermostatInitialized() {
282 Bridge bridge = getBridge();
283 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
284 updateStatus(ThingStatus.ONLINE);
289 public void thermostatRemoved() {
290 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
291 "Thermostat " + thermostatId + " has been removed from the controller");
294 private void restartCommunication(NikoHomeControlCommunication nhcComm) {
295 // We lost connection but the connection object is there, so was correctly started.
296 // Try to restart communication.
297 nhcComm.restartCommunication();
298 // If still not active, take thing offline and return.
299 if (!nhcComm.communicationActive()) {
300 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication error");
303 // Also put the bridge back online
304 NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
305 if (nhcBridgeHandler != null) {
306 nhcBridgeHandler.bridgeOnline();
310 private @Nullable NikoHomeControlCommunication getCommunication() {
311 NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
312 if (nhcBridgeHandler == null) {
313 updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
314 "No bridge initialized for thermostat " + thermostatId);
317 NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
321 private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
322 Bridge nhcBridge = getBridge();
323 if (nhcBridge == null) {
324 updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
325 "No bridge initialized for thermostat " + thermostatId);
328 NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
329 return nhcBridgeHandler;