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