]> git.basschouten.com Git - openhab-addons.git/blob
d852f07ade143e8ae54fef9f15b6265c8697851e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.nikohomecontrol.internal.handler;
14
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;
18
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import javax.measure.quantity.Temperature;
25
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;
43
44 /**
45  * The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Mark Herwege - Initial Contribution
49  */
50 @NonNullByDefault
51 public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
52
53     private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
54
55     private volatile @Nullable NhcThermostat nhcThermostat;
56
57     private String thermostatId = "";
58     private int overruleTime;
59
60     private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
61                                                                 // minute
62
63     public NikoHomeControlThermostatHandler(Thing thing) {
64         super(thing);
65     }
66
67     @Override
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 "
73                             + thermostatId);
74             return;
75         }
76
77         // This can be expensive, therefore do it in a job.
78         scheduler.submit(() -> {
79             if (!nhcComm.communicationActive()) {
80                 restartCommunication(nhcComm);
81             }
82
83             if (nhcComm.communicationActive()) {
84                 handleCommandSelection(channelUID, command);
85             }
86         });
87     }
88
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);
93             return;
94         }
95
96         logger.debug("handle command {} for {}", command, channelUID);
97
98         if (REFRESH.equals(command)) {
99             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
100                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
101             return;
102         }
103
104         switch (channelUID.getId()) {
105             case CHANNEL_MEASURED:
106             case CHANNEL_DEMAND:
107                 updateStatus(ThingStatus.ONLINE);
108                 break;
109
110             case CHANNEL_MODE:
111                 if (command instanceof DecimalType) {
112                     nhcThermostat.executeMode(((DecimalType) command).intValue());
113                 }
114                 updateStatus(ThingStatus.ONLINE);
115                 break;
116
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();
124                     if (time <= 0) {
125                         time = overruleTime;
126                     }
127                     if (setpoint != null) {
128                         nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
129                     }
130                 }
131                 updateStatus(ThingStatus.ONLINE);
132                 break;
133
134             case CHANNEL_OVERRULETIME:
135                 if (command instanceof DecimalType) {
136                     int overruletime = ((DecimalType) command).intValue();
137                     int overrule = nhcThermostat.getOverrule();
138                     if (overruletime <= 0) {
139                         overruletime = 0;
140                         overrule = 0;
141                     }
142                     nhcThermostat.executeOverrule(overrule, overruletime);
143                 }
144                 updateStatus(ThingStatus.ONLINE);
145                 break;
146
147             default:
148                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
149                         "Channel unknown " + channelUID.getId());
150         }
151     }
152
153     @Override
154     public void initialize() {
155         NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
156
157         thermostatId = config.thermostatId;
158         overruleTime = config.overruleTime;
159
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);
164             return;
165         }
166
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);
173                 return;
174             }
175
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");
180                 return;
181             }
182
183             nhcThermostat.setEventHandler(this);
184
185             updateProperties();
186
187             String thermostatLocation = nhcThermostat.getLocation();
188             if (thing.getLocation() == null) {
189                 thing.setLocation(thermostatLocation);
190             }
191
192             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
193                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
194
195             this.nhcThermostat = nhcThermostat;
196
197             logger.debug("thermostat intialized {}", thermostatId);
198
199             Bridge bridge = getBridge();
200             if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
201                 updateStatus(ThingStatus.ONLINE);
202             } else {
203                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
204             }
205         });
206     }
207
208     private void updateProperties() {
209         Map<String, String> properties = new HashMap<>();
210
211         if (nhcThermostat instanceof NhcThermostat2) {
212             NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
213             properties.put("model", thermostat.getModel());
214             properties.put("technology", thermostat.getTechnology());
215         }
216
217         thing.setProperties(properties);
218     }
219
220     @Override
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);
225             return;
226         }
227
228         updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
229
230         int overruletime = nhcThermostat.getRemainingOverruletime();
231         updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
232         // refresh the remaining time every minute
233         scheduleRefreshOverruletime(nhcThermostat);
234
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));
239         } else {
240             updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
241         }
242
243         updateState(CHANNEL_MODE, new DecimalType(mode));
244
245         updateState(CHANNEL_DEMAND, new DecimalType(demand));
246
247         updateStatus(ThingStatus.ONLINE);
248     }
249
250     /**
251      * Method to update state of overruletime channel every minute with remaining time.
252      *
253      * @param NhcThermostat object
254      *
255      */
256     private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
257         cancelRefreshTimer();
258
259         if (nhcThermostat.getRemainingOverruletime() <= 0) {
260             return;
261         }
262
263         refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
264             int remainingTime = nhcThermostat.getRemainingOverruletime();
265             updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
266             if (remainingTime <= 0) {
267                 cancelRefreshTimer();
268             }
269         }, 1, 1, TimeUnit.MINUTES);
270     }
271
272     private void cancelRefreshTimer() {
273         ScheduledFuture<?> timer = refreshTimer;
274         if (timer != null) {
275             timer.cancel(true);
276         }
277         refreshTimer = null;
278     }
279
280     @Override
281     public void thermostatInitialized() {
282         Bridge bridge = getBridge();
283         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
284             updateStatus(ThingStatus.ONLINE);
285         }
286     }
287
288     @Override
289     public void thermostatRemoved() {
290         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
291                 "Thermostat " + thermostatId + " has been removed from the controller");
292     }
293
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");
301             return;
302         }
303         // Also put the bridge back online
304         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
305         if (nhcBridgeHandler != null) {
306             nhcBridgeHandler.bridgeOnline();
307         }
308     }
309
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);
315             return null;
316         }
317         NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
318         return nhcComm;
319     }
320
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);
326             return null;
327         }
328         NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
329         return nhcBridgeHandler;
330     }
331 }