aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2013-08-04 22:01:24 +0200
committerJakob Odersky <jodersky@gmail.com>2013-08-04 22:01:24 +0200
commit10c5c4e68d027e6bbabaad17381c79df24401027 (patch)
tree00b980f9cbe4e66eee265de36d8abb57614af514
parent9efeb44063001d4df0fb7a665a52bfcc52d5a290 (diff)
downloadakka-serial-10c5c4e68d027e6bbabaad17381c79df24401027.tar.gz
akka-serial-10c5c4e68d027e6bbabaad17381c79df24401027.tar.bz2
akka-serial-10c5c4e68d027e6bbabaad17381c79df24401027.zip
group serial settings in class, cosmetic changes & documentation
-rw-r--r--README.md4
-rw-r--r--flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java2
-rw-r--r--flow-main/src/main/scala/com/github/jodersky/flow/Serial.scala92
-rw-r--r--flow-main/src/main/scala/com/github/jodersky/flow/SerialManager.scala9
-rw-r--r--flow-main/src/main/scala/com/github/jodersky/flow/SerialOperator.scala78
-rw-r--r--flow-main/src/main/scala/com/github/jodersky/flow/SerialSettings.scala11
-rw-r--r--flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/ConsoleReader.scala30
-rw-r--r--flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Main.scala14
-rw-r--r--flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Terminal.scala30
9 files changed, 173 insertions, 97 deletions
diff --git a/README.md b/README.md
index 17204e7..40eb122 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ The main reason for yet another serial communication library for the JVM is that
## Native side
Since hardware is involved in serial communication, a Scala-only solution is not possible. Nevertherless, the native code is kept simple and minimalistic with the burden of dealing with threads left to Scala. The code aims to be POSIX compliant and therefore easily portable.
-## Usage
+## Basic usage
(this section will be updated as soon as a maven repository is available)
Clone the repository and run `sbt flow/publish-local` to publish the library locally. From there on, you may use the library in any project simply by adding a library dependency to it.
@@ -18,7 +18,7 @@ Examples on flow's usage are located in the flow-samples directory. The examples
Since flow integrates into the Akka-IO framework, a good resource on its general design is the framework's documentation at http://doc.akka.io/docs/akka/2.2.0/scala/io.html
-### Currently supported platforms
+## Currently supported platforms
| OS (tested on) | Architecture | Notes |
|-------------------|-------------------------|-----------------------------------------------------------------------|
diff --git a/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java b/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
index e7322ed..524803e 100644
--- a/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
+++ b/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
@@ -69,7 +69,7 @@ class NativeSerial {
* @return E_IO on error */
native static int close(long serial);
- /**Sets debugging option. If debugging is enabled, detailed error message are printed from method calls. */
+ /**Sets debugging option. If debugging is enabled, detailed error message are printed (to stderr) from method calls. */
native static void debug(boolean value);
}
diff --git a/flow-main/src/main/scala/com/github/jodersky/flow/Serial.scala b/flow-main/src/main/scala/com/github/jodersky/flow/Serial.scala
index 54fea6b..abc8f39 100644
--- a/flow-main/src/main/scala/com/github/jodersky/flow/Serial.scala
+++ b/flow-main/src/main/scala/com/github/jodersky/flow/Serial.scala
@@ -7,62 +7,100 @@ import akka.util.ByteString
/** Defines messages used by flow's serial IO layer. */
object Serial extends ExtensionKey[SerialExt] {
- /** A message extending this trait is to be viewed as a command, that is an out-bound message. */
+ /** A message extending this trait is to be viewed as a command, an out-bound message issued by the client to flow's API. */
trait Command
- /** A message extending this trait is to be viewed as an event, that is an in-bound message. */
+ /** A message extending this trait is to be viewed as an event, an in-bound message issued by flow to the client. */
trait Event
-
+
+ /** A command has failed. */
case class CommandFailed(command: Command, reason: Throwable) extends Event
/**
- * Open a new serial port. Send this command to the serial manager to request the opening of a serial port. The manager will
+ * Open a new serial port.
+ *
+ * Send this command to the serial manager to request the opening of a serial port. The manager will
* attempt to open a serial port with the specified parameters and, if successful, create a `SerialOperator` actor associated to the port.
- * The operator actor acts as an intermediate to the underlying native serial port, dealing with threading issues and the such. It will send any events
- * to the sender of this message.
- * The manager will respond with an `OpenFailed` in case the port could not be opened, or the operator will respond with `Opened`.
- * @param port name of serial port
- * @param baud baud rate to use with serial port
- * @param characterSize size of a character of the data sent through the serial port
- * @param twoStopBits set to use two stop bits instead of one
- * @param parity type of parity to use with serial port
+ * The operator actor acts as an intermediate to the underlying native serial port, dealing with threading issues and dispatching messages.
+ *
+ * In case the port is successfully opened, the operator will respond with an `Opened` message.
+ * In case the port cannot be opened, the manager will respond with a `CommandFailed` message.
+ *
+ * @param settings settings of serial port to open
+ */
+ case class Open(settings: SerialSettings) extends Command
+
+ /**
+ * A port has been successfully opened.
+ *
+ * Event sent by a port operator, indicating that a serial port was successfully opened. The sender
+ * of this message is the operator associated to the given serial port. Furthermore, an additional reference
+ * to the operator is provided in this class' `operator` field.
+ *
+ * @param settings settings of port that was opened
+ * @param operator operator associated with the serial port
*/
- case class Open(port: String, baud: Int, characterSize: Int = 8, twoStopBits: Boolean = false, parity: Parity.Parity = Parity.None) extends Command
+ case class Opened(settings: SerialSettings, operator: ActorRef) extends Event
/**
- * Event sent from a port operator, indicating that a serial port was successfully opened. The sender of this message is the operator associated to the given serial port.
- * @param port name of serial port
- * @param baud baud rate to use with serial port
- * @param characterSize size of a character of the data sent through the serial port
- * @param twoStopBits set to use two stop bits instead of one
- * @param parity type of parity to use with serial port
+ * Register an actor to receive events.
+ *
+ * Send this command to a serial operator to register an actor for notification on the reception of data on the operator's associated port.
+ * Upon reception, data will be sent by the operator to registered actors in form of `Received` events.
+ *
+ * @param receiver actor to register
*/
- case class Opened(port: String, baud: Int, characterSize: Int, twoStopBits: Boolean, parity: Parity.Parity, operator: ActorRef) extends Event
-
-
case class Register(receiver: ActorRef) extends Command
+
+ /**
+ * Unregister an actor from receiving events.
+ *
+ * Send this command to a serial operator to unregister an actor for notification on the reception of data on the operator's associated port.
+ *
+ * @param receiver actor to unregister
+ */
case class Unregister(receiver: ActorRef) extends Command
/**
- * Event sent by operator, indicating that data was received from the operator's serial port.
- * @param data data received by port
+ * Data has been received.
+ *
+ * Event sent by an operator, indicating that data was received on the operator's serial port.
+ * Clients must register (see `Register`) with a serial operator to receive these events.
+ *
+ * @param data data received on the port
*/
case class Received(data: ByteString) extends Event
/**
- * Write data to serial port. Send this command to an operator to write the given data to its associated serial port.
+ * Write data to a serial port.
+ *
+ * Send this command to an operator to write the given data to its associated serial port.
+ * An acknowledgment may be set, in which case it is sent back to the sender on a successful write.
+ * Note that a successful write does not guarantee the actual transmission of data through the serial port,
+ * it merely guarantees that the data has been stored in the operating system's kernel buffer, ready to
+ * be written.
+ *
* @param data data to be written to port
* @param ack acknowledgment sent back to sender once data has been enqueued in kernel for sending
*/
case class Write(data: ByteString, ack: Event = NoAck) extends Command
- /** Special type of acknowledgment that is not sent back. */
+ /**
+ * Special type of acknowledgment that is not sent back.
+ */
case object NoAck extends Event
- /** Request closing of port. Send this command to an operator to close its associated port. */
+ /**
+ * Request closing of port.
+ *
+ * Send this command to an operator to close its associated port. The operator will respond
+ * with a `Closed` message upon closing the serial port.
+ */
case object Close extends Command
/**
+ * A port has been closed.
+ *
* Event sent from operator, indicating that its port has been closed.
*/
case object Closed extends Event
diff --git a/flow-main/src/main/scala/com/github/jodersky/flow/SerialManager.scala b/flow-main/src/main/scala/com/github/jodersky/flow/SerialManager.scala
index 0d69213..b3128ac 100644
--- a/flow-main/src/main/scala/com/github/jodersky/flow/SerialManager.scala
+++ b/flow-main/src/main/scala/com/github/jodersky/flow/SerialManager.scala
@@ -20,6 +20,7 @@ import akka.actor.actorRef2Scala
/**
* Actor that manages serial port creation. Once opened, a serial port is handed over to
* a dedicated operator actor that acts as an intermediate between client code and the native system serial port.
+ * @see SerialOperator
*/
class SerialManager extends Actor with ActorLogging {
import SerialManager._
@@ -32,12 +33,12 @@ class SerialManager extends Actor with ActorLogging {
}
def receive = {
- case c @ Open(port, baud, cs, tsb, parity) => Try { InternalSerial.open(port, baud, cs, tsb, parity.id) } match {
+ case c @ Open(s) => Try { InternalSerial.open(s.port, s.baud, s.characterSize, s.twoStopBits, s.parity.id) } match {
case Failure(t) => sender ! CommandFailed(c, t)
case Success(serial) => {
- val operator = context.actorOf(Props(classOf[SerialOperator], serial), name = escapePortString(port))
- val opened = Opened(serial.port, serial.baud, serial.characterSize, serial.twoStopBits, Parity(serial.parity), operator)
- sender.tell(opened, operator)
+ val operator = context.actorOf(SerialOperator(serial), name = escapePortString(s.port))
+ val settings = SerialSettings(serial.port, serial.baud, serial.characterSize, serial.twoStopBits, Parity(serial.parity))
+ sender.tell(Opened(settings, operator), operator)
}
}
}
diff --git a/flow-main/src/main/scala/com/github/jodersky/flow/SerialOperator.scala b/flow-main/src/main/scala/com/github/jodersky/flow/SerialOperator.scala
index 1c24729..3ac50c0 100644
--- a/flow-main/src/main/scala/com/github/jodersky/flow/SerialOperator.scala
+++ b/flow-main/src/main/scala/com/github/jodersky/flow/SerialOperator.scala
@@ -10,48 +10,19 @@ import akka.actor.Terminated
import akka.actor.actorRef2Scala
import akka.util.ByteString
import scala.collection.mutable.HashSet
+import akka.actor.Props
-/** Operator associated to an open serial port. All communication with a port is done via an operator. Operators are created though the serial manager. */
+/**
+ * Operator associated to an open serial port. All communication with a port is done via an operator. Operators are created though the serial manager.
+ * @see SerialManager
+ */
class SerialOperator(serial: InternalSerial) extends Actor with ActorLogging {
import SerialOperator._
import context._
- override def preStart() = {
- Reader.start()
- }
-
- override def postStop = {
- serial.close()
- }
-
- def receive: Receive = {
-
- case Register(actor) => receiversLock.synchronized{
- receivers += actor
- }
-
- case Unregister(actor) => receiversLock.synchronized{
- receivers -= actor
- }
-
- case Write(data, ack) => {
- val sent = serial.write(data.toArray)
- if (ack != NoAck) sender ! ack
- }
-
- case Close => {
- sendAllReceivers(Closed)
- context stop self
- }
-
- //go down with reader thread
- case ReadException(ex) => throw ex
-
- }
-
private val receivers = new HashSet[ActorRef]
private val receiversLock = new Object
- private def sendAllReceivers(msg: Any) = receiversLock.synchronized {
+ private def tellAllReceivers(msg: Any) = receiversLock.synchronized {
receivers.foreach { receiver =>
receiver ! msg
}
@@ -63,7 +34,7 @@ class SerialOperator(serial: InternalSerial) extends Actor with ActorLogging {
while (continueReading) {
try {
val data = ByteString(serial.read())
- sendAllReceivers(Received(data))
+ tellAllReceivers(Received(data))
} catch {
//port is closing, stop thread gracefully
@@ -88,8 +59,43 @@ class SerialOperator(serial: InternalSerial) extends Actor with ActorLogging {
}
}
+ override def preStart() = {
+ Reader.start()
+ }
+
+ override def postStop = {
+ serial.close()
+ }
+
+ def receive: Receive = {
+
+ case Register(actor) => receiversLock.synchronized {
+ receivers += actor
+ }
+
+ case Unregister(actor) => receiversLock.synchronized {
+ receivers -= actor
+ }
+
+ case Write(data, ack) => {
+ val sent = serial.write(data.toArray)
+ if (ack != NoAck) sender ! ack
+ }
+
+ case Close => {
+ tellAllReceivers(Closed)
+ context stop self
+ }
+
+ //go down with reader thread
+ case ReadException(ex) => throw ex
+
+ }
+
}
object SerialOperator {
private case class ReadException(ex: Exception)
+
+ def apply(serial: InternalSerial) = Props(classOf[SerialOperator], serial)
} \ No newline at end of file
diff --git a/flow-main/src/main/scala/com/github/jodersky/flow/SerialSettings.scala b/flow-main/src/main/scala/com/github/jodersky/flow/SerialSettings.scala
new file mode 100644
index 0000000..a3bc5e4
--- /dev/null
+++ b/flow-main/src/main/scala/com/github/jodersky/flow/SerialSettings.scala
@@ -0,0 +1,11 @@
+package com.github.jodersky.flow
+
+/**
+ * Groups settings used in communication over a serial port.
+ * @param port name of serial port
+ * @param baud baud rate to use with serial port
+ * @param characterSize size of a character of the data sent through the serial port
+ * @param twoStopBits set to use two stop bits instead of one
+ * @param parity type of parity to use with serial port
+ */
+case class SerialSettings(port: String, baud: Int, characterSize: Int = 8, twoStopBits: Boolean = false, parity: Parity.Parity = Parity.None) \ No newline at end of file
diff --git a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/ConsoleReader.scala b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/ConsoleReader.scala
index 6b06206..c4401e7 100644
--- a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/ConsoleReader.scala
+++ b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/ConsoleReader.scala
@@ -1,19 +1,33 @@
package com.github.jodersky.flow.samples.terminal
import akka.actor._
+import java.io.BufferedReader
+import java.io.InputStreamReader
-case object Read
-case class ConsoleInput(in: String)
class ConsoleReader extends Actor {
import context._
-
+ import ConsoleReader._
+
def receive = {
- case Read => read()
+ case Read => read() match {
+ case Some(input) => parent ! ConsoleInput(input)
+ case None => parent ! EOT
+ }
}
-
- def read() = {
- val in = Console.readLine()
- parent ! ConsoleInput(in)
+
+ def read(): Option[String] = {
+ val eot = 4
+ val line = Console.readLine
+ if (line == ":q") None else Some(line)
}
+}
+
+object ConsoleReader {
+
+ case object Read
+
+ case object EOT
+ case class ConsoleInput(in: String)
+
} \ No newline at end of file
diff --git a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Main.scala b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Main.scala
index b6f45f3..27686ce 100644
--- a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Main.scala
+++ b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Main.scala
@@ -8,14 +8,12 @@ import akka.actor.actorRef2Scala
import akka.util.ByteString
object Main {
-
- def defaultIfEmpty(in: String, default: String): String = if (in.isEmpty) default else in
-
+
def ask(label: String, default: String) = {
print(s"${label} [${default}]: ")
val in = Console.readLine()
println("")
- defaultIfEmpty(in, default)
+ if (in.isEmpty) default else in
}
def main(args: Array[String]): Unit = {
@@ -23,12 +21,14 @@ object Main {
val baud = ask("Baud rate", "115200").toInt
val cs = ask("Char size", "8").toInt
val tsb = ask("Use two stop bits", "false").toBoolean
- val parity = Parity(ask("Parity [0=None, 1=Odd, 2=Even]", "0").toInt)
+ val parity = Parity(ask("Parity (0=None, 1=Odd, 2=Even)", "0").toInt)
+
+ val settings = SerialSettings(port, baud, cs, tsb, parity)
+
println("Starting terminal system, enter :q to exit.")
-
internal.InternalSerial.debug(true)
val system = ActorSystem("flow")
+ val terminal = system.actorOf(Terminal(settings), name = "terminal")
system.registerOnTermination(println("Stopped terminal system."))
- val terminal = system.actorOf(Props(classOf[Terminal], port, baud, cs, tsb, parity), name = "terminal")
}
} \ No newline at end of file
diff --git a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Terminal.scala b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Terminal.scala
index 2997e77..3c1eee6 100644
--- a/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Terminal.scala
+++ b/flow-samples/terminal/src/main/scala/com/github/jodersky/flow/samples/terminal/Terminal.scala
@@ -10,16 +10,17 @@ import com.github.jodersky.flow.Serial
import akka.actor.Terminated
import com.github.jodersky.flow.Parity
import akka.actor.Props
+import com.github.jodersky.flow.SerialSettings
-class Terminal(port: String, baud: Int, cs: Int, tsb: Boolean, parity: Parity.Parity) extends Actor with ActorLogging {
+class Terminal(settings: SerialSettings) extends Actor with ActorLogging {
import Terminal._
import context._
val reader = actorOf(Props[ConsoleReader])
override def preStart() = {
- log.info(s"Requesting manager to open port: ${port}, baud: ${baud}")
- IO(Serial) ! Serial.Open(port, baud)
+ log.info(s"Requesting manager to open port: ${settings.port}, baud: ${settings.baud}")
+ IO(Serial) ! Serial.Open(settings)
}
override def postStop() = {
@@ -31,23 +32,23 @@ class Terminal(port: String, baud: Int, cs: Int, tsb: Boolean, parity: Parity.Pa
log.error(s"Connection failed, stopping terminal. Reason: ${reason}")
context stop self
}
- case Opened(port, _, _, _, _) => {
- log.info(s"Port ${port} is now open.")
+ case Opened(s, _) => {
+ log.info(s"Port ${s.port} is now open.")
val operator = sender
context become opened(operator)
context watch operator
operator ! Register(self)
- reader ! Read
+ reader ! ConsoleReader.Read
}
}
def opened(operator: ActorRef): Receive = {
case Received(data) => {
- log.info(s"Received data: ${formatData(data)} (${new String(data.toArray, "UTF-8")})")
+ log.info(s"Received data: ${formatData(data)}")
}
- case Wrote(data) => log.info(s"Wrote data: ${formatData(data)} (${new String(data.toArray, "UTF-8")})")
+ case Wrote(data) => log.info(s"Wrote data: ${formatData(data)}")
case Closed => {
log.info("Operator closed normally, exiting terminal.")
@@ -60,22 +61,27 @@ class Terminal(port: String, baud: Int, cs: Int, tsb: Boolean, parity: Parity.Pa
context stop self
}
- case ConsoleInput(":q") => {
+ case ConsoleReader.EOT => {
log.info("Initiating close.")
operator ! Close
}
- case ConsoleInput(input) => {
+ case ConsoleReader.ConsoleInput(input) => {
val data = ByteString(input.getBytes)
operator ! Write(data, Wrote(data))
- reader ! Read
+ reader ! ConsoleReader.Read
}
}
- private def formatData(data: ByteString) = data.mkString("[", ",", "]")
+
}
object Terminal {
case class Wrote(data: ByteString) extends Event
+
+ def apply(settings: SerialSettings) = Props(classOf[Terminal], settings)
+
+ private def formatData(data: ByteString) = data.mkString("[", ",", "]") + " " + (new String(data.toArray, "UTF-8"))
+
} \ No newline at end of file