aboutsummaryrefslogtreecommitdiff
path: root/sync/src/main/scala/akka/serial/sync/SerialConnection.scala
diff options
context:
space:
mode:
Diffstat (limited to 'sync/src/main/scala/akka/serial/sync/SerialConnection.scala')
-rw-r--r--sync/src/main/scala/akka/serial/sync/SerialConnection.scala142
1 files changed, 142 insertions, 0 deletions
diff --git a/sync/src/main/scala/akka/serial/sync/SerialConnection.scala b/sync/src/main/scala/akka/serial/sync/SerialConnection.scala
new file mode 100644
index 0000000..7d80a4c
--- /dev/null
+++ b/sync/src/main/scala/akka/serial/sync/SerialConnection.scala
@@ -0,0 +1,142 @@
+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)
+ }
+
+}