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