aboutsummaryrefslogblamecommitdiff
path: root/flow-core/src/main/scala/com/github/jodersky/flow/internal/SerialConnection.scala
blob: 73416e3520985355784ad1e6f6530d0d20d0a62e (plain) (tree)
1
2
3
4
5
6

                                



                                                









                                                                                                             

                           

























                                                                                               
                                   

                              
                                   


























                                                                       

                                                    

                       
                                         

























                                                                                                     
                                                                  

                                                                   

                       
                                          












































                                                                                                                               
 
package com.github.jodersky.flow
package internal

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

/**
 * Represents a serial connection in a more secure and object-oriented style than `NativeSerial`. 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 (
  val port: String,
  val settings: SerialSettings,
  private val pointer: Long
) {

  import SerialConnection._

  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)
      NativeSerial.cancelRead(pointer)
      readLock.synchronized {
        while (reading) this.wait()
      }
      writeLock.synchronized {
        while (writing) this.wait()
      }
      NativeSerial.close(pointer)
    }
  }

  /**
   * Reads data from underlying serial connection into a ByteBuffer.
   * Note that data is read into the buffer's memory, its attributes
   * such as position and limit are not modified.
   *
   * A call to this method is blocking, however it is interrupted
   * if the connection is closed.
   *
   * This method works for direct and indirect buffers but is optimized
   * for the former.
   *
   * @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) {
      reading = true
      try {
        transfer(
          b => NativeSerial.readDirect(pointer, b),
          b => NativeSerial.read(pointer, b.array())
        )(buffer)
      } finally {
        reading = false
        if (closed.get) readLock.notify()
      }
    } else {
      throw new PortClosedException(s"port ${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 for direct and indirect buffers but is optimized
   * for the former.
   *
   * @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) {
      writing = true
      try {
        transfer(
          b => NativeSerial.writeDirect(pointer, b, b.position()),
          b => NativeSerial.write(pointer, b.array(), b.position())
        )(buffer)
      } finally {
        writing = false
        if (closed.get) writeLock.notify()
      }
    } else {
      throw new PortClosedException(s"port ${port} is closed")
    }
  }

  private def transfer[A](direct: ByteBuffer => A, indirect: ByteBuffer => A)(buffer: ByteBuffer): A = if (buffer.isDirect()) {
    direct(buffer)
  } else if (buffer.hasArray()) {
    indirect(buffer)
  } else {
    throw new IllegalArgumentException("buffer is not direct and has no array");
  }

}

object SerialConnection {
  import NativeSerial._

  /**
   * 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 = NativeSerial.open(port, settings.baud, settings.characterSize, settings.twoStopBits, settings.parity.id)
    new SerialConnection(port, settings, pointer)
  }

  /**
   * Sets native debugging mode. If debugging is enabled, detailed error messages
   * are printed (to stderr) from native method calls.
   *
   * @param value set to enable debugging
   */
  def debug(value: Boolean) = NativeSerial.debug(value)

}