]> git.basschouten.com Git - openhab-addons.git/blob
20ce7d59ccb5c187e9b6c4874b7da55aaedea521
[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.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;
44
45 /**
46  * The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Mark Herwege - Initial Contribution
50  */
51 @NonNullByDefault
52 public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
53
54     private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
55
56     private volatile @Nullable NhcThermostat nhcThermostat;
57
58     private volatile boolean initialized = false;
59
60     private String thermostatId = "";
61     private int overruleTime;
62
63     private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
64                                                                 // minute
65
66     public NikoHomeControlThermostatHandler(Thing thing) {
67         super(thing);
68     }
69
70     @Override
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);
75             return;
76         }
77
78         // This can be expensive, therefore do it in a job.
79         scheduler.submit(() -> {
80             if (!nhcComm.communicationActive()) {
81                 restartCommunication(nhcComm);
82             }
83
84             if (nhcComm.communicationActive()) {
85                 handleCommandSelection(channelUID, command);
86             }
87         });
88     }
89
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);
94             return;
95         }
96
97         logger.debug("handle command {} for {}", command, channelUID);
98
99         if (REFRESH.equals(command)) {
100             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
101                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
102             return;
103         }
104
105         switch (channelUID.getId()) {
106             case CHANNEL_MEASURED:
107             case CHANNEL_DEMAND:
108                 updateStatus(ThingStatus.ONLINE);
109                 break;
110
111             case CHANNEL_MODE:
112                 if (command instanceof DecimalType) {
113                     nhcThermostat.executeMode(((DecimalType) command).intValue());
114                 }
115                 updateStatus(ThingStatus.ONLINE);
116                 break;
117
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();
125                     if (time <= 0) {
126                         time = overruleTime;
127                     }
128                     if (setpoint != null) {
129                         nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
130                     }
131                 }
132                 updateStatus(ThingStatus.ONLINE);
133                 break;
134
135             case CHANNEL_OVERRULETIME:
136                 if (command instanceof DecimalType) {
137                     int overruletime = ((DecimalType) command).intValue();
138                     int overrule = nhcThermostat.getOverrule();
139                     if (overruletime <= 0) {
140                         overruletime = 0;
141                         overrule = 0;
142                     }
143                     nhcThermostat.executeOverrule(overrule, overruletime);
144                 }
145                 updateStatus(ThingStatus.ONLINE);
146                 break;
147             default:
148                 logger.debug("unexpected command for channel {}", channelUID.getId());
149         }
150     }
151
152     @Override
153     public void initialize() {
154         initialized = false;
155
156         NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
157
158         thermostatId = config.thermostatId;
159         overruleTime = config.overruleTime;
160
161         NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
162         if (bridgeHandler == null) {
163             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
164                     "@text/offline.configuration-error.invalid-bridge-handler");
165             return;
166         }
167
168         updateStatus(ThingStatus.UNKNOWN);
169
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);
175         }
176     }
177
178     private synchronized void startCommunication() {
179         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
180
181         if (nhcComm == null) {
182             return;
183         }
184
185         if (!nhcComm.communicationActive()) {
186             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
187                     "@text/offline.communication-error");
188             return;
189         }
190
191         NhcThermostat nhcThermostat = nhcComm.getThermostats().get(thermostatId);
192         if (nhcThermostat == null) {
193             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
194                     "@text/offline.configuration-error.thermostatId");
195             return;
196         }
197
198         nhcThermostat.setEventHandler(this);
199
200         updateProperties(nhcThermostat);
201
202         String thermostatLocation = nhcThermostat.getLocation();
203         if (thing.getLocation() == null) {
204             thing.setLocation(thermostatLocation);
205         }
206
207         this.nhcThermostat = nhcThermostat;
208
209         logger.debug("thermostat intialized {}", thermostatId);
210
211         Bridge bridge = getBridge();
212         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
213             updateStatus(ThingStatus.ONLINE);
214         } else {
215             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
216         }
217
218         thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
219                 nhcThermostat.getOverrule(), nhcThermostat.getDemand());
220
221         initialized = true;
222     }
223
224     @Override
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();
231             }
232         }
233         nhcThermostat = null;
234         super.dispose();
235     }
236
237     private void updateProperties(NhcThermostat nhcThermostat) {
238         Map<String, String> properties = new HashMap<>();
239
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());
245         }
246
247         thing.setProperties(properties);
248     }
249
250     @Override
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);
255             return;
256         }
257
258         updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
259
260         int overruletime = nhcThermostat.getRemainingOverruletime();
261         updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
262         // refresh the remaining time every minute
263         scheduleRefreshOverruletime(nhcThermostat);
264
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));
269         } else {
270             updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
271         }
272
273         updateState(CHANNEL_MODE, new DecimalType(mode));
274
275         updateState(CHANNEL_DEMAND, new DecimalType(demand));
276
277         updateStatus(ThingStatus.ONLINE);
278     }
279
280     /**
281      * Method to update state of overruletime channel every minute with remaining time.
282      *
283      * @param NhcThermostat object
284      *
285      */
286     private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
287         cancelRefreshTimer();
288
289         if (nhcThermostat.getRemainingOverruletime() <= 0) {
290             return;
291         }
292
293         refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
294             int remainingTime = nhcThermostat.getRemainingOverruletime();
295             updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
296             if (remainingTime <= 0) {
297                 cancelRefreshTimer();
298             }
299         }, 1, 1, TimeUnit.MINUTES);
300     }
301
302     private void cancelRefreshTimer() {
303         ScheduledFuture<?> timer = refreshTimer;
304         if (timer != null) {
305             timer.cancel(true);
306         }
307         refreshTimer = null;
308     }
309
310     @Override
311     public void thermostatInitialized() {
312         Bridge bridge = getBridge();
313         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
314             updateStatus(ThingStatus.ONLINE);
315         }
316     }
317
318     @Override
319     public void thermostatRemoved() {
320         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
321                 "@text/offline.configuration-error.thermostatRemoved");
322     }
323
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");
332             return;
333         }
334         // Also put the bridge back online
335         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
336         if (nhcBridgeHandler != null) {
337             nhcBridgeHandler.bridgeOnline();
338         } else {
339             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
340                     "@text/offline.configuration-error.invalid-bridge-handler");
341         }
342     }
343
344     private @Nullable NikoHomeControlCommunication getCommunication(
345             @Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
346         return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
347     }
348
349     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
350         Bridge nhcBridge = getBridge();
351         return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
352     }
353
354     @Override
355     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
356         ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
357         if (ThingStatus.ONLINE.equals(bridgeStatus)) {
358             if (!initialized) {
359                 scheduler.submit(this::startCommunication);
360             } else {
361                 updateStatus(ThingStatus.ONLINE);
362             }
363         } else {
364             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
365         }
366     }
367 }