ch.jodersky.flow.SerialConnection.scala Maven / Gradle / Ivy
The newest version!
package ch.jodersky.flow
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.
*/
private[flow] 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, 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) {
try {
reading = true
unsafe.read(buffer)
} 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 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) {
try {
writing = true
unsafe.write(buffer, buffer.position)
} finally {
writing = false
if (closed.get) writeLock.notify()
}
} else {
throw new PortClosedException(s"${port} is closed")
}
}
}
private[flow] 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)
}
}