2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.comfoair.internal;
15 import java.util.Collection;
16 import java.util.List;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.stream.Collectors;
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;
41 * The {@link ComfoAirHandler} is responsible for handling commands, which are
42 * sent to one of the channels.
44 * @author Hans Böhm - Initial contribution
47 public class ComfoAirHandler extends BaseThingHandler {
48 private static final int DEFAULT_REFRESH_INTERVAL_SEC = 60;
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;
57 public static final int BAUDRATE = 9600;
59 public ComfoAirHandler(Thing thing, final SerialPortManager serialPortManager) {
61 this.serialPortManager = serialPortManager;
65 public void handleCommand(ChannelUID channelUID, Command command) {
66 String channelId = channelUID.getId();
68 if (command instanceof RefreshType) {
69 Channel channel = this.thing.getChannel(channelUID);
70 if (channel != null) {
71 updateChannelState(channel);
74 ComfoAirCommand changeCommand = ComfoAirCommandType.getChangeCommand(channelId, command);
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);
81 Collection<ComfoAirCommand> affectedReadCommands = ComfoAirCommandType
82 .getAffectedReadCommands(channelId, keysToUpdate);
84 if (affectedReadCommands.size() > 0) {
85 Runnable updateThread = new AffectedItemsUpdateThread(affectedReadCommands);
86 affectedItemsPoller = scheduler.schedule(updateThread, 3, TimeUnit.SECONDS);
89 logger.warn("Unhandled command type: {}, channelId: {}", command.toString(), channelId);
95 public void initialize() {
96 String serialPort = this.config.serialPort;
98 if (serialPort.isEmpty()) {
99 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port is not configured.");
102 ComfoAirSerialConnector comfoAirConnector = new ComfoAirSerialConnector(serialPortManager, serialPort,
104 this.comfoAirConnector = comfoAirConnector;
106 updateStatus(ThingStatus.UNKNOWN);
107 scheduler.submit(this::connect);
110 private void connect() {
111 if (comfoAirConnector != null) {
113 comfoAirConnector.open();
114 if (comfoAirConnector != null) {
115 updateStatus(ThingStatus.ONLINE);
116 pullDeviceProperties();
118 List<Channel> channels = this.thing.getChannels();
120 poller = scheduler.scheduleWithFixedDelay(() -> {
121 for (Channel channel : channels) {
122 updateChannelState(channel);
124 }, 0, (this.config.refreshInterval > 0) ? this.config.refreshInterval
125 : DEFAULT_REFRESH_INTERVAL_SEC, TimeUnit.SECONDS);
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
129 } catch (ComfoAirSerialException e) {
130 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
138 public void dispose() {
139 if (comfoAirConnector != null) {
140 comfoAirConnector.close();
143 final ScheduledFuture<?> localPoller = poller;
145 if (localPoller != null) {
146 localPoller.cancel(true);
150 final ScheduledFuture<?> localAffectedItemsPoller = affectedItemsPoller;
152 if (localAffectedItemsPoller != null) {
153 localAffectedItemsPoller.cancel(true);
154 affectedItemsPoller = null;
158 private void updateChannelState(Channel channel) {
159 if (!isLinked(channel.getUID())) {
162 String commandKey = channel.getUID().getId();
164 ComfoAirCommand readCommand = ComfoAirCommandType.getReadCommand(commandKey);
165 if (readCommand != null) {
166 scheduler.submit(() -> {
167 State state = sendCommand(readCommand, commandKey);
168 updateState(channel.getUID(), state);
173 private State sendCommand(ComfoAirCommand command, String commandKey) {
174 ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
176 if (comfoAirConnector != null) {
177 Integer requestCmd = command.getRequestCmd();
178 Integer replyCmd = command.getReplyCmd();
179 int[] requestData = command.getRequestData();
181 Integer preRequestCmd;
183 int[] preResponse = ComfoAirCommandType.Constants.EMPTY_INT_ARRAY;
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;
191 case ComfoAirCommandType.Constants.REQUEST_SET_DELAYS:
192 preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_DELAYS;
193 preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_DELAYS;
195 case ComfoAirCommandType.Constants.REQUEST_SET_FAN_LEVEL:
196 preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_FAN_LEVEL;
197 preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_FAN_LEVEL;
199 case ComfoAirCommandType.Constants.REQUEST_SET_STATES:
200 preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_STATES;
201 preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_STATES;
203 case ComfoAirCommandType.Constants.REQUEST_SET_GHX:
204 preRequestCmd = ComfoAirCommandType.Constants.REQUEST_GET_GHX;
205 preReplyCmd = ComfoAirCommandType.Constants.REPLY_GET_GHX;
208 preRequestCmd = requestCmd;
209 preReplyCmd = replyCmd;
212 if (!preRequestCmd.equals(requestCmd)) {
213 command.setRequestCmd(preRequestCmd);
214 command.setReplyCmd(preReplyCmd);
215 command.setRequestData(ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
217 preResponse = comfoAirConnector.sendCommand(command, ComfoAirCommandType.Constants.EMPTY_INT_ARRAY);
219 if (preResponse.length <= 0) {
220 return UnDefType.NULL;
222 command.setRequestCmd(requestCmd);
223 command.setReplyCmd(replyCmd);
224 command.setRequestData(requestData);
228 int[] response = comfoAirConnector.sendCommand(command, preResponse);
230 if (response.length > 0) {
231 ComfoAirCommandType comfoAirCommandType = ComfoAirCommandType.getCommandTypeByKey(commandKey);
232 State value = UnDefType.UNDEF;
234 if (comfoAirCommandType != null) {
235 ComfoAirDataType dataType = comfoAirCommandType.getDataType();
236 value = dataType.convertToState(response, comfoAirCommandType);
238 if (value instanceof UnDefType) {
239 if (logger.isWarnEnabled()) {
240 logger.warn("unexpected value for DATA: {}", ComfoAirSerialConnector.dumpData(response));
247 return UnDefType.UNDEF;
250 public void pullDeviceProperties() {
251 Map<String, String> properties = editProperties();
252 ComfoAirSerialConnector comfoAirConnector = this.comfoAirConnector;
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 };
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);
268 if (comfoAirCommandType != null) {
269 ComfoAirDataType dataType = comfoAirCommandType.getDataType();
270 if (prop.equals(ComfoAirBindingConstants.PROPERTY_DEVICE_NAME)) {
271 value = dataType.calculateStringValue(response, comfoAirCommandType);
273 value = String.valueOf(dataType.calculateNumberValue(response, comfoAirCommandType));
276 properties.put(prop, value);
280 thing.setProperties(properties);
284 private class AffectedItemsUpdateThread implements Runnable {
286 private Collection<ComfoAirCommand> affectedReadCommands;
288 public AffectedItemsUpdateThread(Collection<ComfoAirCommand> affectedReadCommands) {
289 this.affectedReadCommands = affectedReadCommands;
294 for (ComfoAirCommand readCommand : this.affectedReadCommands) {
295 Integer replyCmd = readCommand.getReplyCmd();
296 if (replyCmd != null) {
297 List<ComfoAirCommandType> commandTypes = ComfoAirCommandType.getCommandTypesByReplyCmd(replyCmd);
299 for (ComfoAirCommandType commandType : commandTypes) {
300 String commandKey = commandType.getKey();
301 State state = sendCommand(readCommand, commandKey);
302 updateState(commandKey, state);