]> git.basschouten.com Git - openhab-addons.git/blob
144d242ef6d8c1bb96ad03d91dffacc1f332a06c
[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.Collection;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.stream.Collectors;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.comfoair.internal.datatypes.ComfoAirDataType;
26 import org.openhab.core.io.transport.serial.SerialPortManager;
27 import org.openhab.core.thing.Channel;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.binding.BaseThingHandler;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 /**
41  * The {@link ComfoAirHandler} is responsible for handling commands, which are
42  * sent to one of the channels.
43  *
44  * @author Hans Böhm - Initial contribution
45  */
46 @NonNullByDefault
47 public class ComfoAirHandler extends BaseThingHandler {
48     private static final int DEFAULT_REFRESH_INTERVAL_SEC = 60;
49
50     private final Logger logger = LoggerFactory.getLogger(ComfoAirHandler.class);
51     private final ComfoAirConfiguration config = getConfigAs(ComfoAirConfiguration.class);
52     private final SerialPortManager serialPortManager;
53     private @Nullable ScheduledFuture<?> poller;
54     private @Nullable ScheduledFuture<?> affectedItemsPoller;
55     private @Nullable ComfoAirSerialConnector comfoAirConnector;
56
57     public static final int BAUDRATE = 9600;
58
59     public ComfoAirHandler(Thing thing, final SerialPortManager serialPortManager) {
60         super(thing);
61         this.serialPortManager = serialPortManager;
62     }
63
64     @Override
65     public void handleCommand(ChannelUID channelUID, Command command) {
66         String channelId = channelUID.getId();
67
68         if (command instanceof RefreshType) {
69             Channel channel = this.thing.getChannel(channelUID);
70             if (channel != null) {
71                 updateChannelState(channel);
72             }
73         } else {
74             ComfoAirCommand changeCommand = ComfoAirCommandType.getChangeCommand(channelId, command);
75
76             if (changeCommand != null) {
77                 Set<String> keysToUpdate = getThing().getChannels().stream().map(Channel::getUID).filter(this::isLinked)
78                         .map(ChannelUID::getId).collect(Collectors.toSet());
79                 sendCommand(changeCommand, channelId);
80
81                 Collection<ComfoAirCommand> affectedReadCommands = ComfoAirCommandType
82                         .getAffectedReadCommands(channelId, keysToUpdate);
83
84                 if (affectedReadCommands.size() > 0) {
85                     Runnable updateThread = new AffectedItemsUpdateThread(affectedReadCommands);
86                     affectedItemsPoller = scheduler.schedule(updateThread, 3, TimeUnit.SECONDS);
87                 }
88             } else {
89                 logger.warn("Unhandled command type: {}, channelId: {}", command.toString(), channelId);
90             }
91         }
92     }
93
94     @Override
95     public void initialize() {
96         String serialPort = this.config.serialPort;
97
98         if (serialPort.isEmpty()) {
99             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured.");
100             return;
101         } else {
102             ComfoAirSerialConnector comfoAirConnector = new ComfoAirSerialConnector(serialPortManager, serialPort,
103                     BAUDRATE);
104             this.comfoAirConnector = comfoAirConnector;
105         }
106         updateStatus(ThingStatus.UNKNOWN);
107         scheduler.submit(this::connect);
108     }
109
110     private void connect() {
111         if (comfoAirConnector != null) {
112             try {
113                 comfoAirConnector.open();
114                 if (comfoAirConnector != null) {
115                     updateStatus(ThingStatus.ONLINE);
116                     pullDeviceProperties();
117
118                     List<Channel> channels = this.thing.getChannels();
119
120                     poller = scheduler.scheduleWithFixedDelay(() -> {
121                         for (Channel channel : channels) {
122                             updateChannelState(channel);
123                         }
124                     }, 0, (this.config.refreshInterval > 0) ? this.config.refreshInterval
125                             : DEFAULT_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
126                 } else {
127                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
128                 }
129             } catch (ComfoAirSerialException e) {
130                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
131             }
132         } else {
133             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
134         }
135     }
136
137     @Override
138     public void dispose() {
139         if (comfoAirConnector != null) {
140             comfoAirConnector.close();
141         }
142
143         final ScheduledFuture<?> localPoller = poller;
144
145         if (localPoller != null) {
146             localPoller.cancel(true);
147             poller = null;
148         }
149
150         final ScheduledFuture<?> localAffectedItemsPoller = affectedItemsPoller;
151
152         if (localAffectedItemsPoller != null) {
153             localAffectedItemsPoller.cancel(true);
154             affectedItemsPoller = null;
155         }
156     }
157
158     private void updateChannelState(Channel channel) {
159         if (!isLinked(channel.getUID())) {
160             return;
161         }
162         String commandKey = channel.getUID().getId();
163
164         ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(commandKey);
165         if (readCommand != null) {
166             scheduler.submit(() -> {
167                 State state = sendCommand(readCommand, commandKey);
168                 updateState(channel.getUID(), state);
169             });
170         }
171     }
172
173     private State sendCommand(ComfoAirCommand command, String commandKey) {
174         ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
175
176         if (comfoAirConnector != null) {
177             Integer requestCmd = command.getRequestCmd();
178             Integer replyCmd = command.getReplyCmd();
179             int[] requestData = command.getRequestData();
180
181             Integer preRequestCmd;
182             Integer preReplyCmd;
183             int[] preResponse = ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
184
185             if (requestCmd != null) {
186                 switch (requestCmd) {
187                     case ComfoAirCommandType.Constants.REQUEST_SET_ANALOGS:
188                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_ANALOGS;
189                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_ANALOGS;
190                         break;
191                     case ComfoAirCommandType.Constants.REQUEST_SET_DELAYS:
192                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_DELAYS;
193                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_DELAYS;
194                         break;
195                     case ComfoAirCommandType.Constants.REQUEST_SET_FAN_LEVEL:
196                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_FAN_LEVEL;
197                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_FAN_LEVEL;
198                         break;
199                     case ComfoAirCommandType.Constants.REQUEST_SET_STATES:
200                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_STATES;
201                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_STATES;
202                         break;
203                     case ComfoAirCommandType.Constants.REQUEST_SET_GHX:
204                         preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_GHX;
205                         preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_GHX;
206                         break;
207                     default:
208                         preRequestCmd = requestCmd;
209                         preReplyCmd = replyCmd;
210                 }
211
212                 if (!preRequestCmd.equals(requestCmd)) {
213                     command.setRequestCmd(preRequestCmd);
214                     command.setReplyCmd(preReplyCmd);
215                     command.setRequestData(ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
216
217                     preResponse = comfoAirConnector.sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
218
219                     if (preResponse.length <= 0) {
220                         return UnDefType.NULL;
221                     } else {
222                         command.setRequestCmd(requestCmd);
223                         command.setReplyCmd(replyCmd);
224                         command.setRequestData(requestData);
225                     }
226                 }
227
228                 int[] response = comfoAirConnector.sendCommand(command, preResponse);
229
230                 if (response.length > 0) {
231                     ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(commandKey);
232                     State value = UnDefType.UNDEF;
233
234                     if (comfoAirCommandType != null) {
235                         ComfoAirDataType dataType = comfoAirCommandType.getDataType();
236                         value = dataType.convertToState(response, comfoAirCommandType);
237                     }
238                     if (value instanceof UnDefType) {
239                         if (logger.isWarnEnabled()) {
240                             logger.warn("unexpected value for DATA: {}", ComfoAirSerialConnector.dumpData(response));
241                         }
242                     }
243                     return value;
244                 }
245             }
246         }
247         return UnDefType.UNDEF;
248     }
249
250     public void pullDeviceProperties() {
251         Map<String, String> properties = editProperties();
252         ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
253
254         if (comfoAirConnector != null) {
255             String[] namedProperties = new String[] { ComfoAirBindingConstants.PROPERTY_SOFTWARE_MAIN_VERSION,
256                     ComfoAirBindingConstants.PROPERTY_SOFTWARE_MINOR_VERSION,
257                     ComfoAirBindingConstants.PROPERTY_DEVICE_NAME };
258
259             for (String prop : namedProperties) {
260                 ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(prop);
261                 if (readCommand != null) {
262                     int[] response = comfoAirConnector.sendCommand(readCommand,
263                             ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
264                     if (response.length > 0) {
265                         ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(prop);
266                         String value = "";
267
268                         if (comfoAirCommandType != null) {
269                             ComfoAirDataType dataType = comfoAirCommandType.getDataType();
270                             if (prop.equals(ComfoAirBindingConstants.PROPERTY_DEVICE_NAME)) {
271                                 value = dataType.calculateStringValue(response, comfoAirCommandType);
272                             } else {
273                                 value = String.valueOf(dataType.calculateNumberValue(response, comfoAirCommandType));
274                             }
275                         }
276                         properties.put(prop, value);
277                     }
278                 }
279             }
280             thing.setProperties(properties);
281         }
282     }
283
284     private class AffectedItemsUpdateThread implements Runnable {
285
286         private Collection<ComfoAirCommand> affectedReadCommands;
287
288         public AffectedItemsUpdateThread(Collection<ComfoAirCommand> affectedReadCommands) {
289             this.affectedReadCommands = affectedReadCommands;
290         }
291
292         @Override
293         public void run() {
294             for (ComfoAirCommand readCommand : this.affectedReadCommands) {
295                 Integer replyCmd = readCommand.getReplyCmd();
296                 if (replyCmd != null) {
297                     List<ComfoAirCommandType> commandTypes = ComfoAirCommandType.getCommandTypesByReplyCmd(replyCmd);
298
299                     for (ComfoAirCommandType commandType : commandTypes) {
300                         String commandKey = commandType.getKey();
301                         State state = sendCommand(readCommand, commandKey);
302                         updateState(commandKey, state);
303                     }
304                 }
305             }
306         }
307     }
308 }