2 * Copyright (c) 2010-2023 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.irtrans.internal.handler;
15 import java.io.IOException;
16 import java.io.UnsupportedEncodingException;
17 import java.math.BigDecimal;
18 import java.net.InetAddress;
19 import java.net.InetSocketAddress;
20 import java.nio.ByteBuffer;
21 import java.nio.channels.ClosedChannelException;
22 import java.nio.channels.NoConnectionPendingException;
23 import java.nio.channels.NotYetConnectedException;
24 import java.nio.channels.SelectionKey;
25 import java.nio.channels.Selector;
26 import java.nio.channels.ServerSocketChannel;
27 import java.nio.channels.SocketChannel;
28 import java.util.Collection;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.concurrent.CopyOnWriteArrayList;
33 import java.util.concurrent.locks.Lock;
34 import java.util.concurrent.locks.ReentrantLock;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
38 import org.apache.commons.lang3.StringUtils;
39 import org.eclipse.jdt.annotation.NonNull;
40 import org.openhab.binding.irtrans.internal.IRtransBindingConstants;
41 import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led;
42 import org.openhab.binding.irtrans.internal.IrCommand;
43 import org.openhab.core.config.core.Configuration;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.Channel;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.openhab.core.util.HexUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link EthernetBridgeHandler} is responsible for handling commands, which
59 * are sent to one of the channels.
61 * @author Karel Goderis - Initial contribution
64 public class EthernetBridgeHandler extends BaseBridgeHandler implements TransceiverStatusListener {
66 // List of Configuration constants
67 public static final String BUFFER_SIZE = "bufferSize";
68 public static final String IP_ADDRESS = "ipAddress";
69 public static final String IS_LISTENER = "isListener";
70 public static final String FIRMWARE_VERSION = "firmwareVersion";
71 public static final String LISTENER_PORT = "listenerPort";
72 public static final String MODE = "mode";
73 public static final String PING_TIME_OUT = "pingTimeOut";
74 public static final String PORT_NUMBER = "portNumber";
75 public static final String RECONNECT_INTERVAL = "reconnectInterval";
76 public static final String REFRESH_INTERVAL = "refreshInterval";
77 public static final String RESPONSE_TIME_OUT = "responseTimeOut";
78 public static final String COMMAND = "command";
79 public static final String LED = "led";
80 public static final String REMOTE = "remote";
81 public static final int LISTENING_INTERVAL = 100;
83 private static final Pattern RESPONSE_PATTERN = Pattern.compile("..(\\d{5}) (.*)", Pattern.DOTALL);
84 private static final Pattern HEX_PATTERN = Pattern.compile("RCV_HEX (.*)");
85 private static final Pattern IRDB_PATTERN = Pattern.compile("RCV_COM (.*),(.*),(.*),(.*)");
87 private Logger logger = LoggerFactory.getLogger(EthernetBridgeHandler.class);
89 private Selector selector;
90 private Thread pollingThread;
91 private SocketChannel socketChannel;
92 protected SelectionKey socketChannelKey;
93 protected ServerSocketChannel listenerChannel;
94 protected SelectionKey listenerKey;
95 protected boolean previousConnectionState;
96 private final Lock lock = new ReentrantLock();
98 private List<TransceiverStatusListener> transceiverStatusListeners = new CopyOnWriteArrayList<>();
101 * Data structure to store the infrared commands that are 'loaded' from the
102 * configuration files. Command loading from pre-defined configuration files is not supported
103 * (anymore), but the code is maintained in case this functionality is re-added in the future
105 protected final Collection<IrCommand> irCommands = new HashSet<>();
107 public EthernetBridgeHandler(Bridge bridge) {
109 // Nothing to do here
113 public void initialize() {
114 // register ourselves as a Transceiver Status Listener
115 registerTransceiverStatusListener(this);
118 selector = Selector.open();
119 } catch (IOException e) {
120 logger.debug("An exception occurred while registering the selector: '{}'", e.getMessage());
121 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
124 if (selector != null) {
125 if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
126 if (pollingThread == null) {
127 pollingThread = new Thread(pollingRunnable, "OH-binding-" + getThing().getUID() + "-polling");
128 pollingThread.start();
131 logger.debug("Cannot connect to IRtrans Ethernet device. IP address or port number not set.");
132 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
133 "IP address or port number not set.");
136 if (getConfig().get(IS_LISTENER) != null) {
137 configureListener((String) getConfig().get(LISTENER_PORT));
143 public void dispose() {
144 unregisterTransceiverStatusListener(this);
147 if (socketChannel != null) {
148 socketChannel.close();
150 } catch (IOException e) {
151 logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
155 if (listenerChannel != null) {
156 listenerChannel.close();
158 } catch (IOException e) {
159 logger.warn("An exception occurred while closing the channel '{}': {}", listenerChannel, e.getMessage());
163 if (selector != null) {
166 } catch (IOException e) {
167 logger.debug("An exception occurred while closing the selector: '{}'", e.getMessage());
170 logger.debug("Stopping the IRtrans polling Thread for {}", getThing().getUID());
171 if (pollingThread != null) {
172 pollingThread.interrupt();
174 pollingThread.join();
175 } catch (InterruptedException e) {
176 Thread.currentThread().interrupt();
178 pollingThread = null;
182 public boolean registerTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) {
183 return transceiverStatusListeners.add(transceiverStatusListener);
186 public boolean unregisterTransceiverStatusListener(@NonNull TransceiverStatusListener transceiverStatusListener) {
187 return transceiverStatusListeners.remove(transceiverStatusListener);
190 public void onConnectionLost() {
191 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
194 if (socketChannel != null) {
195 socketChannel.close();
197 } catch (IOException e) {
198 logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
201 establishConnection();
204 public void onConnectionResumed() {
205 configureTransceiver();
206 updateStatus(ThingStatus.ONLINE);
209 private void establishConnection() {
212 if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
214 socketChannel = SocketChannel.open();
215 socketChannel.socket().setKeepAlive(true);
216 socketChannel.configureBlocking(false);
218 synchronized (selector) {
220 int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT;
221 socketChannelKey = socketChannel.register(selector, interestSet);
224 InetSocketAddress remoteAddress = new InetSocketAddress((String) getConfig().get(IP_ADDRESS),
225 ((BigDecimal) getConfig().get(PORT_NUMBER)).intValue());
226 socketChannel.connect(remoteAddress);
227 } catch (IOException e) {
228 logger.debug("An exception occurred while connecting to '{}:{}' : {}", getConfig().get(IP_ADDRESS),
229 ((BigDecimal) getConfig().get(PORT_NUMBER)).intValue(), e.getMessage());
230 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
234 Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
235 } catch (NumberFormatException | InterruptedException e) {
236 Thread.currentThread().interrupt();
237 logger.debug("An exception occurred while putting a thread to sleep: '{}'", e.getMessage());
238 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
248 public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command) {
249 logger.debug("Received infrared command '{},{}' for thing '{}'", command.getRemote(), command.getCommand(),
250 this.getThing().getUID());
252 for (Channel channel : getThing().getChannels()) {
253 Configuration channelConfiguration = channel.getConfiguration();
255 if (channel.getChannelTypeUID() != null
256 && channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
257 IrCommand thingCompatibleCommand = new IrCommand();
258 thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
259 thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
261 if (command.matches(thingCompatibleCommand)) {
262 StringType stringType = new StringType(command.getRemote() + "," + command.getCommand());
263 logger.debug("Received a matching infrared command '{}' for channel '{}'", stringType,
265 updateState(channel.getUID(), stringType);
272 public void handleCommand(ChannelUID channelUID, Command command) {
273 if (!(command instanceof RefreshType)) {
274 Channel channel = this.getThing().getChannel(channelUID.getId());
275 if (channel != null) {
276 Configuration channelConfiguration = channel.getConfiguration();
277 if (channel.getChannelTypeUID() != null
278 && channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.BLASTER_CHANNEL_TYPE)) {
279 if (command instanceof StringType) {
280 String remoteName = StringUtils.substringBefore(command.toString(), ",");
281 String irCommandName = StringUtils.substringAfter(command.toString(), ",");
283 IrCommand ircommand = new IrCommand();
284 ircommand.setRemote(remoteName);
285 ircommand.setCommand(irCommandName);
287 IrCommand thingCompatibleCommand = new IrCommand();
288 thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
289 thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
291 if (ircommand.matches(thingCompatibleCommand)) {
292 if (sendIRcommand(ircommand, Led.get((String) channelConfiguration.get(LED)))) {
293 logger.debug("Sent a matching infrared command '{}' for channel '{}'", command,
297 "An error occured whilst sending the infrared command '{}' for Channel '{}'",
298 command, channelUID);
303 if (channel.getAcceptedItemType() != null
304 && channel.getAcceptedItemType().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
305 logger.warn("Receivers can only receive infrared commands, not send them");
311 private void configureListener(String listenerPort) {
313 listenerChannel = ServerSocketChannel.open();
314 listenerChannel.socket().bind(new InetSocketAddress(Integer.parseInt(listenerPort)));
315 listenerChannel.configureBlocking(false);
317 logger.info("Listening for incoming connections on {}", listenerChannel.getLocalAddress());
319 synchronized (selector) {
322 listenerKey = listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
323 } catch (ClosedChannelException e1) {
324 logger.debug("An exception occurred while registering a selector: '{}'", e1.getMessage());
325 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
328 } catch (IOException e3) {
330 "An exception occurred while creating configuring the listener channel on port number {}: '{}'",
331 Integer.parseInt(listenerPort), e3.getMessage());
335 protected void configureTransceiver() {
338 String putInASCIImode = "ASCI";
339 ByteBuffer response = sendCommand(putInASCIImode);
341 String getFirmwareVersion = "Aver" + (char) 13;
342 response = sendCommand(getFirmwareVersion);
344 if (response != null) {
345 String message = stripByteCount(response).split("\0")[0];
346 if (message != null) {
347 if (message.contains("VERSION")) {
348 logger.info("'{}' matches an IRtrans device with firmware {}", getThing().getUID(), message);
349 getConfig().put(FIRMWARE_VERSION, message);
351 logger.debug("Received some non-compliant garbage ({})", message);
357 logger.debug("Did not receive an answer from the IRtrans transceiver '{}' - Parsing is skipped",
358 socketChannel.getRemoteAddress());
360 } catch (IOException e1) {
361 logger.debug("An exception occurred while getting a remote address: '{}'", e1.getMessage());
362 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
366 int numberOfRemotes = 0;
367 int numberOfRemotesProcessed = 0;
368 int numberOfRemotesInBatch = 0;
369 String[] remoteList = getRemoteList(0);
371 if (remoteList.length > 0) {
372 logger.debug("The IRtrans device for '{}' supports '{}' remotes", getThing().getUID(), remoteList[1]);
373 numberOfRemotes = Integer.valueOf(remoteList[1]);
374 numberOfRemotesInBatch = Integer.valueOf(remoteList[2]);
377 while (numberOfRemotesProcessed < numberOfRemotes) {
378 for (int i = 1; i <= numberOfRemotesInBatch; i++) {
379 String remote = remoteList[2 + i];
381 // get remote commands
382 String[] commands = getRemoteCommands(remote, 0);
383 StringBuilder result = new StringBuilder();
384 int numberOfCommands = 0;
385 int numberOfCommandsInBatch = 0;
386 int numberOfCommandsProcessed = 0;
388 if (commands.length > 0) {
389 numberOfCommands = Integer.valueOf(commands[1]);
390 numberOfCommandsInBatch = Integer.valueOf(commands[2]);
391 numberOfCommandsProcessed = 0;
394 while (numberOfCommandsProcessed < numberOfCommands) {
395 for (int j = 1; j <= numberOfCommandsInBatch; j++) {
396 String command = commands[2 + j];
397 result.append(command);
398 numberOfCommandsProcessed++;
399 if (numberOfCommandsProcessed < numberOfCommands) {
404 if (numberOfCommandsProcessed < numberOfCommands) {
405 commands = getRemoteCommands(remote, numberOfCommandsProcessed);
406 if (commands.length == 0) {
409 numberOfCommandsInBatch = Integer.valueOf(commands[2]);
411 numberOfCommandsInBatch = 0;
415 logger.debug("The remote '{}' on '{}' supports '{}' commands: {}", remote, getThing().getUID(),
416 numberOfCommands, result.toString());
418 numberOfRemotesProcessed++;
421 if (numberOfRemotesProcessed < numberOfRemotes) {
422 remoteList = getRemoteList(numberOfRemotesProcessed);
423 if (remoteList.length == 0) {
426 numberOfRemotesInBatch = Integer.valueOf(remoteList[2]);
428 numberOfRemotesInBatch = 0;
436 private String[] getRemoteCommands(String remote, int index) {
437 String getCommands = "Agetcommands " + remote + "," + index + (char) 13;
438 ByteBuffer response = sendCommand(getCommands);
439 String[] commandList = new String[0];
441 if (response != null) {
442 String message = stripByteCount(response).split("\0")[0];
443 logger.trace("commands returned {}", message);
444 if (message != null) {
445 if (message.contains("COMMANDLIST")) {
446 commandList = message.split(",");
448 logger.debug("Received some non-compliant command ({})", message);
453 logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
454 getThing().getUID());
461 private String[] getRemoteList(int index) {
462 String getRemotes = "Agetremotes " + index + (char) 13;
463 ByteBuffer response = sendCommand(getRemotes);
464 String[] remoteList = new String[0];
466 if (response != null) {
467 String message = stripByteCount(response).split("\0")[0];
468 logger.trace("remotes returned {}", message);
469 if (message != null) {
470 if (message.contains("REMOTELIST")) {
471 remoteList = message.split(",");
473 logger.debug("Received some non-compliant command ({})", message);
478 logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
479 getThing().getUID());
486 public boolean sendIRcommand(IrCommand command, Led led) {
487 // construct the string we need to send to the IRtrans device
488 String output = packIRDBCommand(led, command);
492 ByteBuffer response = sendCommand(output);
494 if (response != null) {
495 String message = stripByteCount(response).split("\0")[0];
496 if (message != null && message.contains("RESULT OK")) {
499 logger.debug("Received an unexpected response from the IRtrans transceiver: '{}'", message);
510 private ByteBuffer sendCommand(String command) {
511 if (command != null) {
512 ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length);
514 byteBuffer.put(command.getBytes("ASCII"));
515 onWritable(byteBuffer);
516 Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
517 return onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), true);
518 } catch (UnsupportedEncodingException | NumberFormatException | InterruptedException e) {
519 Thread.currentThread().interrupt();
520 logger.debug("An exception occurred while sending a command to the IRtrans transceiver for '{}': {}",
521 getThing().getUID(), e.getMessage());
522 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
529 private Runnable pollingRunnable = new Runnable() {
535 if (socketChannel == null) {
536 previousConnectionState = false;
539 if (!previousConnectionState && socketChannel.isConnected()) {
540 previousConnectionState = true;
541 onConnectionResumed();
544 if (previousConnectionState && !socketChannel.isConnected()
545 && !socketChannel.isConnectionPending()) {
546 previousConnectionState = false;
550 if (!socketChannel.isConnectionPending() && !socketChannel.isConnected()) {
551 previousConnectionState = false;
552 logger.debug("Disconnecting '{}' because of a network error", getThing().getUID());
553 socketChannel.close();
554 Thread.sleep(1000 * ((BigDecimal) getConfig().get(RECONNECT_INTERVAL)).intValue());
555 establishConnection();
558 long stamp = System.currentTimeMillis();
559 if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS)))
560 .isReachable(((BigDecimal) getConfig().get(PING_TIME_OUT)).intValue())) {
562 "Ping timed out after '{}' milliseconds. Disconnecting '{}' because of a ping timeout",
563 System.currentTimeMillis() - stamp, getThing().getUID());
564 socketChannel.close();
568 ByteBuffer buffer = onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), false);
569 if (buffer != null && buffer.remaining() > 0) {
576 if (!Thread.currentThread().isInterrupted()) {
577 Thread.sleep(LISTENING_INTERVAL);
581 } catch (IOException e) {
582 logger.trace("An exception occurred while polling the transceiver : '{}'", e.getMessage(), e);
583 } catch (InterruptedException e) {
584 Thread.currentThread().interrupt();
591 protected void onAcceptable() {
595 selector.selectNow();
596 } catch (IOException e) {
597 logger.debug("An exception occurred while selecting: {}", e.getMessage());
598 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
601 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
602 while (it.hasNext()) {
603 SelectionKey selKey = it.next();
605 if (selKey.isValid()) {
606 if (selKey.isAcceptable() && selKey.equals(listenerKey)) {
608 SocketChannel newChannel = listenerChannel.accept();
609 newChannel.configureBlocking(false);
610 logger.trace("Received a connection request from '{}'", newChannel.getRemoteAddress());
612 synchronized (selector) {
614 newChannel.register(selector, newChannel.validOps());
616 } catch (IOException e) {
617 logger.debug("An exception occurred while accepting a connection on channel '{}': {}",
618 listenerChannel, e.getMessage());
619 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
629 protected void onConnectable() {
631 SocketChannel aSocketChannel = null;
633 synchronized (selector) {
634 selector.selectNow();
637 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
638 while (it.hasNext()) {
639 SelectionKey selKey = it.next();
641 if (selKey.isValid() && selKey.isConnectable()) {
642 aSocketChannel = (SocketChannel) selKey.channel();
643 aSocketChannel.finishConnect();
644 logger.trace("The channel for '{}' is connected", aSocketChannel.getRemoteAddress());
647 } catch (IOException | NoConnectionPendingException e) {
648 if (aSocketChannel != null) {
649 logger.debug("Disconnecting '{}' because of a socket error : '{}'", getThing().getUID(), e.getMessage(),
652 aSocketChannel.close();
653 } catch (IOException e1) {
654 logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
663 protected ByteBuffer onReadable(int bufferSize, boolean isSelective) {
666 synchronized (selector) {
668 selector.selectNow();
669 } catch (IOException e) {
670 logger.error("An exception occurred while selecting: {}", e.getMessage());
674 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
675 while (it.hasNext()) {
676 SelectionKey selKey = it.next();
678 if (selKey.isValid() && selKey.isReadable()) {
679 SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
681 if ((aSocketChannel.equals(socketChannel) && isSelective) || !isSelective) {
682 ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize);
683 int numberBytesRead = 0;
684 boolean error = false;
687 numberBytesRead = aSocketChannel.read(readBuffer);
688 } catch (NotYetConnectedException e) {
689 logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
690 if (!aSocketChannel.isConnectionPending()) {
693 } catch (IOException e) {
694 // If some other I/O error occurs
695 logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
699 if (numberBytesRead == -1) {
704 logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID());
706 aSocketChannel.close();
707 } catch (IOException e1) {
708 logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
725 protected void onWritable(ByteBuffer buffer) {
728 synchronized (selector) {
730 selector.selectNow();
731 } catch (IOException e) {
732 logger.error("An exception occurred while selecting: {}", e.getMessage());
736 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
737 while (it.hasNext()) {
738 SelectionKey selKey = it.next();
740 if (selKey.isValid() && selKey.isWritable()) {
741 SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
743 if (aSocketChannel.equals(socketChannel)) {
744 boolean error = false;
748 logger.trace("Sending '{}' on the channel '{}'->'{}'", new String(buffer.array()),
749 aSocketChannel.getLocalAddress(), aSocketChannel.getRemoteAddress());
750 aSocketChannel.write(buffer);
751 } catch (NotYetConnectedException e) {
752 logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
753 if (!aSocketChannel.isConnectionPending()) {
756 } catch (ClosedChannelException e) {
757 // If some other I/O error occurs
758 logger.warn("The channel for '{}' is closed: {}", aSocketChannel, e.getMessage());
760 } catch (IOException e) {
761 // If some other I/O error occurs
762 logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
768 aSocketChannel.close();
769 } catch (IOException e) {
770 logger.warn("An exception occurred while closing the channel '{}': {}", aSocketChannel,
782 protected void onRead(ByteBuffer byteBuffer) {
784 if (logger.isTraceEnabled()) {
785 logger.trace("Received bytebuffer : '{}'", HexUtils.bytesToHex(byteBuffer.array()));
787 int byteCount = getByteCount(byteBuffer);
789 while (byteCount > 0) {
790 byte[] message = new byte[byteCount];
791 byteBuffer.get(message, 0, byteCount);
793 if (logger.isTraceEnabled()) {
794 logger.trace("Received message : '{}'", HexUtils.bytesToHex(message));
797 String strippedBuffer = stripByteCount(ByteBuffer.wrap(message));
799 if (strippedBuffer != null) {
800 String strippedMessage = strippedBuffer.split("\0")[0];
802 // IRTrans devices return "RESULT OK" when it succeeds to emit an
804 if (strippedMessage.contains("RESULT OK")) {
805 parseOKMessage(strippedMessage);
808 // IRTrans devices return a string starting with RCV_HEX each time
809 // it captures an infrared sequence from a remote control
810 if (strippedMessage.contains("RCV_HEX")) {
811 parseHexMessage(strippedMessage);
814 // IRTrans devices return a string starting with RCV_COM each time
815 // it captures an infrared sequence from a remote control that is stored in the device's internal dB
816 if (strippedMessage.contains("RCV_COM")) {
817 parseIRDBMessage(strippedMessage);
820 byteCount = getByteCount(byteBuffer);
822 logger.warn("Received some non-compliant garbage '{}' - Parsing is skipped",
823 new String(byteBuffer.array()));
826 } catch (Exception e) {
827 logger.error("An exception occurred while reading bytebuffer '{}' : {}",
828 HexUtils.bytesToHex(byteBuffer.array()), e.getMessage(), e);
832 protected int getByteCount(ByteBuffer byteBuffer) {
833 String response = new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
834 response = StringUtils.chomp(response);
836 Matcher matcher = RESPONSE_PATTERN.matcher(response);
837 if (matcher.matches()) {
838 return Integer.parseInt(matcher.group(1));
844 protected String stripByteCount(ByteBuffer byteBuffer) {
845 String message = null;
847 String response = new String(byteBuffer.array(), 0, byteBuffer.limit());
848 response = StringUtils.chomp(response);
850 Matcher matcher = RESPONSE_PATTERN.matcher(response);
851 if (matcher.matches()) {
852 message = matcher.group(2);
858 protected void parseOKMessage(String message) {
859 // Nothing to do here
862 protected void parseHexMessage(String message) {
863 Matcher matcher = HEX_PATTERN.matcher(message);
865 if (matcher.matches()) {
866 String command = matcher.group(1);
868 IrCommand theCommand = null;
869 for (IrCommand aCommand : irCommands) {
870 if (aCommand.sequenceToHEXString().equals(command)) {
871 theCommand = aCommand;
876 if (theCommand != null) {
877 for (TransceiverStatusListener listener : transceiverStatusListeners) {
878 listener.onCommandReceived(this, theCommand);
881 logger.error("{} does not match any know infrared command", command);
884 logger.error("{} does not match the infrared message format '{}'", message, matcher.pattern());
888 protected void parseIRDBMessage(String message) {
889 Matcher matcher = IRDB_PATTERN.matcher(message);
891 if (matcher.matches()) {
892 IrCommand command = new IrCommand();
893 command.setRemote(matcher.group(1));
894 command.setCommand(matcher.group(2));
896 for (TransceiverStatusListener listener : transceiverStatusListeners) {
897 listener.onCommandReceived(this, command);
900 logger.error("{} does not match the IRDB infrared message format '{}'", message, matcher.pattern());
905 * "Pack" the infrared command so that it can be sent to the IRTrans device
908 * @param command the the command
909 * @return a string which is the full command to be sent to the device
911 protected String packIRDBCommand(Led led, IrCommand command) {
912 String output = new String();
915 output += command.getRemote();
917 output += command.getCommand();
919 output += led.toString();
927 * "Pack" the infrared command so that it can be sent to the IRTrans device
930 * @param command the the command
931 * @return a string which is the full command to be sent to the device
933 protected String packHexCommand(Led led, IrCommand command) {
934 String output = new String();
938 output += led.toString();
941 output += "H" + command.toHEXString();