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:  https://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2019 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="https://www.pi4j.com/">https://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                // ignore any event triggers that are missing data
175                if(event.getLength() <= 0) return;
176
177                try {
178                    SerialDataEvent sde = null;
179
180                    if(isBufferingDataReceived()) {
181                        // stuff event data payload into the receive buffer
182                        receiveBuffer.write(event.getData());
183
184                        //System.out.println("BUFFER SIZE : " + receiveBuffer.capacity());
185                        //System.out.println("BUFFER LEFT : " + receiveBuffer.remaining());
186                        //System.out.println("BUFFER AVAIL: " + receiveBuffer.available());
187
188                        // create the serial data event; since we are buffering data
189                        // it will be located in the receive buffer
190                        sde = new SerialDataEvent(SerialImpl.this);
191                    }
192                    else{
193                        // create the serial data event; since we are NOT buffering data
194                        // we will pass the specific data payload directly into the event
195                        sde = new SerialDataEvent(SerialImpl.this, event.getData());
196                    }
197
198                    // add a new serial data event notification to the thread pool for *immediate* execution
199                    // we notify the event listeners on a separate thread to prevent blocking the native monitoring thread
200                    executor.execute(new SerialDataEventDispatchTaskImpl(sde, listeners));
201                }
202                catch (IOException e) {
203                    e.printStackTrace();
204                }
205            }
206        });
207
208        // ensure file descriptor is valid
209        if (fileDescriptor == -1) {
210            throw new IOException("Cannot open serial port");
211        }
212    }
213
214    /**
215     * <p>
216     * This opens and initializes the serial port/device and sets the communication parameters.
217     * It sets the port into raw mode (character at a time and no translations).
218     *
219     * This method will use the following default serial configuration parameters:
220     *  - DATA BITS    = 8
221     *  - PARITY       = NONE
222     *  - STOP BITS    = 1
223     *  - FLOW CONTROL = NONE
224     *
225     * </p>
226     *
227     * <p>
228     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
229     * </p>
230     *
231     * @see #DEFAULT_COM_PORT
232     *
233     * @param device
234     *          The device address of the serial port to access. You can use constant
235     *          'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
236     *          GPIO header.
237     * @param baud
238     *          The baud rate to use with the serial port.
239     *
240     * @throws IOException thrown on any error.
241     */
242    @Override
243    public void open(String device, int baud) throws IOException{
244        // open the serial port with config settings of "8N1" and no flow control
245        open(device,
246             baud,
247             com.pi4j.jni.Serial.DATA_BITS_8,
248             com.pi4j.jni.Serial.PARITY_NONE,
249             com.pi4j.jni.Serial.STOP_BITS_1,
250             com.pi4j.jni.Serial.FLOW_CONTROL_NONE);
251    }
252
253    /**
254     * <p>
255     * This opens and initializes the serial port/device and sets the communication parameters.
256     * It sets the port into raw mode (character at a time and no translations).
257     * </p>
258     *
259     * <p>
260     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
261     * </p>
262     *
263     * @see #DEFAULT_COM_PORT
264     *
265     * @param device
266     *          The device address of the serial port to access. You can use constant
267     *          'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
268     *          GPIO header.
269     * @param baud
270     *          The baud rate to use with the serial port.
271     * @param dataBits
272     *          The data bits to use for serial communication. (5,6,7,8)
273     * @param parity
274     *          The parity setting to use for serial communication. (None, Event, Odd, Mark, Space)
275     * @param stopBits
276     *          The stop bits to use for serial communication. (1,2)
277     * @param flowControl
278     *          The flow control option to use for serial communication. (none, hardware, software)
279     *
280     * @throws IOException thrown on any error.
281     */
282    @Override
283    public void open(String device, Baud baud, DataBits dataBits, Parity parity, StopBits stopBits,
284                     FlowControl flowControl) throws IOException{
285        // open the serial port with NO ECHO and NO (forced) BUFFER FLUSH
286        open(device, baud.getValue(), dataBits.getValue(), parity.getIndex(),
287                stopBits.getValue(), flowControl.getIndex());
288    }
289
290    /**
291     * <p>
292     * This opens and initializes the serial port/device and sets the communication parameters.
293     * It sets the port into raw mode (character at a time and no translations).
294     * </p>
295     *
296     * <p>
297     * (ATTENTION: the 'device' argument can only be a maximum of 128 characters.)
298     * </p>
299     *
300     * @see #DEFAULT_COM_PORT
301     *
302     * @param serialConfig
303     *          A serial configuration object that contains the device, baud rate, data bits, parity,
304     *          stop bits, and flow control settings.
305     *
306     * @throws  IOException thrown on any error.
307     */
308    @Override
309    public void open(SerialConfig serialConfig) throws IOException{
310        // open the serial port with config settings
311        open(serialConfig.device(),
312             serialConfig.baud().getValue(),
313             serialConfig.dataBits().getValue(),
314             serialConfig.parity().getIndex(),
315             serialConfig.stopBits().getValue(),
316             serialConfig.flowControl().getIndex());
317    }
318
319    /**
320     * This method is called to determine if the serial port is already open.
321     *
322     * @see #open(String, int)
323     * @return a value of 'true' is returned if the serial port is already open.
324     */
325    @Override
326    public boolean isOpen() {
327        return (fileDescriptor >= 0);
328    }
329
330    /**
331     * This method is called to determine if the serial port is already closed.
332     *
333     * @see #open(String, int)
334     * @return a value of 'true' is returned if the serial port is already in the closed state.
335     */
336    @Override
337    public boolean isClosed(){
338        return !(isOpen());
339    }
340
341
342    /**
343     * This method is called to close a currently open open serial port.
344     *
345     * @throws IllegalStateException thrown if the serial port is not already open.
346     * @throws IOException thrown on any error.
347     */
348    @Override
349    public void close() throws IllegalStateException, IOException {
350
351        // validate state
352        if (isClosed())
353            throw new IllegalStateException("Serial connection is not open; cannot 'close()'.");
354
355        // remove serial port listener
356        SerialInterrupt.removeListener(fileDescriptor);
357
358        // close serial port now
359        com.pi4j.jni.Serial.close(fileDescriptor);
360
361        // reset file descriptor
362        fileDescriptor = -1;
363        }
364
365
366    /**
367     * <p>
368     *     Forces the transmission of any remaining data in the serial port transmit buffer.
369     *     Please note that this does not force the transmission of data, it discards it!
370     * </p>
371     *
372     * @throws IllegalStateException thrown if the serial port is not already open.
373     * @throws IOException thrown on any error.
374     */
375    @Override
376    public void flush() throws IllegalStateException, IOException{
377        // validate state
378        if (isClosed())
379            throw new IllegalStateException("Serial connection is not open; cannot 'flush()'.");
380
381        // flush data to serial port immediately
382        com.pi4j.jni.Serial.flush(fileDescriptor);
383    }
384
385    /**
386     * <p>
387     *     Discards any data in the serial receive (input) buffer.
388     *     Please note that this does not force the transmission of data, it discards it!
389     * </p>
390     *
391     * @throws IllegalStateException thrown if the serial port is not already open.
392     * @throws IOException thrown on any error.
393     */
394    @Override
395    public void discardInput() throws IllegalStateException, IOException{
396        // validate state
397        if (isClosed())
398            throw new IllegalStateException("Serial connection is not open; cannot 'discardInput()'.");
399
400        // flush data to serial port immediately
401        com.pi4j.jni.Serial.discardInput(fileDescriptor);
402    }
403
404    /**
405     * <p>
406     *     Discards any data in the serial transmit (output) buffer.
407     *     Please note that this does not force the transmission of data, it discards it!
408     * </p>
409     *
410     * @throws IllegalStateException thrown if the serial port is not already open.
411     * @throws IOException thrown on any error.
412     */
413    @Override
414    public void discardOutput() throws IllegalStateException, IOException{
415        // validate state
416        if (isClosed())
417                throw new IllegalStateException("Serial connection is not open; cannot 'discardOutput()'.");
418
419        // flush data to serial port immediately
420        com.pi4j.jni.Serial.discardOutput(fileDescriptor);
421    }
422
423    /**
424     * <p>
425     *     Discards any data in  both the serial receive and transmit buffers.
426     *     Please note that this does not force the transmission of data, it discards it!
427     * </p>
428     *
429     * @throws IllegalStateException thrown if the serial port is not already open.
430     * @throws IOException thrown on any error.
431     */
432    @Override
433    public void discardAll() throws IllegalStateException, IOException{
434        // validate state
435        if (isClosed())
436            throw new IllegalStateException("Serial connection is not open; cannot 'discardAll()'.");
437
438        // flush data to serial port immediately
439        com.pi4j.jni.Serial.discardAll(fileDescriptor);
440    }
441
442    /**
443     * <p>
444     *     Send a BREAK signal to connected device.
445     * </p>
446     *
447     * @param duration
448     *          The length of time (milliseconds) to send the BREAK signal
449     * @throws IllegalStateException thrown if the serial port is not already open.
450     * @throws IOException thrown on any error.
451     */
452    @Override
453    public void sendBreak(int duration) throws IllegalStateException, IOException{
454        // validate state
455        if (isClosed())
456            throw new IllegalStateException("Serial connection is not open; cannot 'sendBreak()'.");
457
458        // send BREAK signal to serial port immediately
459        com.pi4j.jni.Serial.sendBreak(fileDescriptor, duration);
460    }
461
462    /**
463     * <p>
464     *     Send a BREAK signal to connected device for at least 0.25 seconds, and not more than 0.5 seconds
465     * </p>
466     *
467     * @throws IllegalStateException thrown if the serial port is not already open.
468     * @throws IOException thrown on any error.
469     */
470    @Override
471    public void sendBreak() throws IllegalStateException, IOException{
472        sendBreak(0);
473    }
474
475    /**
476     * <p>
477     *     Send a constant BREAK signal to connected device. (Turn break on/off)
478     *     When enabled this will send a steady stream of zero bits.
479     *     When enabled, no (other) data transmitting is possible.
480     * </p>
481     *
482     * @param enabled
483     *          The enable or disable state to control the BREAK signal
484     * @throws IllegalStateException thrown if the serial port is not already open.
485     * @throws IOException thrown on any error.
486     */
487    @Override
488    public void setBreak(boolean enabled) throws IllegalStateException, IOException{
489        // validate state
490        if (isClosed())
491            throw new IllegalStateException("Serial connection is not open; cannot 'setBreak()'.");
492
493        // control the constant state of the BREAK signal
494        com.pi4j.jni.Serial.setBreak(fileDescriptor, enabled);
495    }
496
497    /**
498     * <p>
499     *     Control the RTS (request-to-send) pin state.
500     *     When enabled this will set the RTS pin to the HIGH state.
501     * </p>
502     *
503     * @param enabled
504     *          The enable or disable state to control the RTS pin state.
505     * @throws IllegalStateException thrown if the serial port is not already open.
506     * @throws IOException thrown on any error.
507     */
508    @Override
509    public void setRTS(boolean enabled) throws IllegalStateException, IOException{
510        // validate state
511        if (isClosed())
512            throw new IllegalStateException("Serial connection is not open; cannot 'setRTS()'.");
513
514        // control the constant state of the RTS signal
515        com.pi4j.jni.Serial.setRTS(fileDescriptor, enabled);
516    }
517
518    /**
519     * <p>
520     *     Control the DTR (data-terminal-ready) pin state.
521     *     When enabled this will set the DTR pin to the HIGH state.
522     * </p>
523     *
524     * @param enabled
525     *          The enable or disable state to control the RTS pin state.
526     * @throws IllegalStateException thrown if the serial port is not already open.
527     * @throws IOException thrown on any error.
528     */
529    @Override
530    public void setDTR(boolean enabled) throws IllegalStateException, IOException{
531        // validate state
532        if (isClosed())
533            throw new IllegalStateException("Serial connection is not open; cannot 'setDTR()'.");
534
535        // control the constant state of the DTR signal
536        com.pi4j.jni.Serial.setDTR(fileDescriptor, enabled);
537    }
538
539    /**
540     * <p>
541     *     Get the RTS (request-to-send) pin state.
542     * </p>
543     *
544     * @throws IllegalStateException thrown if the serial port is not already open.
545     * @throws IOException thrown on any error.
546     */
547    public boolean getRTS() throws IllegalStateException, IOException{
548        // validate state
549        if (isClosed())
550            throw new IllegalStateException("Serial connection is not open; cannot 'getRTS()'.");
551
552        // get pin state
553        return com.pi4j.jni.Serial.getRTS(fileDescriptor);
554    }
555
556    /**
557     * <p>
558     *     Get the DTR (data-terminal-ready) pin state.
559     * </p>
560     *
561     * @throws IllegalStateException thrown if the serial port is not already open.
562     * @throws IOException thrown on any error.
563     */
564    public boolean getDTR() throws IllegalStateException, IOException{
565        // validate state
566        if (isClosed())
567            throw new IllegalStateException("Serial connection is not open; cannot 'getDTR()'.");
568
569        // get pin state
570        return com.pi4j.jni.Serial.getDTR(fileDescriptor);
571    }
572
573    /**
574     * <p>
575     *     Get the CTS (clean-to-send) pin state.
576     * </p>
577     *
578     * @throws IllegalStateException thrown if the serial port is not already open.
579     * @throws IOException thrown on any error.
580     */
581    public boolean getCTS() throws IllegalStateException, IOException{
582        // validate state
583        if (isClosed())
584            throw new IllegalStateException("Serial connection is not open; cannot 'getCTS()'.");
585
586        // get pin state
587        return com.pi4j.jni.Serial.getCTS(fileDescriptor);
588    }
589
590    /**
591     * <p>
592     *     Get the DSR (data-set-ready) pin state.
593     * </p>
594     *
595     * @throws IllegalStateException thrown if the serial port is not already open.
596     * @throws IOException thrown on any error.
597     */
598    public boolean getDSR() throws IllegalStateException, IOException{
599        // validate state
600        if (isClosed())
601            throw new IllegalStateException("Serial connection is not open; cannot 'getDSR()'.");
602
603        // get pin state
604        return com.pi4j.jni.Serial.getDSR(fileDescriptor);
605    }
606
607    /**
608     * <p>
609     *     Get the RI (ring-indicator) pin state.
610     * </p>
611     *
612     * @throws IllegalStateException thrown if the serial port is not already open.
613     * @throws IOException thrown on any error.
614     */
615    public boolean getRI() throws IllegalStateException, IOException{
616        // validate state
617        if (isClosed())
618            throw new IllegalStateException("Serial connection is not open; cannot 'getRI()'.");
619
620        // get pin state
621        return com.pi4j.jni.Serial.getRI(fileDescriptor);
622    }
623
624    /**
625     * <p>
626     *     Get the CD (carrier-detect) pin state.
627     * </p>
628     *
629     * @throws IllegalStateException thrown if the serial port is not already open.
630     * @throws IOException thrown on any error.
631     */
632    public boolean getCD() throws IllegalStateException, IOException{
633        // validate state
634        if (isClosed())
635            throw new IllegalStateException("Serial connection is not open; cannot 'getCD()'.");
636
637        // get pin state
638        return com.pi4j.jni.Serial.getCD(fileDescriptor);
639    }
640
641    // ----------------------------------------
642    // READ OPERATIONS
643    // ----------------------------------------
644
645    /**
646     * Gets the number of bytes available for reading, or -1 for any error condition.
647     *
648     * @return Returns the number of bytes available for reading, or -1 for any error
649     * @throws IllegalStateException thrown if the serial port is not already open.
650     * @throws IOException thrown on any error.
651     */
652    @Override
653    public int available() throws IllegalStateException, IOException {
654        // validate state
655        if (isClosed())
656            throw new IllegalStateException("Serial connection is not open; cannot 'available()'.");
657
658        // get the number of available bytes in the serial port's receive buffer
659        //return com.pi4j.jni.Serial.available(fileDescriptor);
660        return receiveBuffer.getInputStream().available();
661    }
662
663    /**
664     * <p>Reads all available bytes from the serial port/device.</p>
665     *
666     * @return Returns a byte array with the data read from the serial port.
667     * @throws IllegalStateException thrown if the serial port is not already open.
668     * @throws IOException thrown on any error.
669     */
670    @Override
671    public byte[] read() throws IllegalStateException, IOException{
672        // validate state
673        if (isClosed())
674            throw new IllegalStateException("Serial connection is not open; cannot 'read()'.");
675
676        // read serial data from receive buffer
677        byte[] buffer = new byte[available()];
678        receiveBuffer.getInputStream().read(buffer);
679        return buffer;
680    }
681
682    /**
683     * <p>Reads a length of bytes from the port/serial device.</p>
684     *
685     * @param length
686     *          The number of bytes to get from the serial port/device.
687     *          This number must not be higher than the number of available bytes.
688     *
689     * @return Returns a byte array with the data read from the serial port.
690     * @throws IllegalStateException thrown if the serial port is not already open.
691     * @throws IOException thrown on any error.
692     */
693    @Override
694    public byte[] read(int length) throws IllegalStateException, IOException{
695        // validate state
696        if (isClosed())
697            throw new IllegalStateException("Serial connection is not open; cannot 'read()'.");
698
699        // read serial data from receive buffer
700        byte[] buffer = new byte[length];
701        receiveBuffer.getInputStream().read(buffer, 0 , length);
702        return buffer;
703    }
704
705
706    // ----------------------------------------
707    // WRITE OPERATIONS
708    // ----------------------------------------
709
710    /**
711     * <p>Sends an array of bytes to the serial port/device identified by the given file descriptor.</p>
712     *
713     * @param data
714     *            A ByteBuffer of data to be transmitted.
715     * @param offset
716     *            The starting index (inclusive) in the array to send from.
717     * @param length
718     *            The number of bytes from the byte array to transmit to the serial port.
719     * @throws IllegalStateException thrown if the serial port is not already open.
720     * @throws IOException thrown on any error.
721     */
722    @Override
723    public void write(byte[] data, int offset, int length) throws IllegalStateException, IOException{
724        // validate state
725        if (isClosed()) {
726            throw new IllegalStateException("Serial connection is not open; cannot 'write()'.");
727        }
728
729        // write serial data to transmit buffer
730        com.pi4j.jni.Serial.write(fileDescriptor, data, offset, length);
731    }
732
733
734    // ----------------------------------------
735    // EVENT OPERATIONS
736    // ----------------------------------------
737
738    /**
739     * <p>Add Serial Event Listener</p>
740     *
741     * <p> Java consumer code can call this method to register itself as a listener for serial data
742     * events. </p>
743     *
744     * @see com.pi4j.io.serial.SerialDataEventListener
745     * @see com.pi4j.io.serial.SerialDataEvent
746     *
747     * @param listener  A class instance that implements the SerialListener interface.
748     */
749    @Override
750    public synchronized void addListener(SerialDataEventListener... listener) {
751        // add the new listener to the list of listeners
752        Collections.addAll(listeners, listener);
753    }
754
755    /**
756     * <p>Remove Serial Event Listener</p>
757     *
758     * <p> Java consumer code can call this method to unregister itself as a listener for serial data
759     * events. </p>
760     *
761     * @see com.pi4j.io.serial.SerialDataEventListener
762     * @see com.pi4j.io.serial.SerialDataEvent
763     *
764     * @param listener A class instance that implements the SerialListener interface.
765     */
766    @Override
767    public synchronized void removeListener(SerialDataEventListener... listener) {
768        // remove the listener from the list of listeners
769        for (SerialDataEventListener lsnr : listener) {
770            listeners.remove(lsnr);
771        }
772    }
773
774    /**
775     * This method returns the serial device file descriptor
776     * @return fileDescriptor file descriptor
777     */
778    @Override
779    public int getFileDescriptor() {
780        return fileDescriptor;
781    }
782
783    /**
784     * This method returns the input data stream for the serial port's receive buffer
785     * @return InputStream input stream
786     */
787    @Override
788    public InputStream getInputStream() {
789        return receiveBuffer.getInputStream();
790    }
791
792    /**
793     * This method returns the output data stream for the serial port's transmit buffer
794     * @return OutputStream output stream
795     */
796    @Override
797    public OutputStream getOutputStream() {
798        return new SerialOutputStream();
799    }
800
801
802    /**
803     * This method returns the buffering state for data received from the serial device/port.
804     * @return 'true' if buffering is enabled; else 'false'
805     */
806    @Override
807    public boolean isBufferingDataReceived(){
808        return bufferingDataReceived;
809    }
810
811    /**
812     * <p>
813     *     This method controls the buffering state for data received from the serial device/port.
814     * </p>
815     * <p>
816     *   If the buffering state is enabled, then all data bytes received from the serial port will
817     *   get copied into a data receive buffer.  You can use the 'getInputStream()' or and of the 'read()'
818     *   methods to access this data.  The data will also be available via the 'SerialDataEvent' event.
819     *   It is important to know that if you are using data buffering, the data will continue to grow
820     *   in memory until your program consume it from the data reader/stream.
821     * </p>
822     * <p>
823     *   If the buffering state is disabled, then all data bytes received from the serial port will NOT
824     *   get copied into the data receive buffer, but will be included in the 'SerialDataEvent' event's
825     *   data payload.  If you program does not care about or use data received from the serial port,
826     *   then you should disable the data buffering state to prevent memory waste/leak.
827     * </p>
828     *
829     * @param enabled sets the buffering behavior state
830     */
831    @Override
832    public void setBufferingDataReceived(boolean enabled){
833        bufferingDataReceived = enabled;
834    }
835
836
837    private class SerialOutputStream extends OutputStream {
838
839        @Override
840        public void write(byte b[]) throws IOException {
841            SerialImpl.this.write(b);
842        }
843
844        @Override
845        public void write(int b) throws IOException {
846            SerialImpl.this.write((byte)b);
847        }
848
849        public void write(byte b[], int offset, int length) throws IOException {
850            SerialImpl.this.write(b, offset, length);
851        }
852
853        @Override
854        public void flush() throws IOException {
855            SerialImpl.this.flush();
856        }
857    }
858
859    @SuppressWarnings("unused")
860        private class SerialInputStream extends InputStream {
861
862        @Override
863        public int read() throws IOException {
864            return 0;
865        }
866    }
867}