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