2 * Copyright (c) 2010-2022 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.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Mark Herwege - Initial Contribution
52 public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
54 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
56 private volatile @Nullable NhcThermostat nhcThermostat;
58 private volatile boolean initialized = false;
60 private String thermostatId = "";
61 private int overruleTime;
63 private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
66 public NikoHomeControlThermostatHandler(Thing thing) {
71 public void handleCommand(ChannelUID channelUID, Command command) {
72 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
73 if (nhcComm == null) {
74 logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
78 // This can be expensive, therefore do it in a job.
79 scheduler.submit(() -> {
80 if (!nhcComm.communicationActive()) {
81 restartCommunication(nhcComm);
84 if (nhcComm.communicationActive()) {
85 handleCommandSelection(channelUID, command);
90 private void handleCommandSelection(ChannelUID channelUID, Command command) {
91 NhcThermostat nhcThermostat = this.nhcThermostat;
92 if (nhcThermostat == null) {
93 logger.debug("thermostat with ID {} not initialized", thermostatId);
97 logger.debug("handle command {} for {}", command, channelUID);
99 if (REFRESH.equals(command)) {
100 thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
101 nhcThermostat.getOverrule(), nhcThermostat.getDemand());
105 switch (channelUID.getId()) {
106 case CHANNEL_MEASURED:
108 updateStatus(ThingStatus.ONLINE);
112 if (command instanceof DecimalType) {
113 nhcThermostat.executeMode(((DecimalType) command).intValue());
115 updateStatus(ThingStatus.ONLINE);
118 case CHANNEL_SETPOINT:
119 QuantityType<Temperature> setpoint = null;
120 if (command instanceof QuantityType) {
121 setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
122 // Always set the new setpoint temperature as an overrule
123 // If no overrule time is given yet, set the overrule time to the configuration parameter
124 int time = nhcThermostat.getOverruletime();
128 if (setpoint != null) {
129 nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
132 updateStatus(ThingStatus.ONLINE);
135 case CHANNEL_OVERRULETIME:
136 if (command instanceof DecimalType) {
137 int overruletime = ((DecimalType) command).intValue();
138 int overrule = nhcThermostat.getOverrule();
139 if (overruletime <= 0) {
143 nhcThermostat.executeOverrule(overrule, overruletime);
145 updateStatus(ThingStatus.ONLINE);
148 logger.debug("unexpected command for channel {}", channelUID.getId());
153 public void initialize() {
156 NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
158 thermostatId = config.thermostatId;
159 overruleTime = config.overruleTime;
161 NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
162 if (bridgeHandler == null) {
163 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
164 "@text/offline.configuration-error.invalid-bridge-handler");
168 updateStatus(ThingStatus.UNKNOWN);
170 Bridge bridge = getBridge();
171 if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
172 // We need to do this in a separate thread because we may have to wait for the
173 // communication to become active
174 scheduler.submit(this::startCommunication);
178 private synchronized void startCommunication() {
179 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
181 if (nhcComm == null) {
185 if (!nhcComm.communicationActive()) {
186 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
187 "@text/offline.communication-error");
191 NhcThermostat nhcThermostat = nhcComm.getThermostats().get(thermostatId);
192 if (nhcThermostat == null) {
193 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
194 "@text/offline.configuration-error.thermostatId");
198 nhcThermostat.setEventHandler(this);
200 updateProperties(nhcThermostat);
202 String thermostatLocation = nhcThermostat.getLocation();
203 if (thing.getLocation() == null) {
204 thing.setLocation(thermostatLocation);
207 this.nhcThermostat = nhcThermostat;
209 logger.debug("thermostat intialized {}", thermostatId);
211 Bridge bridge = getBridge();
212 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
213 updateStatus(ThingStatus.ONLINE);
215 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
218 thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
219 nhcThermostat.getOverrule(), nhcThermostat.getDemand());
225 public void dispose() {
226 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
227 if (nhcComm != null) {
228 NhcThermostat thermostat = nhcComm.getThermostats().get(thermostatId);
229 if (thermostat != null) {
230 thermostat.unsetEventHandler();
233 nhcThermostat = null;
237 private void updateProperties(NhcThermostat nhcThermostat) {
238 Map<String, String> properties = new HashMap<>();
240 if (nhcThermostat instanceof NhcThermostat2) {
241 NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
242 properties.put(PROPERTY_DEVICE_TYPE, thermostat.getDeviceType());
243 properties.put(PROPERTY_DEVICE_TECHNOLOGY, thermostat.getDeviceTechnology());
244 properties.put(PROPERTY_DEVICE_MODEL, thermostat.getDeviceModel());
247 thing.setProperties(properties);
251 public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
252 NhcThermostat nhcThermostat = this.nhcThermostat;
253 if (nhcThermostat == null) {
254 logger.debug("thermostat with ID {} not initialized", thermostatId);
258 updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
260 int overruletime = nhcThermostat.getRemainingOverruletime();
261 updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
262 // refresh the remaining time every minute
263 scheduleRefreshOverruletime(nhcThermostat);
265 // If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
266 // setpoint temperature
267 if (overruletime == 0) {
268 updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
270 updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
273 updateState(CHANNEL_MODE, new DecimalType(mode));
275 updateState(CHANNEL_DEMAND, new DecimalType(demand));
277 updateStatus(ThingStatus.ONLINE);
281 * Method to update state of overruletime channel every minute with remaining time.
283 * @param NhcThermostat object
286 private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
287 cancelRefreshTimer();
289 if (nhcThermostat.getRemainingOverruletime() <= 0) {
293 refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
294 int remainingTime = nhcThermostat.getRemainingOverruletime();
295 updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
296 if (remainingTime <= 0) {
297 cancelRefreshTimer();
299 }, 1, 1, TimeUnit.MINUTES);
302 private void cancelRefreshTimer() {
303 ScheduledFuture<?> timer = refreshTimer;
311 public void thermostatInitialized() {
312 Bridge bridge = getBridge();
313 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
314 updateStatus(ThingStatus.ONLINE);
319 public void thermostatRemoved() {
320 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
321 "@text/offline.configuration-error.thermostatRemoved");
324 private void restartCommunication(NikoHomeControlCommunication nhcComm) {
325 // We lost connection but the connection object is there, so was correctly started.
326 // Try to restart communication.
327 nhcComm.scheduleRestartCommunication();
328 // If still not active, take thing offline and return.
329 if (!nhcComm.communicationActive()) {
330 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
331 "@text/offline.communication-error");
334 // Also put the bridge back online
335 NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
336 if (nhcBridgeHandler != null) {
337 nhcBridgeHandler.bridgeOnline();
339 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
340 "@text/offline.configuration-error.invalid-bridge-handler");
344 private @Nullable NikoHomeControlCommunication getCommunication(
345 @Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
346 return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
349 private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
350 Bridge nhcBridge = getBridge();
351 return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
355 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
356 ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
357 if (ThingStatus.ONLINE.equals(bridgeStatus)) {
359 scheduler.submit(this::startCommunication);
361 updateStatus(ThingStatus.ONLINE);
364 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);