001package com.pi4j.io.serial.impl;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  SerialImpl.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  http://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2016 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032
033import com.pi4j.io.serial.*;
034import com.pi4j.io.serial.tasks.SerialDataEventDispatchTaskImpl;
035import com.pi4j.jni.SerialInterrupt;
036import com.pi4j.jni.SerialInterruptEvent;
037import com.pi4j.jni.SerialInterruptListener;
038
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.OutputStream;
042import java.util.Collections;
043import java.util.concurrent.CopyOnWriteArrayList;
044import java.util.concurrent.ExecutorService;
045
046/**
047 * <p> This implementation class implements the 'Serial' interface using the WiringPi Serial library.</p>
048 *
049 * <p>
050 * Before using the Pi4J library, you need to ensure that the Java VM in configured with access to
051 * the following system libraries:
052 * <ul>
053 * <li>pi4j</li>
054 * <li>wiringPi</li>
055 * </ul>
056 * <blockquote> This library depends on the wiringPi native system library.</br> (developed by
057 * Gordon Henderson @ <a href="http://wiringpi.com/">http://wiringpi.com/</a>)
058 * </blockquote>
059 * </p>
060 *
061 * @see com.pi4j.io.serial.Serial
062 * @see com.pi4j.io.serial.SerialDataEvent
063 * @see com.pi4j.io.serial.SerialDataEventListener
064 * @see com.pi4j.io.serial.SerialFactory
065 *
066 * @see <a href="http://www.pi4j.com/">http://www.pi4j.com/</a>
067 * @author Robert Savage (<a
068 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
069 */
070public class SerialImpl extends AbstractSerialDataReaderWriter implements Serial {
071
072    protected int fileDescriptor = -1;
073    protected final CopyOnWriteArrayList<SerialDataEventListener> listeners;
074    protected final ExecutorService executor;
075    protected final SerialByteBuffer receiveBuffer;
076    protected boolean bufferingDataReceived = true;
077
078    /**
079     * default constructor
080     */
081    public SerialImpl(){
082        listeners = new CopyOnWriteArrayList<>();
083        executor = SerialFactory.getExecutorServiceFactory().newSingleThreadExecutorService();
084        receiveBuffer = new SerialByteBuffer();
085
086        // register shutdown callback hook class
087        Runtime.getRuntime().addShutdownHook(new ShutdownHook());
088    }
089
090    /**
091     * This class is used to perform any configured shutdown actions
092     * for the serial impl
093     *
094     * @author Robert Savage
095     *
096     */
097    private class ShutdownHook extends Thread {
098        public void run() {
099
100            // close serial port
101            if(isOpen()){
102                try {
103                    close();
104                } catch (IOException e) {
105                    e.printStackTrace();
106                }
107            }
108
109            // remove serial port listener
110            SerialInterrupt.removeListener(fileDescriptor);
111
112            // perform shutdown of any monitoring threads
113            SerialFactory.shutdown();
114        }
115    }
116
117    /**
118     * <p>
119     * This opens and initializes the serial port/device and sets the communication parameters.
120     * It sets the port into raw mode (character at a time and no translations).
121     * </p>
122     *
123     * <p>
124     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
125     * </p>
126     *
127     * @see #DEFAULT_COM_PORT
128     *
129     * @param device
130     *          The device address of the serial port to access. You can use constant
131     *          'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
132     *          GPIO header.
133     * @param baud
134     *          The baud rate to use with the serial port. (Custom baud rate are not supported)
135     * @param dataBits
136     *          The data bits to use for serial communication. (5,6,7,8)
137     * @param parity
138     *          The parity setting to use for serial communication. (None, Event, Odd, Mark, Space)
139     * @param stopBits
140     *          The stop bits to use for serial communication. (1,2)
141     * @param flowControl
142     *          The flow control option to use for serial communication. (none, hardware, software)
143     *
144     * @throws IOException thrown on any error.
145     */
146    @Override
147    public void open(String device, int baud, int dataBits, int parity, int stopBits, int flowControl)
148            throws IOException{
149
150        // open serial port
151        fileDescriptor = com.pi4j.jni.Serial.open(device, baud, dataBits, parity, stopBits, flowControl);
152
153        // read in initial buffered data (if any) into the receive buffer
154        int available = com.pi4j.jni.Serial.available(fileDescriptor);
155
156        if(available > 0) {
157            byte[] initial_data = com.pi4j.jni.Serial.read(fileDescriptor, available);
158            if (initial_data.length > 0) {
159                try {
160                    // write data to the receive buffer
161                    receiveBuffer.write(initial_data);
162                }
163                catch (IOException e) {
164                    e.printStackTrace();
165                }
166            }
167        }
168
169        // create a serial data listener event for data receive events from the serial device
170        SerialInterrupt.addListener(fileDescriptor, new SerialInterruptListener() {
171            @Override
172            public void onDataReceive(SerialInterruptEvent event) {
173
174                try {
175                    SerialDataEvent sde = null;
176
177                    if(isBufferingDataReceived()) {
178                        // stuff event data payload into the receive buffer
179                        receiveBuffer.write(event.getData());
180
181                        //System.out.println("BUFFER SIZE : " + receiveBuffer.capacity());
182                        //System.out.println("BUFFER LEFT : " + receiveBuffer.remaining());
183                        //System.out.println("BUFFER AVAIL: " + receiveBuffer.available());
184
185                        // create the serial data event; since we are buffering data
186                        // it will be located in the receive buffer
187                        sde = new SerialDataEvent(SerialImpl.this);
188                    }
189                    else{
190                        // create the serial data event; since we are NOT buffering data
191                        // we will pass the specific data payload directly into the event
192                        sde = new SerialDataEvent(SerialImpl.this, event.getData());
193                    }
194
195                    // add a new serial data event notification to the thread pool for *immediate* execution
196                    // we notify the event listeners on a separate thread to prevent blocking the native monitoring thread
197                    executor.execute(new SerialDataEventDispatchTaskImpl(sde, listeners));
198                }
199                catch (IOException e) {
200                    e.printStackTrace();
201                }
202            }
203        });
204
205        // ensure file descriptor is valid
206        if (fileDescriptor == -1) {
207            throw new IOException("Cannot open serial port");
208        }
209    }
210
211    /**
212     * <p>
213     * This opens and initializes the serial port/device and sets the communication parameters.
214     * It sets the port into raw mode (character at a time and no translations).
215     *
216     * This method will use the following default serial configuration parameters:
217     *  - DATA BITS    = 8
218     *  - PARITY       = NONE
219     *  - STOP BITS    = 1
220     *  - FLOW CONTROL = NONE
221     *
222     * </p>
223     *
224     * <p>
225     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
226     * </p>
227     *
228     * @see #DEFAULT_COM_PORT
229     *
230     * @param device
231     *          The device address of the serial port to access. You can use constant
232     *          'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
233     *          GPIO header.
234     * @param baud
235     *          The baud rate to use with the serial port.
236     *
237     * @throws IOException thrown on any error.
238     */
239    @Override
240    public void open(String device, int baud) throws IOException{
241        // open the serial port with config settings of "8N1" and no flow control
242        open(device,
243             baud,
244             com.pi4j.jni.Serial.DATA_BITS_8,
245             com.pi4j.jni.Serial.PARITY_NONE,
246             com.pi4j.jni.Serial.STOP_BITS_1,
247             com.pi4j.jni.Serial.FLOW_CONTROL_NONE);
248    }
249
250    /**
251     * <p>
252     * This opens and initializes the serial port/device and sets the communication parameters.
253     * It sets the port into raw mode (character at a time and no translations).
254     * </p>
255     *
256     * <p>
257     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
258     * </p>
259     *
260     * @see #DEFAULT_COM_PORT
261     *
262     * @param device
263     *          The device address of the serial port to access. You can use constant
264     *          'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
265     *          GPIO header.
266     * @param baud
267     *          The baud rate to use with the serial port.
268     * @param dataBits
269     *          The data bits to use for serial communication. (5,6,7,8)
270     * @param parity
271     *          The parity setting to use for serial communication. (None, Event, Odd, Mark, Space)
272     * @param stopBits
273     *          The stop bits to use for serial communication. (1,2)
274     * @param flowControl
275     *          The flow control option to use for serial communication. (none, hardware, software)
276     *
277     * @throws IOException thrown on any error.
278     */
279    @Override
280    public void open(String device, Baud baud, DataBits dataBits, Parity parity, StopBits stopBits,
281                     FlowControl flowControl) throws IOException{
282        // open the serial port with NO ECHO and NO (forced) BUFFER FLUSH
283        open(device, baud.getValue(), dataBits.getValue(), parity.getIndex(),
284                stopBits.getValue(), flowControl.getIndex());
285    }
286
287    /**
288     * <p>
289     * This opens and initializes the serial port/device and sets the communication parameters.
290     * It sets the port into raw mode (character at a time and no translations).
291     * </p>
292     *
293     * <p>
294     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
295     * </p>
296     *
297     * @see #DEFAULT_COM_PORT
298     *
299     * @param serialConfig
300     *          A serial configuration object that contains the device, baud rate, data bits, parity,
301     *          stop bits, and flow control settings.
302     *
303     * @throws  IOException thrown on any error.
304     */
305    @Override
306    public void open(SerialConfig serialConfig) throws IOException{
307        // open the serial port with config settings
308        open(serialConfig.device(),
309             serialConfig.baud().getValue(),
310             serialConfig.dataBits().getValue(),
311             serialConfig.parity().getIndex(),
312             serialConfig.stopBits().getValue(),
313             serialConfig.flowControl().getIndex());
314    }
315
316    /**
317     * This method is called to determine if the serial port is already open.
318     *
319     * @see #open(String, int)
320     * @return a value of 'true' is returned if the serial port is already open.
321     */
322    @Override
323    public boolean isOpen() {
324        return (fileDescriptor >= 0);
325    }
326
327    /**
328     * This method is called to determine if the serial port is already closed.
329     *
330     * @see #open(String, int)
331     * @return a value of 'true' is returned if the serial port is already in the closed state.
332     */
333    @Override
334    public boolean isClosed(){
335        return !(isOpen());
336    }
337
338
339    /**
340     * This method is called to close a currently open open serial port.
341     *
342     * @throws IllegalStateException thrown if the serial port is not already open.
343     * @throws IOException thrown on any error.
344     */
345    @Override
346    public void close() throws IllegalStateException, IOException {
347
348        // validate state
349        if (isClosed())
350            throw new IllegalStateException("Serial connection is not open; cannot 'close()'.");
351
352        // remove serial port listener
353        SerialInterrupt.removeListener(fileDescriptor);
354
355        // close serial port now
356        com.pi4j.jni.Serial.close(fileDescriptor);
357
358        // reset file descriptor
359        fileDescriptor = -1;
360        }
361
362
363    /**
364     * <p>
365     *     Forces the transmission of any remaining data in the serial port transmit buffer.
366     *     Please note that this does not force the transmission of data, it discards it!
367     * </p>
368     *
369     * @throws IllegalStateException thrown if the serial port is not already open.
370     * @throws IOException thrown on any error.
371     */
372    @Override
373    public void flush() throws IllegalStateException, IOException{
374        // validate state
375        if (isClosed())
376            throw new IllegalStateException("Serial connection is not open; cannot 'flush()'.");
377
378        // flush data to serial port immediately
379        com.pi4j.jni.Serial.flush(fileDescriptor);
380    }
381
382    /**
383     * <p>
384     *     Discards any data in the serial receive (input) buffer.
385     *     Please note that this does not force the transmission of data, it discards it!
386     * </p>
387     *
388     * @throws IllegalStateException thrown if the serial port is not already open.
389     * @throws IOException thrown on any error.
390     */
391    @Override
392    public void discardInput() throws IllegalStateException, IOException{
393        // validate state
394        if (isClosed())
395            throw new IllegalStateException("Serial connection is not open; cannot 'discardInput()'.");
396
397        // flush data to serial port immediately
398        com.pi4j.jni.Serial.discardInput(fileDescriptor);
399    }
400
401    /**
402     * <p>
403     *     Discards any data in the serial transmit (output) buffer.
404     *     Please note that this does not force the transmission of data, it discards it!
405     * </p>
406     *
407     * @throws IllegalStateException thrown if the serial port is not already open.
408     * @throws IOException thrown on any error.
409     */
410    @Override
411    public void discardOutput() throws IllegalStateException, IOException{
412        // validate state
413        if (isClosed())
414                throw new IllegalStateException("Serial connection is not open; cannot 'discardOutput()'.");
415
416        // flush data to serial port immediately
417        com.pi4j.jni.Serial.discardOutput(fileDescriptor);
418    }
419
420    /**
421     * <p>
422     *     Discards any data in  both the serial receive and transmit buffers.
423     *     Please note that this does not force the transmission of data, it discards it!
424     * </p>
425     *
426     * @throws IllegalStateException thrown if the serial port is not already open.
427     * @throws IOException thrown on any error.
428     */
429    @Override
430    public void discardAll() throws IllegalStateException, IOException{
431        // validate state
432        if (isClosed())
433            throw new IllegalStateException("Serial connection is not open; cannot 'discardAll()'.");
434
435        // flush data to serial port immediately
436        com.pi4j.jni.Serial.discardAll(fileDescriptor);
437    }
438
439    /**
440     * <p>
441     *     Send a BREAK signal to connected device.
442     * </p>
443     *
444     * @param duration
445     *          The length of time (milliseconds) to send the BREAK signal
446     * @throws IllegalStateException thrown if the serial port is not already open.
447     * @throws IOException thrown on any error.
448     */
449    @Override
450    public void sendBreak(int duration) throws IllegalStateException, IOException{
451        // validate state
452        if (isClosed())
453            throw new IllegalStateException("Serial connection is not open; cannot 'sendBreak()'.");
454
455        // send BREAK signal to serial port immediately
456        com.pi4j.jni.Serial.sendBreak(fileDescriptor, duration);
457    }
458
459    /**
460     * <p>
461     *     Send a BREAK signal to connected device for at least 0.25 seconds, and not more than 0.5 seconds
462     * </p>
463     *
464     * @throws IllegalStateException thrown if the serial port is not already open.
465     * @throws IOException thrown on any error.
466     */
467    @Override
468    public void sendBreak() throws IllegalStateException, IOException{
469        sendBreak(0);
470    }
471
472    /**
473     * <p>
474     *     Send a constant BREAK signal to connected device. (Turn break on/off)
475     *     When enabled this will send a steady stream of zero bits.
476     *     When enabled, no (other) data transmitting is possible.
477     * </p>
478     *
479     * @param enabled
480     *          The enable or disable state to control the BREAK signal
481     * @throws IllegalStateException thrown if the serial port is not already open.
482     * @throws IOException thrown on any error.
483     */
484    @Override
485    public void setBreak(boolean enabled) throws IllegalStateException, IOException{
486        // validate state
487        if (isClosed())
488            throw new IllegalStateException("Serial connection is not open; cannot 'setBreak()'.");
489
490        // control the constant state of the BREAK signal
491        com.pi4j.jni.Serial.setBreak(fileDescriptor, enabled);
492    }
493
494    /**
495     * <p>
496     *     Control the RTS (request-to-send) pin state.
497     *     When enabled this will set the RTS pin to the HIGH state.
498     * </p>
499     *
500     * @param enabled
501     *          The enable or disable state to control the RTS pin state.
502     * @throws IllegalStateException thrown if the serial port is not already open.
503     * @throws IOException thrown on any error.
504     */
505    @Override
506    public void setRTS(boolean enabled) throws IllegalStateException, IOException{
507        // validate state
508        if (isClosed())
509            throw new IllegalStateException("Serial connection is not open; cannot 'setRTS()'.");
510
511        // control the constant state of the RTS signal
512        com.pi4j.jni.Serial.setRTS(fileDescriptor, enabled);
513    }
514
515    /**
516     * <p>
517     *     Control the DTR (data-terminal-ready) pin state.
518     *     When enabled this will set the DTR pin to the HIGH state.
519     * </p>
520     *
521     * @param enabled
522     *          The enable or disable state to control the RTS pin state.
523     * @throws IllegalStateException thrown if the serial port is not already open.
524     * @throws IOException thrown on any error.
525     */
526    @Override
527    public void setDTR(boolean enabled) throws IllegalStateException, IOException{
528        // validate state
529        if (isClosed())
530            throw new IllegalStateException("Serial connection is not open; cannot 'setDTR()'.");
531
532        // control the constant state of the DTR signal
533        com.pi4j.jni.Serial.setDTR(fileDescriptor, enabled);
534    }
535
536    /**
537     * <p>
538     *     Get the RTS (request-to-send) pin state.
539     * </p>
540     *
541     * @throws IllegalStateException thrown if the serial port is not already open.
542     * @throws IOException thrown on any error.
543     */
544    public boolean getRTS() throws IllegalStateException, IOException{
545        // validate state
546        if (isClosed())
547            throw new IllegalStateException("Serial connection is not open; cannot 'getRTS()'.");
548
549        // get pin state
550        return com.pi4j.jni.Serial.getRTS(fileDescriptor);
551    }
552
553    /**
554     * <p>
555     *     Get the DTR (data-terminal-ready) pin state.
556     * </p>
557     *
558     * @throws IllegalStateException thrown if the serial port is not already open.
559     * @throws IOException thrown on any error.
560     */
561    public boolean getDTR() throws IllegalStateException, IOException{
562        // validate state
563        if (isClosed())
564            throw new IllegalStateException("Serial connection is not open; cannot 'getDTR()'.");
565
566        // get pin state
567        return com.pi4j.jni.Serial.getDTR(fileDescriptor);
568    }
569
570    /**
571     * <p>
572     *     Get the CTS (clean-to-send) pin state.
573     * </p>
574     *
575     * @throws IllegalStateException thrown if the serial port is not already open.
576     * @throws IOException thrown on any error.
577     */
578    public boolean getCTS() throws IllegalStateException, IOException{
579        // validate state
580        if (isClosed())
581            throw new IllegalStateException("Serial connection is not open; cannot 'getCTS()'.");
582
583        // get pin state
584        return com.pi4j.jni.Serial.getCTS(fileDescriptor);
585    }
586
587    /**
588     * <p>
589     *     Get the DSR (data-set-ready) pin state.
590     * </p>
591     *
592     * @throws IllegalStateException thrown if the serial port is not already open.
593     * @throws IOException thrown on any error.
594     */
595    public boolean getDSR() throws IllegalStateException, IOException{
596        // validate state
597        if (isClosed())
598            throw new IllegalStateException("Serial connection is not open; cannot 'getDSR()'.");
599
600        // get pin state
601        return com.pi4j.jni.Serial.getDSR(fileDescriptor);
602    }
603
604    /**
605     * <p>
606     *     Get the RI (ring-indicator) pin state.
607     * </p>
608     *
609     * @throws IllegalStateException thrown if the serial port is not already open.
610     * @throws IOException thrown on any error.
611     */
612    public boolean getRI() throws IllegalStateException, IOException{
613        // validate state
614        if (isClosed())
615            throw new IllegalStateException("Serial connection is not open; cannot 'getRI()'.");
616
617        // get pin state
618        return com.pi4j.jni.Serial.getRI(fileDescriptor);
619    }
620
621    /**
622     * <p>
623     *     Get the CD (carrier-detect) pin state.
624     * </p>
625     *
626     * @throws IllegalStateException thrown if the serial port is not already open.
627     * @throws IOException thrown on any error.
628     */
629    public boolean getCD() throws IllegalStateException, IOException{
630        // validate state
631        if (isClosed())
632            throw new IllegalStateException("Serial connection is not open; cannot 'getCD()'.");
633
634        // get pin state
635        return com.pi4j.jni.Serial.getCD(fileDescriptor);
636    }
637
638    // ----------------------------------------
639    // READ OPERATIONS
640    // ----------------------------------------
641
642    /**
643     * Gets the number of bytes available for reading, or -1 for any error condition.
644     *
645     * @return Returns the number of bytes available for reading, or -1 for any error
646     * @throws IllegalStateException thrown if the serial port is not already open.
647     * @throws IOException thrown on any error.
648     */
649    @Override
650    public int available() throws IllegalStateException, IOException {
651        // validate state
652        if (isClosed())
653            throw new IllegalStateException("Serial connection is not open; cannot 'available()'.");
654
655        // get the number of available bytes in the serial port's receive buffer
656        //return com.pi4j.jni.Serial.available(fileDescriptor);
657        return receiveBuffer.getInputStream().available();
658    }
659
660    /**
661     * <p>Reads all available bytes from the serial port/device.</p>
662     *
663     * @return Returns a byte array with the data read from the serial port.
664     * @throws IllegalStateException thrown if the serial port is not already open.
665     * @throws IOException thrown on any error.
666     */
667    @Override
668    public byte[] read() throws IllegalStateException, IOException{
669        // validate state
670        if (isClosed())
671            throw new IllegalStateException("Serial connection is not open; cannot 'read()'.");
672
673        // read serial data from receive buffer
674        byte[] buffer = new byte[available()];
675        receiveBuffer.getInputStream().read(buffer);
676        return buffer;
677    }
678
679    /**
680     * <p>Reads a length of bytes from the port/serial device.</p>
681     *
682     * @param length
683     *          The number of bytes to get from the serial port/device.
684     *          This number must not be higher than the number of available bytes.
685     *
686     * @return Returns a byte array with the data read from the serial port.
687     * @throws IllegalStateException thrown if the serial port is not already open.
688     * @throws IOException thrown on any error.
689     */
690    @Override
691    public byte[] read(int length) throws IllegalStateException, IOException{
692        // validate state
693        if (isClosed())
694            throw new IllegalStateException("Serial connection is not open; cannot 'read()'.");
695
696        // read serial data from receive buffer
697        byte[] buffer = new byte[length];
698        receiveBuffer.getInputStream().read(buffer, 0 , length);
699        return buffer;
700    }
701
702
703    // ----------------------------------------
704    // WRITE OPERATIONS
705    // ----------------------------------------
706
707    /**
708     * <p>Sends an array of bytes to the serial port/device identified by the given file descriptor.</p>
709     *
710     * @param data
711     *            A ByteBuffer of data to be transmitted.
712     * @param offset
713     *            The starting index (inclusive) in the array to send from.
714     * @param length
715     *            The number of bytes from the byte array to transmit to the serial port.
716     * @throws IllegalStateException thrown if the serial port is not already open.
717     * @throws IOException thrown on any error.
718     */
719    @Override
720    public void write(byte[] data, int offset, int length) throws IllegalStateException, IOException{
721        // validate state
722        if (isClosed()) {
723            throw new IllegalStateException("Serial connection is not open; cannot 'write()'.");
724        }
725
726        // write serial data to transmit buffer
727        com.pi4j.jni.Serial.write(fileDescriptor, data, offset, length);
728    }
729
730
731    // ----------------------------------------
732    // EVENT OPERATIONS
733    // ----------------------------------------
734
735    /**
736     * <p>Add Serial Event Listener</p>
737     *
738     * <p> Java consumer code can call this method to register itself as a listener for serial data
739     * events. </p>
740     *
741     * @see com.pi4j.io.serial.SerialDataEventListener
742     * @see com.pi4j.io.serial.SerialDataEvent
743     *
744     * @param listener  A class instance that implements the SerialListener interface.
745     */
746    @Override
747    public synchronized void addListener(SerialDataEventListener... listener) {
748        // add the new listener to the list of listeners
749        Collections.addAll(listeners, listener);
750    }
751
752    /**
753     * <p>Remove Serial Event Listener</p>
754     *
755     * <p> Java consumer code can call this method to unregister itself as a listener for serial data
756     * events. </p>
757     *
758     * @see com.pi4j.io.serial.SerialDataEventListener
759     * @see com.pi4j.io.serial.SerialDataEvent
760     *
761     * @param listener A class instance that implements the SerialListener interface.
762     */
763    @Override
764    public synchronized void removeListener(SerialDataEventListener... listener) {
765        // remove the listener from the list of listeners
766        for (SerialDataEventListener lsnr : listener) {
767            listeners.remove(lsnr);
768        }
769    }
770
771    /**
772     * This method returns the serial device file descriptor
773     * @return fileDescriptor file descriptor
774     */
775    @Override
776    public int getFileDescriptor() {
777        return fileDescriptor;
778    }
779
780    /**
781     * This method returns the input data stream for the serial port's receive buffer
782     * @return InputStream input stream
783     */
784    @Override
785    public InputStream getInputStream() {
786        return receiveBuffer.getInputStream();
787    }
788
789    /**
790     * This method returns the output data stream for the serial port's transmit buffer
791     * @return OutputStream output stream
792     */
793    @Override
794    public OutputStream getOutputStream() {
795        return new SerialOutputStream();
796    }
797
798
799    /**
800     * This method returns the buffering state for data received from the serial device/port.
801     * @return 'true' if buffering is enabled; else 'false'
802     */
803    @Override
804    public boolean isBufferingDataReceived(){
805        return bufferingDataReceived;
806    }
807
808    /**
809     * <p>
810     *     This method controls the buffering state for data received from the serial device/port.
811     * </p>
812     * <p>
813     *   If the buffering state is enabled, then all data bytes received from the serial port will
814     *   get copied into a data receive buffer.  You can use the 'getInputStream()' or and of the 'read()'
815     *   methods to access this data.  The data will also be available via the 'SerialDataEvent' event.
816     *   It is important to know that if you are using data buffering, the data will continue to grow
817     *   in memory until your program consume it from the data reader/stream.
818     * </p>
819     * <p>
820     *   If the buffering state is disabled, then all data bytes received from the serial port will NOT
821     *   get copied into the data receive buffer, but will be included in the 'SerialDataEvent' event's
822     *   data payload.  If you program does not care about or use data received from the serial port,
823     *   then you should disable the data buffering state to prevent memory waste/leak.
824     * </p>
825     *
826     * @param enabled sets the buffering behavior state
827     */
828    @Override
829    public void setBufferingDataReceived(boolean enabled){
830        bufferingDataReceived = enabled;
831    }
832
833
834    private class SerialOutputStream extends OutputStream {
835
836        @Override
837        public void write(byte b[]) throws IOException {
838            SerialImpl.this.write(b);
839        }
840
841        @Override
842        public void write(int b) throws IOException {
843            SerialImpl.this.write((byte)b);
844        }
845
846        public void write(byte b[], int offset, int length) throws IOException {
847            SerialImpl.this.write(b, offset, length);
848        }
849
850        @Override
851        public void flush() throws IOException {
852            SerialImpl.this.flush();
853        }
854    }
855
856    private class SerialInputStream extends InputStream {
857
858        @Override
859        public int read() throws IOException {
860            return 0;
861        }
862    }
863}