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