]> git.basschouten.com Git - openhab-addons.git/blob
825616e8d668bfd155242cf91fc52ef9d2cba353
[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.irtrans.internal.handler;
14
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;
37
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;
55
56 /**
57  * The {@link EthernetBridgeHandler} is responsible for handling commands, which
58  * are sent to one of the channels.
59  *
60  * @author Karel Goderis - Initial contribution
61  *
62  */
63 public class EthernetBridgeHandler extends BaseBridgeHandler implements TransceiverStatusListener {
64
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;
81
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 (.*),(.*),(.*),(.*)");
85
86     private Logger logger = LoggerFactory.getLogger(EthernetBridgeHandler.class);
87
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();
96
97     private List<TransceiverStatusListener> transceiverStatusListeners = new CopyOnWriteArrayList<>();
98
99     /**
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
103      **/
104     protected final Collection<IrCommand> irCommands = new HashSet<>();
105
106     public EthernetBridgeHandler(Bridge bridge) {
107         super(bridge);
108         // Nothing to do here
109     }
110
111     @Override
112     public void initialize() {
113         // register ourselves as a Transceiver Status Listener
114         registerTransceiverStatusListener(this);
115
116         try {
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());
121         }
122
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();
128                 }
129             } else {
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.");
133             }
134
135             if (getConfig().get(IS_LISTENER) != null) {
136                 configureListener((String) getConfig().get(LISTENER_PORT));
137             }
138         }
139     }
140
141     @Override
142     public void dispose() {
143         unregisterTransceiverStatusListener(this);
144
145         try {
146             if (socketChannel != null) {
147                 socketChannel.close();
148             }
149         } catch (IOException e) {
150             logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
151         }
152
153         try {
154             if (listenerChannel != null) {
155                 listenerChannel.close();
156             }
157         } catch (IOException e) {
158             logger.warn("An exception occurred while closing the channel '{}': {}", listenerChannel, e.getMessage());
159         }
160
161         try {
162             if (selector != null) {
163                 selector.close();
164             }
165         } catch (IOException e) {
166             logger.debug("An exception occurred while closing the selector: '{}'", e.getMessage());
167         }
168
169         logger.debug("Stopping the IRtrans polling Thread for {}", getThing().getUID());
170         if (pollingThread != null) {
171             pollingThread.interrupt();
172             try {
173                 pollingThread.join();
174             } catch (InterruptedException e) {
175                 Thread.currentThread().interrupt();
176             }
177             pollingThread = null;
178         }
179     }
180
181     public boolean registerTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) {
182         return transceiverStatusListeners.add(transceiverStatusListener);
183     }
184
185     public boolean unregisterTransceiverStatusListener(TransceiverStatusListener transceiverStatusListener) {
186         return transceiverStatusListeners.remove(transceiverStatusListener);
187     }
188
189     public void onConnectionLost() {
190         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
191
192         try {
193             if (socketChannel != null) {
194                 socketChannel.close();
195             }
196         } catch (IOException e) {
197             logger.warn("An exception occurred while closing the channel '{}': {}", socketChannel, e.getMessage());
198         }
199
200         establishConnection();
201     }
202
203     public void onConnectionResumed() {
204         configureTransceiver();
205         updateStatus(ThingStatus.ONLINE);
206     }
207
208     private void establishConnection() {
209         lock.lock();
210         try {
211             if (getConfig().get(IP_ADDRESS) != null && getConfig().get(PORT_NUMBER) != null) {
212                 try {
213                     socketChannel = SocketChannel.open();
214                     socketChannel.socket().setKeepAlive(true);
215                     socketChannel.configureBlocking(false);
216
217                     synchronized (selector) {
218                         selector.wakeup();
219                         int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT;
220                         socketChannelKey = socketChannel.register(selector, interestSet);
221                     }
222
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());
230                 }
231
232                 try {
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());
238                 }
239                 onConnectable();
240             }
241         } finally {
242             lock.unlock();
243         }
244     }
245
246     @Override
247     public void onCommandReceived(EthernetBridgeHandler bridge, IrCommand command) {
248         logger.debug("Received infrared command '{},{}' for thing '{}'", command.getRemote(), command.getCommand(),
249                 this.getThing().getUID());
250
251         for (Channel channel : getThing().getChannels()) {
252             Configuration channelConfiguration = channel.getConfiguration();
253
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));
259
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,
263                             channel.getUID());
264                     updateState(channel.getUID(), stringType);
265                 }
266             }
267         }
268     }
269
270     @Override
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);
282                             return;
283                         }
284
285                         IrCommand ircommand = new IrCommand();
286                         ircommand.setRemote(remoteCommand[0]);
287                         ircommand.setCommand(remoteCommand[1]);
288
289                         IrCommand thingCompatibleCommand = new IrCommand();
290                         thingCompatibleCommand.setRemote((String) channelConfiguration.get(REMOTE));
291                         thingCompatibleCommand.setCommand((String) channelConfiguration.get(COMMAND));
292
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,
296                                         channelUID);
297                             } else {
298                                 logger.warn(
299                                         "An error occured whilst sending the infrared command '{}' for Channel '{}'",
300                                         command, channelUID);
301                             }
302                         }
303                     }
304                 }
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");
308                 }
309             }
310         }
311     }
312
313     private void configureListener(String listenerPort) {
314         try {
315             listenerChannel = ServerSocketChannel.open();
316             listenerChannel.socket().bind(new InetSocketAddress(Integer.parseInt(listenerPort)));
317             listenerChannel.configureBlocking(false);
318
319             logger.info("Listening for incoming connections on {}", listenerChannel.getLocalAddress());
320
321             synchronized (selector) {
322                 selector.wakeup();
323                 try {
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());
328                 }
329             }
330         } catch (IOException e3) {
331             logger.error(
332                     "An exception occurred while creating configuring the listener channel on port number {}: '{}'",
333                     Integer.parseInt(listenerPort), e3.getMessage());
334         }
335     }
336
337     protected void configureTransceiver() {
338         lock.lock();
339         try {
340             String putInASCIImode = "ASCI";
341             ByteBuffer response = sendCommand(putInASCIImode);
342
343             String getFirmwareVersion = "Aver" + (char) 13;
344             response = sendCommand(getFirmwareVersion);
345
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);
351                 } else {
352                     logger.debug("Received some non-compliant garbage ({})", message);
353                     onConnectionLost();
354                 }
355             } else {
356                 try {
357                     logger.debug("Did not receive an answer from the IRtrans transceiver '{}' - Parsing is skipped",
358                             socketChannel.getRemoteAddress());
359                     onConnectionLost();
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());
363                 }
364             }
365
366             int numberOfRemotes = 0;
367             int numberOfRemotesProcessed = 0;
368             int numberOfRemotesInBatch = 0;
369             String[] remoteList = getRemoteList(0);
370
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]);
375             }
376
377             while (numberOfRemotesProcessed < numberOfRemotes) {
378                 for (int i = 1; i <= numberOfRemotesInBatch; i++) {
379                     String remote = remoteList[2 + i];
380
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;
387
388                     if (commands.length > 0) {
389                         numberOfCommands = Integer.valueOf(commands[1]);
390                         numberOfCommandsInBatch = Integer.valueOf(commands[2]);
391                         numberOfCommandsProcessed = 0;
392                     }
393
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) {
400                                 result.append(", ");
401                             }
402                         }
403
404                         if (numberOfCommandsProcessed < numberOfCommands) {
405                             commands = getRemoteCommands(remote, numberOfCommandsProcessed);
406                             if (commands.length == 0) {
407                                 break;
408                             }
409                             numberOfCommandsInBatch = Integer.valueOf(commands[2]);
410                         } else {
411                             numberOfCommandsInBatch = 0;
412                         }
413                     }
414
415                     logger.debug("The remote '{}' on '{}' supports '{}' commands: {}", remote, getThing().getUID(),
416                             numberOfCommands, result.toString());
417
418                     numberOfRemotesProcessed++;
419                 }
420
421                 if (numberOfRemotesProcessed < numberOfRemotes) {
422                     remoteList = getRemoteList(numberOfRemotesProcessed);
423                     if (remoteList.length == 0) {
424                         break;
425                     }
426                     numberOfRemotesInBatch = Integer.valueOf(remoteList[2]);
427                 } else {
428                     numberOfRemotesInBatch = 0;
429                 }
430             }
431         } finally {
432             lock.unlock();
433         }
434     }
435
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];
440
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(",");
446             } else {
447                 logger.debug("Received some non-compliant command ({})", message);
448                 onConnectionLost();
449             }
450         } else {
451             logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
452                     getThing().getUID());
453             onConnectionLost();
454         }
455         return commandList;
456     }
457
458     private String[] getRemoteList(int index) {
459         String getRemotes = "Agetremotes " + index + (char) 13;
460         ByteBuffer response = sendCommand(getRemotes);
461         String[] remoteList = new String[0];
462
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(",");
468             } else {
469                 logger.debug("Received some non-compliant command ({})", message);
470                 onConnectionLost();
471             }
472         } else {
473             logger.debug("Did not receive an answer from the IRtrans transceiver for '{}' - Parsing is skipped",
474                     getThing().getUID());
475             onConnectionLost();
476         }
477
478         return remoteList;
479     }
480
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);
484
485         lock.lock();
486         try {
487             ByteBuffer response = sendCommand(output);
488
489             if (response != null) {
490                 String message = stripByteCount(response).split("\0")[0];
491                 if (message.contains("RESULT OK")) {
492                     return true;
493                 } else {
494                     logger.debug("Received an unexpected response from the IRtrans transceiver: '{}'", message);
495                     return false;
496                 }
497             }
498         } finally {
499             lock.unlock();
500         }
501
502         return false;
503     }
504
505     private ByteBuffer sendCommand(String command) {
506         if (command != null) {
507             ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length);
508             try {
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());
518             }
519         }
520
521         return null;
522     }
523
524     private Runnable pollingRunnable = new Runnable() {
525
526         @Override
527         public void run() {
528             while (true) {
529                 try {
530                     if (socketChannel == null) {
531                         previousConnectionState = false;
532                         onConnectionLost();
533                     } else {
534                         if (!previousConnectionState && socketChannel.isConnected()) {
535                             previousConnectionState = true;
536                             onConnectionResumed();
537                         }
538
539                         if (previousConnectionState && !socketChannel.isConnected()
540                                 && !socketChannel.isConnectionPending()) {
541                             previousConnectionState = false;
542                             onConnectionLost();
543                         }
544
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();
551                         }
552
553                         long stamp = System.currentTimeMillis();
554                         if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS)))
555                                 .isReachable(((BigDecimal) getConfig().get(PING_TIME_OUT)).intValue())) {
556                             logger.debug(
557                                     "Ping timed out after '{}' milliseconds. Disconnecting '{}' because of a ping timeout",
558                                     System.currentTimeMillis() - stamp, getThing().getUID());
559                             socketChannel.close();
560                         }
561
562                         onConnectable();
563                         ByteBuffer buffer = onReadable(((BigDecimal) getConfig().get(BUFFER_SIZE)).intValue(), false);
564                         if (buffer != null && buffer.remaining() > 0) {
565                             onRead(buffer);
566                         }
567                     }
568
569                     onAcceptable();
570
571                     if (!Thread.currentThread().isInterrupted()) {
572                         Thread.sleep(LISTENING_INTERVAL);
573                     } else {
574                         return;
575                     }
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();
580                     return;
581                 }
582             }
583         }
584     };
585
586     protected void onAcceptable() {
587         lock.lock();
588         try {
589             try {
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());
594             }
595
596             Iterator<SelectionKey> it = selector.selectedKeys().iterator();
597             while (it.hasNext()) {
598                 SelectionKey selKey = it.next();
599                 it.remove();
600                 if (selKey.isValid()) {
601                     if (selKey.isAcceptable() && selKey.equals(listenerKey)) {
602                         try {
603                             SocketChannel newChannel = listenerChannel.accept();
604                             newChannel.configureBlocking(false);
605                             logger.trace("Received a connection request from '{}'", newChannel.getRemoteAddress());
606
607                             synchronized (selector) {
608                                 selector.wakeup();
609                                 newChannel.register(selector, newChannel.validOps());
610                             }
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());
615                         }
616                     }
617                 }
618             }
619         } finally {
620             lock.unlock();
621         }
622     }
623
624     protected void onConnectable() {
625         lock.lock();
626         SocketChannel aSocketChannel = null;
627         try {
628             synchronized (selector) {
629                 selector.selectNow();
630             }
631
632             Iterator<SelectionKey> it = selector.selectedKeys().iterator();
633             while (it.hasNext()) {
634                 SelectionKey selKey = it.next();
635                 it.remove();
636                 if (selKey.isValid() && selKey.isConnectable()) {
637                     aSocketChannel = (SocketChannel) selKey.channel();
638                     aSocketChannel.finishConnect();
639                     logger.trace("The channel for '{}' is connected", aSocketChannel.getRemoteAddress());
640                 }
641             }
642         } catch (IOException | NoConnectionPendingException e) {
643             if (aSocketChannel != null) {
644                 logger.debug("Disconnecting '{}' because of a socket error : '{}'", getThing().getUID(), e.getMessage(),
645                         e);
646                 try {
647                     aSocketChannel.close();
648                 } catch (IOException e1) {
649                     logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
650                             e1.getMessage());
651                 }
652             }
653         } finally {
654             lock.unlock();
655         }
656     }
657
658     protected ByteBuffer onReadable(int bufferSize, boolean isSelective) {
659         lock.lock();
660         try {
661             synchronized (selector) {
662                 try {
663                     selector.selectNow();
664                 } catch (IOException e) {
665                     logger.error("An exception occurred while selecting: {}", e.getMessage());
666                 }
667             }
668
669             Iterator<SelectionKey> it = selector.selectedKeys().iterator();
670             while (it.hasNext()) {
671                 SelectionKey selKey = it.next();
672                 it.remove();
673                 if (selKey.isValid() && selKey.isReadable()) {
674                     SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
675
676                     if ((aSocketChannel.equals(socketChannel) && isSelective) || !isSelective) {
677                         ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize);
678                         int numberBytesRead = 0;
679                         boolean error = false;
680
681                         try {
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()) {
686                                 error = true;
687                             }
688                         } catch (IOException e) {
689                             // If some other I/O error occurs
690                             logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
691                             error = true;
692                         }
693
694                         if (numberBytesRead == -1) {
695                             error = true;
696                         }
697
698                         if (error) {
699                             logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID());
700                             try {
701                                 aSocketChannel.close();
702                             } catch (IOException e1) {
703                                 logger.debug("An exception occurred while closing the channel '{}': {}", socketChannel,
704                                         e1.getMessage());
705                             }
706                         } else {
707                             readBuffer.flip();
708                             return readBuffer;
709                         }
710                     }
711                 }
712             }
713
714             return null;
715         } finally {
716             lock.unlock();
717         }
718     }
719
720     protected void onWritable(ByteBuffer buffer) {
721         lock.lock();
722         try {
723             synchronized (selector) {
724                 try {
725                     selector.selectNow();
726                 } catch (IOException e) {
727                     logger.error("An exception occurred while selecting: {}", e.getMessage());
728                 }
729             }
730
731             Iterator<SelectionKey> it = selector.selectedKeys().iterator();
732             while (it.hasNext()) {
733                 SelectionKey selKey = it.next();
734                 it.remove();
735                 if (selKey.isValid() && selKey.isWritable()) {
736                     SocketChannel aSocketChannel = (SocketChannel) selKey.channel();
737
738                     if (aSocketChannel.equals(socketChannel)) {
739                         boolean error = false;
740
741                         buffer.rewind();
742                         try {
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()) {
749                                 error = true;
750                             }
751                         } catch (ClosedChannelException e) {
752                             // If some other I/O error occurs
753                             logger.warn("The channel for '{}' is closed: {}", aSocketChannel, e.getMessage());
754                             error = true;
755                         } catch (IOException e) {
756                             // If some other I/O error occurs
757                             logger.warn("An IO exception occured on channel '{}': {}", aSocketChannel, e.getMessage());
758                             error = true;
759                         }
760
761                         if (error) {
762                             try {
763                                 aSocketChannel.close();
764                             } catch (IOException e) {
765                                 logger.warn("An exception occurred while closing the channel '{}': {}", aSocketChannel,
766                                         e.getMessage());
767                             }
768                         }
769                     }
770                 }
771             }
772         } finally {
773             lock.unlock();
774         }
775     }
776
777     protected void onRead(ByteBuffer byteBuffer) {
778         try {
779             if (logger.isTraceEnabled()) {
780                 logger.trace("Received bytebuffer : '{}'", HexUtils.bytesToHex(byteBuffer.array()));
781             }
782             int byteCount = getByteCount(byteBuffer);
783
784             while (byteCount > 0) {
785                 byte[] message = new byte[byteCount];
786                 byteBuffer.get(message, 0, byteCount);
787
788                 if (logger.isTraceEnabled()) {
789                     logger.trace("Received message : '{}'", HexUtils.bytesToHex(message));
790                 }
791
792                 String strippedBuffer = stripByteCount(ByteBuffer.wrap(message));
793
794                 if (strippedBuffer != null) {
795                     String strippedMessage = strippedBuffer.split("\0")[0];
796
797                     // IRTrans devices return "RESULT OK" when it succeeds to emit an
798                     // infrared sequence
799                     if (strippedMessage.contains("RESULT OK")) {
800                         parseOKMessage(strippedMessage);
801                     }
802
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);
807                     }
808
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);
813                     }
814
815                     byteCount = getByteCount(byteBuffer);
816                 } else {
817                     logger.warn("Received some non-compliant garbage '{}' - Parsing is skipped",
818                             new String(byteBuffer.array()));
819                 }
820             }
821         } catch (Exception e) {
822             logger.error("An exception occurred while reading bytebuffer '{}' : {}",
823                     HexUtils.bytesToHex(byteBuffer.array()), e.getMessage(), e);
824         }
825     }
826
827     protected int getByteCount(ByteBuffer byteBuffer) {
828         String response = new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
829         response = StringUtils.chomp(response);
830
831         Matcher matcher = RESPONSE_PATTERN.matcher(response);
832         if (matcher.matches()) {
833             return Integer.parseInt(matcher.group(1));
834         }
835
836         return 0;
837     }
838
839     protected String stripByteCount(ByteBuffer byteBuffer) {
840         String message = null;
841
842         String response = new String(byteBuffer.array(), 0, byteBuffer.limit());
843         response = StringUtils.chomp(response);
844
845         Matcher matcher = RESPONSE_PATTERN.matcher(response);
846         if (matcher.matches()) {
847             message = matcher.group(2);
848         }
849
850         return message;
851     }
852
853     protected void parseOKMessage(String message) {
854         // Nothing to do here
855     }
856
857     protected void parseHexMessage(String message) {
858         Matcher matcher = HEX_PATTERN.matcher(message);
859
860         if (matcher.matches()) {
861             String command = matcher.group(1);
862
863             IrCommand theCommand = null;
864             for (IrCommand aCommand : irCommands) {
865                 if (aCommand.sequenceToHEXString().equals(command)) {
866                     theCommand = aCommand;
867                     break;
868                 }
869             }
870
871             if (theCommand != null) {
872                 for (TransceiverStatusListener listener : transceiverStatusListeners) {
873                     listener.onCommandReceived(this, theCommand);
874                 }
875             } else {
876                 logger.error("{} does not match any know infrared command", command);
877             }
878         } else {
879             logger.error("{} does not match the infrared message format '{}'", message, matcher.pattern());
880         }
881     }
882
883     protected void parseIRDBMessage(String message) {
884         Matcher matcher = IRDB_PATTERN.matcher(message);
885
886         if (matcher.matches()) {
887             IrCommand command = new IrCommand();
888             command.setRemote(matcher.group(1));
889             command.setCommand(matcher.group(2));
890
891             for (TransceiverStatusListener listener : transceiverStatusListeners) {
892                 listener.onCommandReceived(this, command);
893             }
894         } else {
895             logger.error("{} does not match the IRDB infrared message format '{}'", message, matcher.pattern());
896         }
897     }
898
899     /**
900      * "Pack" the infrared command so that it can be sent to the IRTrans device
901      *
902      * @param led the led
903      * @param command the the command
904      * @return a string which is the full command to be sent to the device
905      */
906     protected String packIRDBCommand(Led led, IrCommand command) {
907         String output = new String();
908
909         output = "Asnd ";
910         output += command.getRemote();
911         output += ",";
912         output += command.getCommand();
913         output += ",l";
914         output += led.toString();
915
916         output += "\r\n";
917
918         return output;
919     }
920
921     /**
922      * "Pack" the infrared command so that it can be sent to the IRTrans device
923      *
924      * @param led the led
925      * @param command the the command
926      * @return a string which is the full command to be sent to the device
927      */
928     protected String packHexCommand(Led led, IrCommand command) {
929         String output = new String();
930
931         output = "Asndhex ";
932         output += "L";
933         output += led.toString();
934
935         output += ",";
936         output += "H" + command.toHEXString();
937
938         output += (char) 13;
939
940         return output;
941     }
942 }