]> git.basschouten.com Git - openhab-addons.git/blob
0debc0e7a4c08e7037cd8648821e6f2bd3c08eb2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.BRIDGE_UNINITIALIZED,
72                     "@text/offline.bridge-unitialized");
73             return;
74         }
75
76         // This can be expensive, therefore do it in a job.
77         scheduler.submit(() -> {
78             if (!nhcComm.communicationActive()) {
79                 restartCommunication(nhcComm);
80             }
81
82             if (nhcComm.communicationActive()) {
83                 handleCommandSelection(channelUID, command);
84             }
85         });
86     }
87
88     private void handleCommandSelection(ChannelUID channelUID, Command command) {
89         NhcThermostat nhcThermostat = this.nhcThermostat;
90         if (nhcThermostat == null) {
91             logger.debug("thermostat with ID {} not initialized", thermostatId);
92             return;
93         }
94
95         logger.debug("handle command {} for {}", command, channelUID);
96
97         if (REFRESH.equals(command)) {
98             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
99                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
100             return;
101         }
102
103         switch (channelUID.getId()) {
104             case CHANNEL_MEASURED:
105             case CHANNEL_DEMAND:
106                 updateStatus(ThingStatus.ONLINE);
107                 break;
108
109             case CHANNEL_MODE:
110                 if (command instanceof DecimalType) {
111                     nhcThermostat.executeMode(((DecimalType) command).intValue());
112                 }
113                 updateStatus(ThingStatus.ONLINE);
114                 break;
115
116             case CHANNEL_SETPOINT:
117                 QuantityType<Temperature> setpoint = null;
118                 if (command instanceof QuantityType) {
119                     setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
120                     // Always set the new setpoint temperature as an overrule
121                     // If no overrule time is given yet, set the overrule time to the configuration parameter
122                     int time = nhcThermostat.getOverruletime();
123                     if (time <= 0) {
124                         time = overruleTime;
125                     }
126                     if (setpoint != null) {
127                         nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
128                     }
129                 }
130                 updateStatus(ThingStatus.ONLINE);
131                 break;
132
133             case CHANNEL_OVERRULETIME:
134                 if (command instanceof DecimalType) {
135                     int overruletime = ((DecimalType) command).intValue();
136                     int overrule = nhcThermostat.getOverrule();
137                     if (overruletime <= 0) {
138                         overruletime = 0;
139                         overrule = 0;
140                     }
141                     nhcThermostat.executeOverrule(overrule, overruletime);
142                 }
143                 updateStatus(ThingStatus.ONLINE);
144                 break;
145             default:
146                 logger.debug("unexpected command for channel {}", channelUID.getId());
147         }
148     }
149
150     @Override
151     public void initialize() {
152         NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
153
154         thermostatId = config.thermostatId;
155         overruleTime = config.overruleTime;
156
157         NikoHomeControlCommunication nhcComm = getCommunication();
158         if (nhcComm == null) {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
160                     "@text/offline.bridge-unitialized");
161             return;
162         } else {
163             updateStatus(ThingStatus.UNKNOWN);
164         }
165
166         // We need to do this in a separate thread because we may have to wait for the
167         // communication to become active
168         scheduler.submit(() -> {
169             if (!nhcComm.communicationActive()) {
170                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
171                         "@text/offline.communication-error");
172                 return;
173             }
174
175             NhcThermostat nhcThermostat = nhcComm.getThermostats().get(thermostatId);
176             if (nhcThermostat == null) {
177                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
178                         "@text/offline.configuration-error.thermostatId");
179                 return;
180             }
181
182             nhcThermostat.setEventHandler(this);
183
184             updateProperties(nhcThermostat);
185
186             String thermostatLocation = nhcThermostat.getLocation();
187             if (thing.getLocation() == null) {
188                 thing.setLocation(thermostatLocation);
189             }
190
191             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
192                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
193
194             this.nhcThermostat = nhcThermostat;
195
196             logger.debug("thermostat intialized {}", thermostatId);
197
198             Bridge bridge = getBridge();
199             if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
200                 updateStatus(ThingStatus.ONLINE);
201             } else {
202                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
203             }
204         });
205     }
206
207     private void updateProperties(NhcThermostat nhcThermostat) {
208         Map<String, String> properties = new HashMap<>();
209
210         if (nhcThermostat instanceof NhcThermostat2) {
211             NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
212             properties.put(PROPERTY_DEVICE_TYPE, thermostat.getDeviceType());
213             properties.put(PROPERTY_DEVICE_TECHNOLOGY, thermostat.getDeviceTechnology());
214             properties.put(PROPERTY_DEVICE_MODEL, thermostat.getDeviceModel());
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                 "@text/offline.configuration-error.thermostatRemoved");
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,
301                     "@text/offline.communication-error");
302             return;
303         }
304         // Also put the bridge back online
305         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
306         if (nhcBridgeHandler != null) {
307             nhcBridgeHandler.bridgeOnline();
308         } else {
309             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
310                     "@text/offline.bridge-unitialized");
311         }
312     }
313
314     private @Nullable NikoHomeControlCommunication getCommunication() {
315         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
316         return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
317     }
318
319     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
320         Bridge nhcBridge = getBridge();
321         return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
322     }
323 }