]> git.basschouten.com Git - openhab-addons.git/blob
12da67d0e763be529d0a65d3fed09c1a23f2d666
[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 @NonNullByDefault({}) 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                     "Niko Home Control: bridge communication not initialized when trying to execute thermostat command "
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         logger.debug("Niko Home Control: handle command {} for {}", command, channelUID);
91
92         if (REFRESH.equals(command)) {
93             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
94                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
95             return;
96         }
97
98         switch (channelUID.getId()) {
99             case CHANNEL_MEASURED:
100             case CHANNEL_DEMAND:
101                 updateStatus(ThingStatus.ONLINE);
102                 break;
103
104             case CHANNEL_MODE:
105                 if (command instanceof DecimalType) {
106                     nhcThermostat.executeMode(((DecimalType) command).intValue());
107                 }
108                 updateStatus(ThingStatus.ONLINE);
109                 break;
110
111             case CHANNEL_SETPOINT:
112                 QuantityType<Temperature> setpoint = null;
113                 if (command instanceof QuantityType) {
114                     setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
115                     // Always set the new setpoint temperature as an overrule
116                     // If no overrule time is given yet, set the overrule time to the configuration parameter
117                     int time = nhcThermostat.getOverruletime();
118                     if (time <= 0) {
119                         time = overruleTime;
120                     }
121                     if (setpoint != null) {
122                         nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
123                     }
124                 }
125                 updateStatus(ThingStatus.ONLINE);
126                 break;
127
128             case CHANNEL_OVERRULETIME:
129                 if (command instanceof DecimalType) {
130                     int overruletime = ((DecimalType) command).intValue();
131                     int overrule = nhcThermostat.getOverrule();
132                     if (overruletime <= 0) {
133                         overruletime = 0;
134                         overrule = 0;
135                     }
136                     nhcThermostat.executeOverrule(overrule, overruletime);
137                 }
138                 updateStatus(ThingStatus.ONLINE);
139                 break;
140
141             default:
142                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
143                         "Niko Home Control: channel unknown " + channelUID.getId());
144         }
145     }
146
147     @Override
148     public void initialize() {
149         NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
150
151         thermostatId = config.thermostatId;
152         overruleTime = config.overruleTime;
153
154         NikoHomeControlCommunication nhcComm = getCommunication();
155         if (nhcComm == null) {
156             return;
157         }
158
159         // We need to do this in a separate thread because we may have to wait for the
160         // communication to become active
161         scheduler.submit(() -> {
162             if (!nhcComm.communicationActive()) {
163                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
164                         "Niko Home Control: no connection with Niko Home Control, could not initialize thermostat "
165                                 + thermostatId);
166                 return;
167             }
168
169             nhcThermostat = nhcComm.getThermostats().get(thermostatId);
170             if (nhcThermostat == null) {
171                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
172                         "Niko Home Control: thermostatId does not match a thermostat in the controller "
173                                 + thermostatId);
174                 return;
175             }
176
177             nhcThermostat.setEventHandler(this);
178
179             updateProperties();
180
181             String thermostatLocation = nhcThermostat.getLocation();
182             if (thing.getLocation() == null) {
183                 thing.setLocation(thermostatLocation);
184             }
185
186             thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
187                     nhcThermostat.getOverrule(), nhcThermostat.getDemand());
188
189             logger.debug("Niko Home Control: thermostat intialized {}", thermostatId);
190
191             Bridge bridge = getBridge();
192             if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
193                 updateStatus(ThingStatus.ONLINE);
194             } else {
195                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
196             }
197         });
198     }
199
200     private void updateProperties() {
201         Map<String, String> properties = new HashMap<>();
202
203         if (nhcThermostat instanceof NhcThermostat2) {
204             NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
205             properties.put("model", thermostat.getModel());
206             properties.put("technology", thermostat.getTechnology());
207         }
208
209         thing.setProperties(properties);
210     }
211
212     @Override
213     public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
214         updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
215
216         int overruletime = nhcThermostat.getRemainingOverruletime();
217         updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
218         // refresh the remaining time every minute
219         scheduleRefreshOverruletime(nhcThermostat);
220
221         // If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
222         // setpoint temperature
223         if (overruletime == 0) {
224             updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
225         } else {
226             updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
227         }
228
229         updateState(CHANNEL_MODE, new DecimalType(mode));
230
231         updateState(CHANNEL_DEMAND, new DecimalType(demand));
232
233         updateStatus(ThingStatus.ONLINE);
234     }
235
236     /**
237      * Method to update state of overruletime channel every minute with remaining time.
238      *
239      * @param NhcThermostat object
240      *
241      */
242     private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
243         cancelRefreshTimer();
244
245         if (nhcThermostat.getRemainingOverruletime() <= 0) {
246             return;
247         }
248
249         refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
250             int remainingTime = nhcThermostat.getRemainingOverruletime();
251             updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
252             if (remainingTime <= 0) {
253                 cancelRefreshTimer();
254             }
255         }, 1, 1, TimeUnit.MINUTES);
256     }
257
258     private void cancelRefreshTimer() {
259         ScheduledFuture<?> timer = refreshTimer;
260         if (timer != null) {
261             timer.cancel(true);
262         }
263         refreshTimer = null;
264     }
265
266     @Override
267     public void thermostatRemoved() {
268         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
269                 "Niko Home Control: thermostat has been removed from the controller " + thermostatId);
270     }
271
272     private void restartCommunication(NikoHomeControlCommunication nhcComm) {
273         // We lost connection but the connection object is there, so was correctly started.
274         // Try to restart communication.
275         nhcComm.restartCommunication();
276         // If still not active, take thing offline and return.
277         if (!nhcComm.communicationActive()) {
278             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
279                     "Niko Home Control: communication socket error");
280             return;
281         }
282         // Also put the bridge back online
283         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
284         if (nhcBridgeHandler != null) {
285             nhcBridgeHandler.bridgeOnline();
286         }
287     }
288
289     private @Nullable NikoHomeControlCommunication getCommunication() {
290         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
291         if (nhcBridgeHandler == null) {
292             updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
293                     "Niko Home Control: no bridge initialized for thermostat " + thermostatId);
294             return null;
295         }
296         NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
297         return nhcComm;
298     }
299
300     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
301         Bridge nhcBridge = getBridge();
302         if (nhcBridge == null) {
303             updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
304                     "Niko Home Control: no bridge initialized for thermostat " + thermostatId);
305             return null;
306         }
307         NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
308         return nhcBridgeHandler;
309     }
310 }