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.openhab.binding.irtrans.internal.IRtransBindingConstants;
39 import org.openhab.binding.irtrans.internal.IRtransBindingConstants.Led;
40 import org.openhab.binding.irtrans.internal.IrCommand;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.Channel;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.openhab.core.util.HexUtils;
52 import org.openhab.core.util.StringUtils;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * The {@link EthernetBridgeHandler} is responsible for handling commands, which
58 * are sent to one of the channels.
60 * @author Karel Goderis - Initial contribution
63 public class EthernetBridgeHandler extends BaseBridgeHandler implements TransceiverStatusListener {
65 // List of Configuration constants
66 public static final String BUFFER_SIZE = "bufferSize";
67 public static final String IP_ADDRESS = "ipAddress";
68 public static final String IS_LISTENER = "isListener";
69 public static final String FIRMWARE_VERSION = "firmwareVersion";
70 public static final String LISTENER_PORT = "listenerPort";
71 public static final String MODE = "mode";
72 public static final String PING_TIME_OUT = "pingTimeOut";
73 public static final String PORT_NUMBER = "portNumber";
74 public static final String RECONNECT_INTERVAL = "reconnectInterval";
75 public static final String REFRESH_INTERVAL = "refreshInterval";
76 public static final String RESPONSE_TIME_OUT = "responseTimeOut";
77 public static final String COMMAND = "command";
78 public static final String LED = "led";
79 public static final String REMOTE = "remote";
80 public static final int LISTENING_INTERVAL = 100;
82 private static final Pattern RESPONSE_PATTERN = Pattern.compile("..(\\d{5}) (.*)", Pattern.DOTALL);
83 private static final Pattern HEX_PATTERN = Pattern.compile("RCV_HEX (.*)");
84 private static final Pattern IRDB_PATTERN = Pattern.compile("RCV_COM (.*),(.*),(.*),(.*)");
86 private Logger logger = LoggerFactory.getLogger(EthernetBridgeHandler.class);
88 private Selector selector;
89 private Thread pollingThread;
90 private SocketChannel socketChannel;
91 protected SelectionKey socketChannelKey;
92 protected ServerSocketChannel listenerChannel;
93 protected SelectionKey listenerKey;
94 protected boolean previousConnectionState;
95 private final Lock lock = new ReentrantLock();
97 private List<TransceiverStatusListener> transceiverStatusListeners = new CopyOnWriteArrayList<>();
100 * Data structure to store the infrared commands that are 'loaded' from the
101 * configuration files. Command loading from pre-defined configuration files is not supported
102 * (anymore), but the code is maintained in case this functionality is re-added in the future
104 protected final Collection<IrCommand> irCommands = new HashSet<>();
106 public EthernetBridgeHandler(Bridge bridge) {
108 // Nothing to do here
112 public void initialize() {
113 // register ourselves as a Transceiver Status Listener
114 registerTransceiverStatusListener(this);
117 selector = Selector.open();
118 } catch (IOException e) {
119 logger.debug("An exception occurred while registering the selector: '{}'", e.getMessage());
120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
123 if (selector != null) {
124 if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
125 if (pollingThread == null) {
126 pollingThread = new Thread(pollingRunnable, "OH-binding-" + getThing().getUID() + "-polling");
127 pollingThread.start();
130 logger.debug("Cannot connect to IRtrans Ethernet device. IP address or port number not set.");
131 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
132 "IP address or port number not set.");
135 if (getConfig().get(IS_LISTENER) != null) {
136 configureListener((String) getConfig().get(LISTENER_PORT));
142 public void dispose() {
143 unregisterTransceiverStatusListener(this);
146 if (socketChannel != null) {
147 socketChannel.close();
149 } catch (IOException e) {
150 logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
154 if (listenerChannel != null) {
155 listenerChannel.close();
157 } catch (IOException e) {
158 logger.warn("An exception occurred while closing the channel '{}': {}", listenerChannel, e.getMessage());
162 if (selector != null) {
165 } catch (IOException e) {
166 logger.debug("An exception occurred while closing the selector: '{}'", e.getMessage());
169 logger.debug("Stopping the IRtrans polling Thread for {}", getThing().getUID());
170 if (pollingThread != null) {
171 pollingThread.interrupt();
173 pollingThread.join();
174 } catch (InterruptedException e) {
175 Thread.currentThread().interrupt();
177 pollingThread = null;
181 public boolean registerTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) {
182 return transceiverStatusListeners.add(transceiverStatusListener);
185 public boolean unregisterTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) {
186 return transceiverStatusListeners.remove(transceiverStatusListener);
189 public void onConnectionLost() {
190 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
193 if (socketChannel != null) {
194 socketChannel.close();
196 } catch (IOException e) {
197 logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
200 establishConnection();
203 public void onConnectionResumed() {
204 configureTransceiver();
205 updateStatus(ThingStatus.ONLINE);
208 private void establishConnection() {
211 if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
213 socketChannel = SocketChannel.open();
214 socketChannel.socket().setKeepAlive(true);
215 socketChannel.configureBlocking(false);
217 synchronized (selector) {
219 int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT;
220 socketChannelKey = socketChannel.register(selector, interestSet);
223 InetSocketAddress remoteAddress = new InetSocketAddress((String) getConfig().get(IP_ADDRESS),
224 ((BigDecimal) getConfig().get(PORT_NUMBER)).intValue());
225 socketChannel.connect(remoteAddress);
226 } catch (IOException e) {
227 logger.debug("An exception occurred while connecting to '{}:{}' : {}", getConfig().get(IP_ADDRESS),
228 ((BigDecimal) getConfig().get(PORT_NUMBER)).intValue(), e.getMessage());
229 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
233 Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
234 } catch (NumberFormatException | InterruptedException e) {
235 Thread.currentThread().interrupt();
236 logger.debug("An exception occurred while putting a thread to sleep: '{}'", e.getMessage());
237 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
247 public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command) {
248 logger.debug("Received infrared command '{},{}' for thing '{}'", command.getRemote(), command.getCommand(),
249 this.getThing().getUID());
251 for (Channel channel : getThing().getChannels()) {
252 Configuration channelConfiguration = channel.getConfiguration();
254 if (channel.getChannelTypeUID() != null
255 && channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
256 IrCommand thingCompatibleCommand = new IrCommand();
257 thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
258 thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
260 if (command.matches(thingCompatibleCommand)) {
261 StringType stringType = new StringType(command.getRemote() + "," + command.getCommand());
262 logger.debug("Received a matching infrared command '{}' for channel '{}'", stringType,
264 updateState(channel.getUID(), stringType);
271 public void handleCommand(ChannelUID channelUID, Command command) {
272 if (!(command instanceof RefreshType)) {
273 Channel channel = this.getThing().getChannel(channelUID.getId());
274 if (channel != null) {
275 Configuration channelConfiguration = channel.getConfiguration();
276 if (channel.getChannelTypeUID() != null
277 && channel.getChannelTypeUID().getId().equals(IRtransBindingConstants.BLASTER_CHANNEL_TYPE)) {
278 if (command instanceof StringType) {
279 String[] remoteCommand = command.toString().split(",", 2);
280 if (remoteCommand.length < 2) {
281 logger.warn("Ignoring invalid command '{}'", command);
285 IrCommand ircommand = new IrCommand();
286 ircommand.setRemote(remoteCommand[0]);
287 ircommand.setCommand(remoteCommand[1]);
289 IrCommand thingCompatibleCommand = new IrCommand();
290 thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
291 thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
293 if (ircommand.matches(thingCompatibleCommand)) {
294 if (sendIRcommand(ircommand, Led.get((String) channelConfiguration.get(LED)))) {
295 logger.debug("Sent a matching infrared command '{}' for channel '{}'", command,
299 "An error occured whilst sending the infrared command '{}' for Channel '{}'",
300 command, channelUID);
305 if (channel.getAcceptedItemType() != null
306 && channel.getAcceptedItemType().equals(IRtransBindingConstants.RECEIVER_CHANNEL_TYPE)) {
307 logger.warn("Receivers can only receive infrared commands, not send them");
313 private void configureListener(String listenerPort) {
315 listenerChannel = ServerSocketChannel.open();
316 listenerChannel.socket().bind(new InetSocketAddress(Integer.parseInt(listenerPort)));
317 listenerChannel.configureBlocking(false);
319 logger.info("Listening for incoming connections on {}", listenerChannel.getLocalAddress());
321 synchronized (selector) {
324 listenerKey = listenerChannel.register(selector, SelectionKey.OP_ACCEPT);
325 } catch (ClosedChannelException e1) {
326 logger.debug("An exception occurred while registering a selector: '{}'", e1.getMessage());
327 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e1.getMessage());
330 } catch (IOException e3) {
332 "An exception occurred while creating configuring the listener channel on port number {}: '{}'",
333 Integer.parseInt(listenerPort), e3.getMessage());
337 protected void configureTransceiver() {
340 String putInASCIImode = "ASCI";
341 ByteBuffer response = sendCommand(putInASCIImode);
343 String getFirmwareVersion = "Aver" + (char) 13;
344 response = sendCommand(getFirmwareVersion);
346 if (response != null) {
347 String message = stripByteCount(response).split("\0")[0];
348 if (message.contains("VERSION")) {
349 logger.info("'{}' matches an IRtrans device with firmware {}", getThing().getUID(), message);
350 getConfig().put(FIRMWARE_VERSION, message);
352 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.contains("COMMANDLIST")) {
445 commandList = message.split(",");
447 logger.debug("Received some non-compliant command ({})", message);
451 logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
452 getThing().getUID());
458 private String[] getRemoteList(int index) {
459 String getRemotes = "Agetremotes " + index + (char) 13;
460 ByteBuffer response = sendCommand(getRemotes);
461 String[] remoteList = new String[0];
463 if (response != null) {
464 String message = stripByteCount(response).split("\0")[0];
465 logger.trace("remotes returned {}", message);
466 if (message.contains("REMOTELIST")) {
467 remoteList = message.split(",");
469 logger.debug("Received some non-compliant command ({})", message);
473 logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
474 getThing().getUID());
481 public boolean sendIRcommand(IrCommand command, Led led) {
482 // construct the string we need to send to the IRtrans device
483 String output = packIRDBCommand(led, command);
487 ByteBuffer response = sendCommand(output);
489 if (response != null) {
490 String message = stripByteCount(response).split("\0")[0];
491 if (message.contains("RESULT OK")) {
494 logger.debug("Received an unexpected response from the IRtrans transceiver: '{}'", message);
505 private ByteBuffer sendCommand(String command) {
506 if (command != null) {
507 ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length);
509 byteBuffer.put(command.getBytes("ASCII"));
510 onWritable(byteBuffer);
511 Thread.sleep(((BigDecimal) getConfig().get(RESPONSE_TIME_OUT)).intValue());
512 return onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), true);
513 } catch (UnsupportedEncodingException | NumberFormatException | InterruptedException e) {
514 Thread.currentThread().interrupt();
515 logger.debug("An exception occurred while sending a command to the IRtrans transceiver for '{}': {}",
516 getThing().getUID(), e.getMessage());
517 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
524 private Runnable pollingRunnable = new Runnable() {
530 if (socketChannel == null) {
531 previousConnectionState = false;
534 if (!previousConnectionState && socketChannel.isConnected()) {
535 previousConnectionState = true;
536 onConnectionResumed();
539 if (previousConnectionState && !socketChannel.isConnected()
540 && !socketChannel.isConnectionPending()) {
541 previousConnectionState = false;
545 if (!socketChannel.isConnectionPending() && !socketChannel.isConnected()) {
546 previousConnectionState = false;
547 logger.debug("Disconnecting '{}' because of a network error", getThing().getUID());
548 socketChannel.close();
549 Thread.sleep(1000 * ((BigDecimal) getConfig().get(RECONNECT_INTERVAL)).intValue());
550 establishConnection();
553 long stamp = System.currentTimeMillis();
554 if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS)))
555 .isReachable(((BigDecimal) getConfig().get(PING_TIME_OUT)).intValue())) {
557 "Ping timed out after '{}' milliseconds. Disconnecting '{}' because of a ping timeout",
558 System.currentTimeMillis() - stamp, getThing().getUID());
559 socketChannel.close();
563 ByteBuffer buffer = onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), false);
564 if (buffer != null && buffer.remaining() > 0) {
571 if (!Thread.currentThread().isInterrupted()) {
572 Thread.sleep(LISTENING_INTERVAL);
576 } catch (IOException e) {
577 logger.trace("An exception occurred while polling the transceiver : '{}'", e.getMessage(), e);
578 } catch (InterruptedException e) {
579 Thread.currentThread().interrupt();
586 protected void onAcceptable() {
590 selector.selectNow();
591 } catch (IOException e) {
592 logger.debug("An exception occurred while selecting: {}", e.getMessage());
593 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
596 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
597 while (it.hasNext()) {
598 SelectionKey selKey = it.next();
600 if (selKey.isValid()) {
601 if (selKey.isAcceptable() && selKey.equals(listenerKey)) {
603 SocketChannel newChannel = listenerChannel.accept();
604 newChannel.configureBlocking(false);
605 logger.trace("Received a connection request from '{}'", newChannel.getRemoteAddress());
607 synchronized (selector) {
609 newChannel.register(selector, newChannel.validOps());
611 } catch (IOException e) {
612 logger.debug("An exception occurred while accepting a connection on channel '{}': {}",
613 listenerChannel, e.getMessage());
614 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
624 protected void onConnectable() {
626 SocketChannel aSocketChannel = null;
628 synchronized (selector) {
629 selector.selectNow();
632 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
633 while (it.hasNext()) {
634 SelectionKey selKey = it.next();
636 if (selKey.isValid() && selKey.isConnectable()) {
637 aSocketChannel = (SocketChannel) selKey.channel();
638 aSocketChannel.finishConnect();
639 logger.trace("The channel for '{}' is connected", aSocketChannel.getRemoteAddress());
642 } catch (IOException | NoConnectionPendingException e) {
643 if (aSocketChannel != null) {
644 logger.debug("Disconnecting '{}' because of a socket error : '{}'", getThing().getUID(), e.getMessage(),
647 aSocketChannel.close();
648 } catch (IOException e1) {
649 logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
658 protected ByteBuffer onReadable(int bufferSize, boolean isSelective) {
661 synchronized (selector) {
663 selector.selectNow();
664 } catch (IOException e) {
665 logger.error("An exception occurred while selecting: {}", e.getMessage());
669 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
670 while (it.hasNext()) {
671 SelectionKey selKey = it.next();
673 if (selKey.isValid() && selKey.isReadable()) {
674 SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
676 if ((aSocketChannel.equals(socketChannel) && isSelective) || !isSelective) {
677 ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize);
678 int numberBytesRead = 0;
679 boolean error = false;
682 numberBytesRead = aSocketChannel.read(readBuffer);
683 } catch (NotYetConnectedException e) {
684 logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
685 if (!aSocketChannel.isConnectionPending()) {
688 } catch (IOException e) {
689 // If some other I/O error occurs
690 logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
694 if (numberBytesRead == -1) {
699 logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID());
701 aSocketChannel.close();
702 } catch (IOException e1) {
703 logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
720 protected void onWritable(ByteBuffer buffer) {
723 synchronized (selector) {
725 selector.selectNow();
726 } catch (IOException e) {
727 logger.error("An exception occurred while selecting: {}", e.getMessage());
731 Iterator<SelectionKey> it = selector.selectedKeys().iterator();
732 while (it.hasNext()) {
733 SelectionKey selKey = it.next();
735 if (selKey.isValid() && selKey.isWritable()) {
736 SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
738 if (aSocketChannel.equals(socketChannel)) {
739 boolean error = false;
743 logger.trace("Sending '{}' on the channel '{}'->'{}'", new String(buffer.array()),
744 aSocketChannel.getLocalAddress(), aSocketChannel.getRemoteAddress());
745 aSocketChannel.write(buffer);
746 } catch (NotYetConnectedException e) {
747 logger.warn("The channel '{}' is not yet connected: {}", aSocketChannel, e.getMessage());
748 if (!aSocketChannel.isConnectionPending()) {
751 } catch (ClosedChannelException e) {
752 // If some other I/O error occurs
753 logger.warn("The channel for '{}' is closed: {}", aSocketChannel, e.getMessage());
755 } catch (IOException e) {
756 // If some other I/O error occurs
757 logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
763 aSocketChannel.close();
764 } catch (IOException e) {
765 logger.warn("An exception occurred while closing the channel '{}': {}", aSocketChannel,
777 protected void onRead(ByteBuffer byteBuffer) {
779 if (logger.isTraceEnabled()) {
780 logger.trace("Received bytebuffer : '{}'", HexUtils.bytesToHex(byteBuffer.array()));
782 int byteCount = getByteCount(byteBuffer);
784 while (byteCount > 0) {
785 byte[] message = new byte[byteCount];
786 byteBuffer.get(message, 0, byteCount);
788 if (logger.isTraceEnabled()) {
789 logger.trace("Received message : '{}'", HexUtils.bytesToHex(message));
792 String strippedBuffer = stripByteCount(ByteBuffer.wrap(message));
794 if (strippedBuffer != null) {
795 String strippedMessage = strippedBuffer.split("\0")[0];
797 // IRTrans devices return "RESULT OK" when it succeeds to emit an
799 if (strippedMessage.contains("RESULT OK")) {
800 parseOKMessage(strippedMessage);
803 // IRTrans devices return a string starting with RCV_HEX each time
804 // it captures an infrared sequence from a remote control
805 if (strippedMessage.contains("RCV_HEX")) {
806 parseHexMessage(strippedMessage);
809 // IRTrans devices return a string starting with RCV_COM each time
810 // it captures an infrared sequence from a remote control that is stored in the device's internal dB
811 if (strippedMessage.contains("RCV_COM")) {
812 parseIRDBMessage(strippedMessage);
815 byteCount = getByteCount(byteBuffer);
817 logger.warn("Received some non-compliant garbage '{}' - Parsing is skipped",
818 new String(byteBuffer.array()));
821 } catch (Exception e) {
822 logger.error("An exception occurred while reading bytebuffer '{}' : {}",
823 HexUtils.bytesToHex(byteBuffer.array()), e.getMessage(), e);
827 protected int getByteCount(ByteBuffer byteBuffer) {
828 String response = new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
829 response = StringUtils.chomp(response);
831 Matcher matcher = RESPONSE_PATTERN.matcher(response);
832 if (matcher.matches()) {
833 return Integer.parseInt(matcher.group(1));
839 protected String stripByteCount(ByteBuffer byteBuffer) {
840 String message = null;
842 String response = new String(byteBuffer.array(), 0, byteBuffer.limit());
843 response = StringUtils.chomp(response);
845 Matcher matcher = RESPONSE_PATTERN.matcher(response);
846 if (matcher.matches()) {
847 message = matcher.group(2);
853 protected void parseOKMessage(String message) {
854 // Nothing to do here
857 protected void parseHexMessage(String message) {
858 Matcher matcher = HEX_PATTERN.matcher(message);
860 if (matcher.matches()) {
861 String command = matcher.group(1);
863 IrCommand theCommand = null;
864 for (IrCommand aCommand : irCommands) {
865 if (aCommand.sequenceToHEXString().equals(command)) {
866 theCommand = aCommand;
871 if (theCommand != null) {
872 for (TransceiverStatusListener listener : transceiverStatusListeners) {
873 listener.onCommandReceived(this, theCommand);
876 logger.error("{} does not match any know infrared command", command);
879 logger.error("{} does not match the infrared message format '{}'", message, matcher.pattern());
883 protected void parseIRDBMessage(String message) {
884 Matcher matcher = IRDB_PATTERN.matcher(message);
886 if (matcher.matches()) {
887 IrCommand command = new IrCommand();
888 command.setRemote(matcher.group(1));
889 command.setCommand(matcher.group(2));
891 for (TransceiverStatusListener listener : transceiverStatusListeners) {
892 listener.onCommandReceived(this, command);
895 logger.error("{} does not match the IRDB infrared message format '{}'", message, matcher.pattern());
900 * "Pack" the infrared command so that it can be sent to the IRTrans device
903 * @param command the the command
904 * @return a string which is the full command to be sent to the device
906 protected String packIRDBCommand(Led led, IrCommand command) {
907 String output = new String();
910 output += command.getRemote();
912 output += command.getCommand();
914 output += led.toString();
922 * "Pack" the infrared command so that it can be sent to the IRTrans device
925 * @param command the the command
926 * @return a string which is the full command to be sent to the device
928 protected String packHexCommand(Led led, IrCommand command) {
929 String output = new String();
933 output += led.toString();
936 output += "H" + command.toHEXString();