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 - 2015 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.Serial;
034import com.pi4j.io.serial.SerialDataListener;
035import com.pi4j.io.serial.SerialPortException;
036
037import java.util.Collections;
038import java.util.concurrent.CopyOnWriteArrayList;
039
040/**
041 * <p> This implementation class implements the 'Serial' interface using the WiringPi Serial library.</p>
042 * 
043 * <p>
044 * Before using the Pi4J library, you need to ensure that the Java VM in configured with access to
045 * the following system libraries:
046 * <ul>
047 * <li>pi4j</li>
048 * <li>wiringPi</li>
049 * </ul>
050 * <blockquote> This library depends on the wiringPi native system library.</br> (developed by
051 * Gordon Henderson @ <a href="http://wiringpi.com/">http://wiringpi.com/</a>)
052 * </blockquote>
053 * </p>
054 * 
055 * @see com.pi4j.io.serial.Serial
056 * @see com.pi4j.io.serial.SerialDataEvent
057 * @see com.pi4j.io.serial.SerialDataListener
058 * @see com.pi4j.io.serial.SerialFactory
059 * 
060 * @see <a href="http://www.pi4j.com/">http://www.pi4j.com/</a>
061 * @author Robert Savage (<a
062 *         href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
063 */
064public class SerialImpl implements Serial {
065
066    protected int fileDescriptor = -1;
067    protected final CopyOnWriteArrayList<SerialDataListener> listeners = new CopyOnWriteArrayList<>();
068    protected SerialDataMonitorThread monitor;
069    protected int monitorInterval = Serial.DEFAULT_MONITOR_INTERVAL;
070    protected boolean isshutdown = false;
071
072    /**
073     * This method is call to open a serial port for communication. Throws SerialException on error.
074     * 
075     * @see #DEFAULT_COM_PORT
076     * 
077     * @param device The device address of the serial port to access. You can use constant
078     *            'DEFAULT_COM_PORT' if you wish to access the default serial port provided via the
079     *            GPIO header.
080     * @param baudRate The baud rate to use with the serial port.
081     * @throws SerialPortException Exception thrown on any error.
082     */
083    public void open(String device, int baudRate) throws SerialPortException {
084        fileDescriptor = com.pi4j.wiringpi.Serial.serialOpen(device, baudRate);
085        if (fileDescriptor == -1) {
086                throw new SerialPortException("Cannot open serial port");
087        }
088    }
089
090    /**
091     * This method is called to determine if the serial port is already open.
092     * 
093     * @see #open(String, int)
094     * @return a value of 'true' is returned if the serial port is already open.
095     */
096    public boolean isOpen() {
097        return (fileDescriptor >= 0);
098    }
099    
100    /**
101     * This method is called to determine if the serial port is already closed.
102     * 
103     * @see #open(String, int)
104     * @return a value of 'true' is returned if the serial port is already in the closed state.
105     */
106    public boolean isClosed(){
107        return !(isOpen());
108    }
109    
110
111    /**
112     * This method is called to close a currently open open serial port.
113     */
114    public void close() throws IllegalStateException {
115        
116        // validate state
117        if (isClosed()) 
118            throw new IllegalStateException("Serial connection is not open; cannot 'close()'.");
119        
120        // close serial port now    
121        com.pi4j.wiringpi.Serial.serialClose(fileDescriptor);
122        }
123
124    /**
125     * This method is called to immediately flush the serial data transmit buffer and force any
126     * pending data to be sent to the serial port immediately.
127     */
128    public void flush() throws IllegalStateException {
129
130        // validate state
131        if (isClosed()) 
132            throw new IllegalStateException("Serial connection is not open; cannot 'flush()'.");
133        
134        // flush data to serial port immediately
135        com.pi4j.wiringpi.Serial.serialFlush(fileDescriptor);
136    }
137
138    /**
139     * <p> This method will read the next character available from the serial port receive buffer.</p>
140     * <p>
141     * <b>NOTE: If a serial data listener has been implemented and registered with this class, then
142     * this method should not be called directly. A background thread will be running to collect
143     * received data from the serial port receive buffer and the received data will be available on
144     * via the event.</b>
145     * </p>
146     * 
147     * @return next available character in the serial data buffer
148     */
149    public char read() throws IllegalStateException {
150        
151        // validate state
152        if (isClosed()) 
153            throw new IllegalStateException("Serial connection is not open; cannot 'read()'.");
154
155        // attempt read byte from serial port 
156        return (char) com.pi4j.wiringpi.Serial.serialGetchar(fileDescriptor);
157    }
158
159    /**
160     * This method is called to submit a single character of data to the serial port transmit
161     * buffer.
162     * 
163     * @param data  A single character to be transmitted.
164     */
165    public void write(char data) throws IllegalStateException {
166        
167        // validate state
168        if (isClosed()) 
169            throw new IllegalStateException("Serial connection is not open; cannot 'write(char)'.");
170        
171        // write character to serial port
172        com.pi4j.wiringpi.Serial.serialPutchar(fileDescriptor, data);
173    }
174
175    /**
176     * This method is called to submit a character array of data to the serial port transmit buffer.
177     * 
178     * @param data A character array of data to be transmitted.
179     */
180    public void write(char data[]) throws IllegalStateException {
181        
182        // validate state
183        if (isClosed()) 
184            throw new IllegalStateException("Serial connection is not open; cannot 'write(char[])'.");
185
186        // write character array to serial port
187        write(new String(data));
188    }
189
190    /**
191     * This method is called to submit a single byte of data to the serial port transmit buffer.
192     * 
193     * @param data  A single byte to be transmitted.
194     */
195    public void write(byte data) throws IllegalStateException {
196
197        // validate state
198        if (isClosed()) 
199            throw new IllegalStateException("Serial connection is not open; cannot 'write(byte)'.");
200
201        // write byte to serial port        
202        com.pi4j.wiringpi.Serial.serialPutchar(fileDescriptor, (char) data);
203    }
204
205    /**
206     * This method is called to submit a byte array of data to the serial port transmit buffer.
207     * 
208     * @param data  A byte array of data to be transmitted.
209     */
210    public void write(byte data[]) throws IllegalStateException {
211        
212        // validate state
213        if (isClosed()) 
214            throw new IllegalStateException("Serial connection is not open; cannot 'write(byte[])'.");
215
216        // write byte array to serial port
217        for(byte b : data){
218            // write byte to serial port        
219            com.pi4j.wiringpi.Serial.serialPutchar(fileDescriptor, (char)b);
220        }
221    }
222
223    /**
224     * This method is called to submit a string of data to the serial port transmit buffer.
225     * 
226     * @param data A string of data to be transmitted.
227     */
228    public void write(String data) throws IllegalStateException {
229        
230        // validate state
231        if (isClosed()) 
232            throw new IllegalStateException("Serial connection is not open; cannot 'write(String)'.");
233
234        // break data into packets of 1024 bytes
235        int position = 0;
236        while (position < data.length()) {
237            int length = 1024;
238            if (position + 1024 > data.length()) {
239                com.pi4j.wiringpi.Serial.serialPuts(fileDescriptor, data.substring(position));
240            } else {
241                com.pi4j.wiringpi.Serial.serialPuts(fileDescriptor,
242                                                    data.substring(position, (position + length)));
243            }
244            position += length;
245        }
246    }
247    
248    /**
249     * This method is called to submit a string of data with trailing CR + LF characters to the
250     * serial port transmit buffer.
251     * 
252     * @param data A string of data to be transmitted.
253     */
254    public void writeln(String data) throws IllegalStateException {
255        
256        // validate state
257        if (isClosed()) 
258            throw new IllegalStateException("Serial connection is not open; cannot 'writeln(String)'.");
259        
260        // write string with CR+LF to serial port 
261        write(data + "\r\n");
262    }
263
264    /**
265     * This method is called to submit a string of formatted data to the serial port transmit
266     * buffer.
267     * 
268     * @param data A string of formatted data to be transmitted.
269     * @param args  A series of arguments that can be included for the format string variable
270     *            replacements.
271     */
272    public void write(String data, String... args) throws IllegalStateException {
273        
274        // validate state
275        if (isClosed()) 
276            throw new IllegalStateException("Serial connection is not open; cannot 'write(String data, String...args)'.");
277        
278        // write formatted string to serial port 
279        write(String.format(data, (Object[]) args));
280    }
281
282    /**
283     * This method is called to submit a string of formatted data with trailing CR + LF characters
284     * to the serial port transmit buffer.
285     * 
286     * @param data  A string of formatted data to be transmitted.
287     * @param args  A series of arguments that can be included for the format string variable
288     *            replacements.
289     */
290    public void writeln(String data, String... args) throws IllegalStateException {
291        
292        // validate state
293        if (isClosed()) 
294            throw new IllegalStateException("Serial connection is not open; cannot 'writeln(String data, String...args)'.");
295        
296        // write formatted string with CR+LF to serial port         
297        write(data + "\r\n", args);
298    }
299    
300    /**
301     * This method is called to determine if and how many bytes are available on the serial received
302     * data buffer.
303     * 
304     * @return  The number of available bytes pending in the serial received buffer is returned.
305     */
306    public int availableBytes() throws IllegalStateException {
307        
308        // validate state
309        if (isClosed()) 
310            throw new IllegalStateException("Serial connection is not open; cannot 'availableBytes()'.");
311        
312        // return the number of bytes available in the serial buffer                 
313        return com.pi4j.wiringpi.Serial.serialDataAvail(fileDescriptor);
314    }
315
316    /**
317     * <p>Add Serial Event Listener</p>
318     * 
319     * <p> Java consumer code can call this method to register itself as a listener for serial data
320     * events. </p>
321     * 
322     * @see com.pi4j.io.serial.SerialDataListener
323     * @see com.pi4j.io.serial.SerialDataEvent
324     * 
325     * @param listener  A class instance that implements the SerialListener interface.
326     */
327    public synchronized void addListener(SerialDataListener... listener) {
328        // add the new listener to the list of listeners
329        Collections.addAll(listeners, listener);
330
331        // if there is not a current listening monitor thread running,
332        // then lets start it now
333        if (monitor == null || !monitor.isAlive()) {
334            monitor = new SerialDataMonitorThread(this, listeners);
335            monitor.start();
336        }
337    }
338
339    /**
340     * <p>Remove Serial Event Listener</p>
341     * 
342     * <p> Java consumer code can call this method to unregister itself as a listener for serial data
343     * events. </p>
344     * 
345     * @see com.pi4j.io.serial.SerialDataListener
346     * @see com.pi4j.io.serial.SerialDataEvent
347     * 
348     * @param listener A class instance that implements the SerialListener interface.
349     */
350    public synchronized void removeListener(SerialDataListener... listener) {
351        // remove the listener from the list of listeners
352        for (SerialDataListener lsnr : listener) {
353            listeners.remove(lsnr);
354        }
355
356        // if there are not more listeners, then exit and destroy
357        // the monitor thread now
358        if (listeners.isEmpty() && monitor != null) {
359            monitor.shutdown();
360            monitor = null;
361        }
362    }
363    
364    /**
365     * This method returns TRUE if the serial interface has been shutdown.
366     * 
367     * @return shutdown state
368     */
369    @Override
370    public boolean isShutdown(){
371        return isshutdown;
372    }
373
374    
375    /**
376     * This method can be called to forcefully shutdown all 
377     * serial data monitoring threads.
378     */
379    @Override
380    public synchronized void shutdown()
381    {
382        // close serial port if still open
383        if(isOpen())
384            close();
385
386        // prevent reentrant invocation
387        if(isShutdown())
388            return;
389        
390        // shutdown monitoring thread
391        if(monitor != null)
392            monitor.shutdown();
393    }
394
395    /**
396     * This method returns the serial data receive monitor delay interval in milliseconds.
397     * @return interval milliseconds
398     */
399    @Override
400    public int getMonitorInterval(){
401        return monitorInterval;
402    }
403
404    /**
405     * This method set the serial data receive monitor delay interval in milliseconds.
406     *
407     * @param interval number of milliseconds
408     */
409    @Override
410    public void setMonitorInterval(int interval){
411        monitorInterval = interval;
412    }
413}