]> git.basschouten.com Git - openhab-addons.git/blob
4b71fa2760c9556a54a74f2ecb5d5789c9db694a
[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.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();
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() {
208         Map<String, String> properties = new HashMap<>();
209
210         if (nhcThermostat instanceof NhcThermostat2) {
211             NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
212             properties.put("model", thermostat.getModel());
213             properties.put("technology", thermostat.getTechnology());
214         }
215
216         thing.setProperties(properties);
217     }
218
219     @Override
220     public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
221         NhcThermostat nhcThermostat = this.nhcThermostat;
222         if (nhcThermostat == null) {
223             logger.debug("thermostat with ID {} not initialized", thermostatId);
224             return;
225         }
226
227         updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
228
229         int overruletime = nhcThermostat.getRemainingOverruletime();
230         updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
231         // refresh the remaining time every minute
232         scheduleRefreshOverruletime(nhcThermostat);
233
234         // If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
235         // setpoint temperature
236         if (overruletime == 0) {
237             updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
238         } else {
239             updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
240         }
241
242         updateState(CHANNEL_MODE, new DecimalType(mode));
243
244         updateState(CHANNEL_DEMAND, new DecimalType(demand));
245
246         updateStatus(ThingStatus.ONLINE);
247     }
248
249     /**
250      * Method to update state of overruletime channel every minute with remaining time.
251      *
252      * @param NhcThermostat object
253      *
254      */
255     private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
256         cancelRefreshTimer();
257
258         if (nhcThermostat.getRemainingOverruletime() <= 0) {
259             return;
260         }
261
262         refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
263             int remainingTime = nhcThermostat.getRemainingOverruletime();
264             updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
265             if (remainingTime <= 0) {
266                 cancelRefreshTimer();
267             }
268         }, 1, 1, TimeUnit.MINUTES);
269     }
270
271     private void cancelRefreshTimer() {
272         ScheduledFuture<?> timer = refreshTimer;
273         if (timer != null) {
274             timer.cancel(true);
275         }
276         refreshTimer = null;
277     }
278
279     @Override
280     public void thermostatInitialized() {
281         Bridge bridge = getBridge();
282         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
283             updateStatus(ThingStatus.ONLINE);
284         }
285     }
286
287     @Override
288     public void thermostatRemoved() {
289         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
290                 "@text/offline.configuration-error.thermostatRemoved");
291     }
292
293     private void restartCommunication(NikoHomeControlCommunication nhcComm) {
294         // We lost connection but the connection object is there, so was correctly started.
295         // Try to restart communication.
296         nhcComm.restartCommunication();
297         // If still not active, take thing offline and return.
298         if (!nhcComm.communicationActive()) {
299             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
300                     "@text/offline.communication-error");
301             return;
302         }
303         // Also put the bridge back online
304         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
305         if (nhcBridgeHandler != null) {
306             nhcBridgeHandler.bridgeOnline();
307         } else {
308             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
309                     "@text/offline.bridge-unitialized");
310         }
311     }
312
313     private @Nullable NikoHomeControlCommunication getCommunication() {
314         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
315         return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
316     }
317
318     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
319         Bridge nhcBridge = getBridge();
320         return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
321     }
322 }