aboutsummaryrefslogblamecommitdiff
path: root/sync/src/main/scala/akka/serial/sync/SerialConnection.scala
blob: 7d80a4cf1fcdd2771d9a113314c4c2048d335b41 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                   



                                                
   


                                                                                                    


                                                                             
                                

                       
   
 




















                                                                                               
                         
                             
                                   

                              
                                   
       
                    




                                                                    


                                                                     



                                                                 
                                               







                                                                     
           
                      


                                   

                       
                                         

            
                                                         










                                                                                                     
                                               






                                                               
           

                                             

                       
                                          

            
                                                         


     

 
                         













                                                                                                     











                                                         

   
 
package akka.serial
package sync

import java.nio.ByteBuffer
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Represents a serial connection in a more secure and object-oriented style than `UnsafeSerial`. In
 * contrast to the latter, this class encapsulates and secures any pointers used to communicate with
 * the native backend and is thread-safe.
 *
 * The underlying serial port is assumed open when this class is initialized.
 */
class SerialConnection private (
  unsafe: UnsafeSerial,
  val port: String
) {

  private var reading: Boolean = false
  private val readLock = new Object

  private var writing: Boolean = false
  private val writeLock = new Object

  private val closed = new AtomicBoolean(false)

  /**
   * Checks if this serial port is closed.
   */
  def isClosed = closed.get()

  /**
   * Closes the underlying serial connection. Any callers blocked on read or write will return.
   * A call of this method has no effect if the serial port is already closed.
   * @throws IOException on IO error
   */
  def close(): Unit = this.synchronized {
    if (!closed.get) {
      closed.set(true)
      unsafe.cancelRead()
      readLock.synchronized {
        while (reading) this.wait()
      }
      writeLock.synchronized {
        while (writing) this.wait()
      }
      unsafe.close()
    }
  }

  /**
   * Reads data from underlying serial connection into a ByteBuffer.
   * Note that data is read into the buffer's memory, starting at the
   * first position. The buffer's limit is set to the number of bytes
   * read.
   *
   * A call to this method is blocking, however it is interrupted
   * if the connection is closed.
   *
   * This method works only for direct buffers.
   *
   * @param buffer a ByteBuffer into which data is read
   * @return the actual number of bytes read
   * @throws PortInterruptedException if port is closed while reading
   * @throws IOException on IO error
   */
  def read(buffer: ByteBuffer): Int = readLock.synchronized {
    if (!closed.get) {
      try {
        reading = true
        val n = unsafe.read(buffer)
        buffer.limit(n)
        n
      } finally {
        reading = false
        if (closed.get) readLock.notify()
      }
    } else {
      throw new PortClosedException(s"${port} is closed")
    }
  }

  /**
   * Writes data from a ByteBuffer to underlying serial connection.
   * Note that data is read from the buffer's memory, its attributes
   * such as position and limit are not modified.
   *
   * The write is non-blocking, this function returns as soon as the data is copied into the kernel's
   * transmission buffer.
   *
   * This method works only for direct buffers.
   *
   * @param buffer a ByteBuffer from which data is taken
   * @return the actual number of bytes written
   * @throws IOException on IO error
   */
  def write(buffer: ByteBuffer): Int = writeLock.synchronized {
    if (!closed.get) {
      try {
        writing = true
        unsafe.write(buffer, buffer.position)
      } finally {
        writing = false
        if (closed.get) writeLock.notify()
      }
    } else {
      throw new PortClosedException(s"${port} is closed")
    }
  }

}

object SerialConnection {

  /**
   * Opens a new connection to a serial port.
   * This method acts as a factory to creating serial connections.
   *
   * @param port name of serial port to open
   * @param settings settings with which to initialize the connection
   * @return an instance of the open serial connection
   * @throws NoSuchPortException if the given port does not exist
   * @throws AccessDeniedException if permissions of the current user are not sufficient to open port
   * @throws PortInUseException if port is already in use
   * @throws InvalidSettingsException if any of the specified settings are invalid
   * @throws IOException on IO error
   */
  def open(
    port: String,
    settings: SerialSettings
  ): SerialConnection = synchronized {
    val pointer = UnsafeSerial.open(
      port,
      settings.baud,
      settings.characterSize,
      settings.twoStopBits,
      settings.parity.id
    )
    new SerialConnection(new UnsafeSerial(pointer), port)
  }

}