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}