]> git.basschouten.com Git - openhab-addons.git/blob
cfb941a461cf6ef2ba3db109a3cd5333bdda1a09
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.comfoair.internal;
14
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.stream.Collectors;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.comfoair.internal.datatypes.ComfoAirDataType;
27 import org.openhab.core.io.transport.serial.SerialPortManager;
28 import org.openhab.core.thing.Channel;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.thing.binding.builder.ThingBuilder;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link ComfoAirHandler} is responsible for handling commands, which are
44  * sent to one of the channels.
45  *
46  * @author Hans Böhm - Initial contribution
47  */
48 @NonNullByDefault
49 public class ComfoAirHandler extends BaseThingHandler {
50     private static final int DEFAULT_REFRESH_INTERVAL_SEC = 60;
51
52     private final Logger logger = LoggerFactory.getLogger(ComfoAirHandler.class);
53     private final ComfoAirConfiguration config = getConfigAs(ComfoAirConfiguration.class);
54     private final SerialPortManager serialPortManager;
55     private @Nullable ScheduledFuture<?> poller;
56     private @Nullable ScheduledFuture<?> affectedItemsPoller;
57     private @Nullable ComfoAirSerialConnector comfoAirConnector;
58
59     public static final int BAUDRATE = 9600;
60
61     public ComfoAirHandler(Thing thing, final SerialPortManager serialPortManager) {
62         super(thing);
63         this.serialPortManager = serialPortManager;
64     }
65
66     @Override
67     public void handleCommand(ChannelUID channelUID, Command command) {
68         String channelId = channelUID.getId();
69
70         if (command instanceof RefreshType) {
71             Channel channel = this.thing.getChannel(channelUID);
72             if (channel != null) {
73                 updateChannelState(channel);
74             }
75         } else {
76             ComfoAirCommand changeCommand = ComfoAirCommandType.getChangeCommand(channelId, command);
77
78             if (changeCommand != null) {
79                 Set<String> keysToUpdate = getThing().getChannels().stream().map(Channel::getUID).filter(this::isLinked)
80                         .map(ChannelUID::getId).collect(Collectors.toSet());
81                 sendCommand(changeCommand, channelId);
82
83                 Collection<ComfoAirCommand> affectedReadCommands = ComfoAirCommandType
84                         .getAffectedReadCommands(channelId, keysToUpdate);
85
86                 if (affectedReadCommands.size() > 0) {
87                     Runnable updateThread = new AffectedItemsUpdateThread(affectedReadCommands);
88                     affectedItemsPoller = scheduler.schedule(updateThread, 3, TimeUnit.SECONDS);
89                 }
90             } else {
91                 logger.warn("Unhandled command type: {}, channelId: {}", command.toString(), channelId);
92             }
93         }
94     }
95
96     @Override
97     public void initialize() {
98         String serialPort = this.config.serialPort;
99
100         if (serialPort.isEmpty()) {
101             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured.");
102             return;
103         } else {
104             ComfoAirSerialConnector comfoAirConnector = new ComfoAirSerialConnector(serialPortManager, serialPort,
105                     BAUDRATE);
106             this.comfoAirConnector = comfoAirConnector;
107         }
108         updateStatus(ThingStatus.UNKNOWN);
109         scheduler.submit(this::connect);
110     }
111
112     private void connect() {
113         if (comfoAirConnector != null) {
114             try {
115                 comfoAirConnector.open();
116                 if (comfoAirConnector != null) {
117                     updateStatus(ThingStatus.ONLINE);
118                     pullDeviceProperties();
119                     Map<String, String> properties = thing.getProperties();
120
121                     List<Channel> toBeRemovedChannels = new ArrayList<>();
122                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER)
123                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
124                         toBeRemovedChannels.addAll(getThing()
125                                 .getChannelsOfGroup(ComfoAirBindingConstants.CG_PREHEATER_PREFIX.replaceAll("#$", "")));
126                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
127                                 + ComfoAirBindingConstants.CHANNEL_FROST_STATE);
128                         if (stateChannel != null) {
129                             toBeRemovedChannels.add(stateChannel);
130                         }
131                     }
132                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_BYPASS)
133                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
134                         toBeRemovedChannels.addAll(getThing()
135                                 .getChannelsOfGroup(ComfoAirBindingConstants.CG_BYPASS_PREFIX.replaceAll("#$", "")));
136                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
137                                 + ComfoAirBindingConstants.CHANNEL_BYPASS_STATE);
138                         if (stateChannel != null) {
139                             toBeRemovedChannels.add(stateChannel);
140                         }
141                     }
142                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_CHIMNEY)
143                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
144                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
145                                 + ComfoAirBindingConstants.CHANNEL_CHIMNEY_STATE);
146                         if (stateChannel != null) {
147                             toBeRemovedChannels.add(stateChannel);
148                         }
149                     }
150                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_COOKERHOOD)
151                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
152                         toBeRemovedChannels.addAll(getThing().getChannelsOfGroup(
153                                 ComfoAirBindingConstants.CG_COOKERHOOD_PREFIX.replaceAll("#$", "")));
154                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
155                                 + ComfoAirBindingConstants.CHANNEL_COOKERHOOD_STATE);
156                         if (stateChannel != null) {
157                             toBeRemovedChannels.add(stateChannel);
158                         }
159                     }
160                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_HEATER)
161                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
162                         toBeRemovedChannels.addAll(getThing()
163                                 .getChannelsOfGroup(ComfoAirBindingConstants.CG_HEATER_PREFIX.replaceAll("#$", "")));
164                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
165                                 + ComfoAirBindingConstants.CHANNEL_HEATER_STATE);
166                         if (stateChannel != null) {
167                             toBeRemovedChannels.add(stateChannel);
168                         }
169                     }
170                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY)
171                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
172                         toBeRemovedChannels.addAll(getThing()
173                                 .getChannelsOfGroup(ComfoAirBindingConstants.CG_ENTHALPY_PREFIX.replaceAll("#$", "")));
174                         Channel stateChannel = getThing().getChannel(ComfoAirBindingConstants.CG_MENUP9_PREFIX
175                                 + ComfoAirBindingConstants.CHANNEL_ENTHALPY_STATE);
176                         if (stateChannel != null) {
177                             toBeRemovedChannels.add(stateChannel);
178                         }
179                     }
180                     if (properties.get(ComfoAirBindingConstants.PROPERTY_OPTION_EWT)
181                             .equals(ComfoAirBindingConstants.COMMON_OPTION_STATES[0])) {
182                         toBeRemovedChannels.addAll(getThing()
183                                 .getChannelsOfGroup(ComfoAirBindingConstants.CG_EWT_PREFIX.replaceAll("#$", "")));
184                         Channel stateChannel = getThing().getChannel(
185                                 ComfoAirBindingConstants.CG_MENUP9_PREFIX + ComfoAirBindingConstants.CHANNEL_EWT_STATE);
186                         if (stateChannel != null) {
187                             toBeRemovedChannels.add(stateChannel);
188                         }
189                     }
190                     ThingBuilder builder = editThing().withoutChannels(toBeRemovedChannels);
191                     updateThing(builder.build());
192
193                     List<Channel> channels = this.thing.getChannels();
194
195                     poller = scheduler.scheduleWithFixedDelay(() -> {
196                         for (Channel channel : channels) {
197                             updateChannelState(channel);
198                         }
199                     }, 0, (this.config.refreshInterval > 0) ? this.config.refreshInterval
200                             : DEFAULT_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
201                 } else {
202                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
203                 }
204             } catch (ComfoAirSerialException e) {
205                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
206             }
207         } else {
208             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
209         }
210     }
211
212     @Override
213     public void dispose() {
214         if (comfoAirConnector != null) {
215             comfoAirConnector.close();
216         }
217
218         final ScheduledFuture<?> localPoller = poller;
219
220         if (localPoller != null) {
221             localPoller.cancel(true);
222             poller = null;
223         }
224
225         final ScheduledFuture<?> localAffectedItemsPoller = affectedItemsPoller;
226
227         if (localAffectedItemsPoller != null) {
228             localAffectedItemsPoller.cancel(true);
229             affectedItemsPoller = null;
230         }
231     }
232
233     private void updateChannelState(Channel channel) {
234         if (!isLinked(channel.getUID())) {
235             return;
236         }
237         String commandKey = channel.getUID().getId();
238
239         ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(commandKey);
240         if (readCommand != null) {
241             scheduler.submit(() -> {
242                 State state = sendCommand(readCommand, commandKey);
243                 updateState(channel.getUID(), state);
244             });
245         }
246     }
247
248     private State sendCommand(ComfoAirCommand command, String commandKey) {
249         ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
250
251         if (comfoAirConnector != null) {
252             Integer requestCmd = command.getRequestCmd();
253             Integer replyCmd = command.getReplyCmd();
254             int[] requestData = command.getRequestData();
255
256             Integer preRequestCmd;
257             Integer preReplyCmd;
258             int[] preResponse = ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
259
260             if (requestCmd != null) {
261                 switch (requestCmd) {
262                     case ComfoAirCommandType.Constants.REQUEST_SET_ANALOGS:
263                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_ANALOGS;
264                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_ANALOGS;
265                         break;
266                     case ComfoAirCommandType.Constants.REQUEST_SET_DELAYS:
267                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_DELAYS;
268                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_DELAYS;
269                         break;
270                     case ComfoAirCommandType.Constants.REQUEST_SET_FAN_LEVEL:
271                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_FAN_LEVEL;
272                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_FAN_LEVEL;
273                         break;
274                     case ComfoAirCommandType.Constants.REQUEST_SET_STATES:
275                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_STATES;
276                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_STATES;
277                         break;
278                     case ComfoAirCommandType.Constants.REQUEST_SET_EWT:
279                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_EWT;
280                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_EWT;
281                         break;
282                     default:
283                         preRequestCmd = requestCmd;
284                         preReplyCmd = replyCmd;
285                 }
286
287                 if (!preRequestCmd.equals(requestCmd)) {
288                     command.setRequestCmd(preRequestCmd);
289                     command.setReplyCmd(preReplyCmd);
290                     command.setRequestData(ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
291
292                     preResponse = comfoAirConnector.sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
293
294                     if (preResponse.length <= 0) {
295                         return UnDefType.NULL;
296                     } else {
297                         command.setRequestCmd(requestCmd);
298                         command.setReplyCmd(replyCmd);
299                         command.setRequestData(requestData);
300                     }
301                 }
302
303                 int[] response = comfoAirConnector.sendCommand(command, preResponse);
304
305                 if (response.length > 0) {
306                     ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(commandKey);
307                     State value = UnDefType.UNDEF;
308
309                     if (comfoAirCommandType != null) {
310                         ComfoAirDataType dataType = comfoAirCommandType.getDataType();
311                         value = dataType.convertToState(response, comfoAirCommandType);
312                     }
313                     if (value instanceof UnDefType) {
314                         if (logger.isWarnEnabled()) {
315                             logger.warn("unexpected value for DATA: {}", ComfoAirSerialConnector.dumpData(response));
316                         }
317                     }
318                     return value;
319                 }
320             }
321         }
322         return UnDefType.UNDEF;
323     }
324
325     public void pullDeviceProperties() {
326         Map<String, String> properties = editProperties();
327         ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
328
329         if (comfoAirConnector != null) {
330             String[] namedProperties = new String[] { ComfoAirBindingConstants.PROPERTY_SOFTWARE_MAIN_VERSION,
331                     ComfoAirBindingConstants.PROPERTY_SOFTWARE_MINOR_VERSION,
332                     ComfoAirBindingConstants.PROPERTY_DEVICE_NAME };
333             String[] optionProperties = new String[] { ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER,
334                     ComfoAirBindingConstants.PROPERTY_OPTION_BYPASS, ComfoAirBindingConstants.PROPERTY_OPTION_RECU_TYPE,
335                     ComfoAirBindingConstants.PROPERTY_OPTION_RECU_SIZE,
336                     ComfoAirBindingConstants.PROPERTY_OPTION_CHIMNEY,
337                     ComfoAirBindingConstants.PROPERTY_OPTION_COOKERHOOD,
338                     ComfoAirBindingConstants.PROPERTY_OPTION_HEATER, ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY,
339                     ComfoAirBindingConstants.PROPERTY_OPTION_EWT };
340
341             for (String prop : namedProperties) {
342                 ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(prop);
343                 if (readCommand != null) {
344                     int[] response = comfoAirConnector.sendCommand(readCommand,
345                             ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
346                     if (response.length > 0) {
347                         ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(prop);
348                         String value = "";
349
350                         if (comfoAirCommandType != null) {
351                             ComfoAirDataType dataType = comfoAirCommandType.getDataType();
352                             if (prop.equals(ComfoAirBindingConstants.PROPERTY_DEVICE_NAME)) {
353                                 value = dataType.calculateStringValue(response, comfoAirCommandType);
354                             } else {
355                                 value = String.valueOf(dataType.calculateNumberValue(response, comfoAirCommandType));
356                             }
357                         }
358                         properties.put(prop, value);
359                     }
360                 }
361             }
362
363             ComfoAirCommand optionsReadCommand = new ComfoAirCommand(ComfoAirBindingConstants.PROPERTY_OPTION_PREHEATER,
364                     ComfoAirCommandType.Constants.REQUEST_GET_STATES, ComfoAirCommandType.Constants.REPLY_GET_STATES,
365                     ComfoAirCommandType.Constants.EMPTY_INT_ARRAY, null, null);
366             int[] response = comfoAirConnector.sendCommand(optionsReadCommand,
367                     ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
368             if (response.length > 0) {
369                 for (String prop : optionProperties) {
370                     ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(prop);
371                     String value = "";
372
373                     if (comfoAirCommandType != null) {
374                         ComfoAirDataType dataType = comfoAirCommandType.getDataType();
375                         int intValue = dataType.calculateNumberValue(response, comfoAirCommandType);
376
377                         switch (prop) {
378                             case ComfoAirBindingConstants.PROPERTY_OPTION_RECU_TYPE:
379                                 value = intValue == 1 ? "LEFT" : "RIGHT";
380                                 break;
381                             case ComfoAirBindingConstants.PROPERTY_OPTION_RECU_SIZE:
382                                 value = intValue == 1 ? "BIG" : "SMALL";
383                                 break;
384                             case ComfoAirBindingConstants.PROPERTY_OPTION_ENTHALPY:
385                                 if (intValue == 1) {
386                                     value = ComfoAirBindingConstants.COMMON_OPTION_STATES[1];
387                                 } else if (intValue == 2) {
388                                     value = "Installed w\\o sensor";
389                                 } else {
390                                     value = ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
391                                 }
392                                 break;
393                             case ComfoAirBindingConstants.PROPERTY_OPTION_EWT:
394                                 if (intValue == 1) {
395                                     value = "Regulated";
396                                 } else if (intValue == 2) {
397                                     value = "Unregulated";
398                                 } else {
399                                     value = ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
400                                 }
401                                 break;
402                             default:
403                                 value = intValue > 0 ? ComfoAirBindingConstants.COMMON_OPTION_STATES[1]
404                                         : ComfoAirBindingConstants.COMMON_OPTION_STATES[0];
405                                 break;
406                         }
407                     }
408                     properties.put(prop, value);
409                 }
410             }
411             thing.setProperties(properties);
412         }
413     }
414
415     private class AffectedItemsUpdateThread implements Runnable {
416
417         private Collection<ComfoAirCommand> affectedReadCommands;
418
419         public AffectedItemsUpdateThread(Collection<ComfoAirCommand> affectedReadCommands) {
420             this.affectedReadCommands = affectedReadCommands;
421         }
422
423         @Override
424         public void run() {
425             for (ComfoAirCommand readCommand : this.affectedReadCommands) {
426                 Integer replyCmd = readCommand.getReplyCmd();
427                 if (replyCmd != null) {
428                     List<ComfoAirCommandType> commandTypes = ComfoAirCommandType.getCommandTypesByReplyCmd(replyCmd);
429
430                     for (ComfoAirCommandType commandType : commandTypes) {
431                         String commandKey = commandType.getKey();
432                         sendCommand(readCommand, commandKey);
433                     }
434                 }
435             }
436         }
437     }
438 }