aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2014-11-09 13:19:34 +0100
committerJakob Odersky <jodersky@gmail.com>2014-11-09 13:25:25 +0100
commitf37bdb01ecc88618c267deaa80ecacb520001d1a (patch)
tree02e283fe71461587be8e98f57a8069decd3d1142
parent7ebffc568407e950955b9975f06a0164295cbc5a (diff)
downloadmavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.tar.gz
mavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.tar.bz2
mavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.zip
implement serial backend functionality
-rw-r--r--vfd-backend/app/plugins/UavPlugin.scala21
-rw-r--r--vfd-backend/app/views/uav.scala.html7
-rw-r--r--vfd-backend/conf/application.conf2
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala3
-rw-r--r--vfd-uav/src/main/scala/vfd/uav/Connection.scala26
-rw-r--r--vfd-uav/src/main/scala/vfd/uav/MockConnection.scala40
-rw-r--r--vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala101
7 files changed, 128 insertions, 72 deletions
diff --git a/vfd-backend/app/plugins/UavPlugin.scala b/vfd-backend/app/plugins/UavPlugin.scala
index 998f445..2e5af65 100644
--- a/vfd-backend/app/plugins/UavPlugin.scala
+++ b/vfd-backend/app/plugins/UavPlugin.scala
@@ -1,6 +1,7 @@
package plugins
import akka.actor.Actor
+import akka.actor.ActorLogging
import akka.actor.ActorRef
import akka.actor.Props
import akka.actor.actorRef2Scala
@@ -8,8 +9,8 @@ import play.api.Application
import play.api.Plugin
import play.api.libs.concurrent.Akka
import vfd.uav.Connection
-import vfd.uav.SerialConnection
import vfd.uav.MockConnection
+import vfd.uav.SerialConnection
class UavPlugin(app: Application) extends Plugin {
@@ -41,21 +42,29 @@ class UavPlugin(app: Application) extends Plugin {
}
Akka.system(app).actorOf(props, name = "uav-connection")
-
}
- def register(out: ActorRef): Props = Props(classOf[Repeater], out, connection)
+ def register(websocket: ActorRef): Props = Props(classOf[ClientConnection], websocket, connection)
}
-class Repeater(out: ActorRef, connection: ActorRef) extends Actor {
+class ClientConnection(websocket: ActorRef, uav: ActorRef) extends Actor with ActorLogging {
override def preStart = {
- connection ! Connection.Register
+ uav ! Connection.Register
}
def receive = {
- case Connection.Received(bytes) => out ! bytes
+
+ case Connection.Received(bstr) =>
+ log.info(bstr.toArray.mkString(","))
+ websocket ! bstr.toArray
+
+
+ case Connection.Closed(msg) =>
+ log.warning(msg)
+ context stop self
+
}
} \ No newline at end of file
diff --git a/vfd-backend/app/views/uav.scala.html b/vfd-backend/app/views/uav.scala.html
index 6ce3007..410e3c0 100644
--- a/vfd-backend/app/views/uav.scala.html
+++ b/vfd-backend/app/views/uav.scala.html
@@ -3,8 +3,11 @@
@main("Main", "Remote System " + remoteSystemId){
<div id="scalajsError" class="alert alert-danger" style="display: none;">
- <strong><i class="fa fa-bug"></i> Warning: an uncaught error occurred in the display software!</strong> Any visible information may be
- corrupt. The error was: "<span id="scalajsErrorMessage"></span>"
+ <p><strong><i class="fa fa-bug"></i> Error! </strong> An uncaught exception occurred in the browser application,
+ any information displayed on this website may be corrupt. This is NOT an error that should occur under normal
+ operation, it is an indication of a bug in the software.</p>
+ <p>The error was: "<span id="scalajsErrorMessage"></span>"
+ </p>
</div>
<div id="app" data-socketUrl="@socket" data-remoteSystemId="@remoteSystemId.toString">
diff --git a/vfd-backend/conf/application.conf b/vfd-backend/conf/application.conf
index 27586a2..1559b89 100644
--- a/vfd-backend/conf/application.conf
+++ b/vfd-backend/conf/application.conf
@@ -69,7 +69,7 @@ uav.system_id=1
# Type of connection to use
# 'mock' for a sample connection
-uav.connection.type=mock
+uav.connection.type=serial
# Mavlink component id used by this connection
# (not the web frontend), in case it needs to inject messages
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
index 81e6667..4f84c61 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
@@ -48,5 +48,8 @@ class MavlinkSocket(url: String, remoteSystemId: Int) {
parser.push(dv.getInt8(i))
}
}
+ connection.onclose = (e: dom.CloseEvent) => {
+ dom.alert("closed")
+ }
} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/vfd/uav/Connection.scala b/vfd-uav/src/main/scala/vfd/uav/Connection.scala
index 5d9ed54..afc1c1a 100644
--- a/vfd-uav/src/main/scala/vfd/uav/Connection.scala
+++ b/vfd-uav/src/main/scala/vfd/uav/Connection.scala
@@ -4,21 +4,43 @@ import scala.collection.mutable.ArrayBuffer
import akka.actor.Actor
import akka.actor.ActorRef
+import akka.actor.Terminated
+import akka.actor.actorRef2Scala
+import akka.util.ByteString
object Connection {
trait Event
trait Command
+
+ //received data from the uav (or any other systems on the link)
+ case class Received(bstr: ByteString) extends Event
+
+ //the connection closed or could be opened
+ case class Closed(message: String) extends Event
+
+ //register the sender to be notified on events
case object Register extends Command
- case class Received(bytes: Array[Byte]) extends Event
+ //send given bytes out to the uav (or any other systems on the link)
+ case class Send(bstr: ByteString) extends Command
}
trait Connection { that: Actor =>
private val _clients = new ArrayBuffer[ActorRef]
+
def clients = _clients.toSeq
+
def register(client: ActorRef) = {
_clients += client;
that.context.watch(client)
}
+
def unregister(client: ActorRef) = _clients -= client
-}
+
+ def sendAll(msg: Any) = clients foreach (_ ! msg)
+
+ def registration: Receive = {
+ case Connection.Register => register(sender)
+ case Terminated(client) if clients contains client => unregister(client)
+ }
+} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala b/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala
index 94a14db..126fa9a 100644
--- a/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala
+++ b/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala
@@ -1,33 +1,32 @@
package vfd.uav
import java.util.concurrent.TimeUnit.MILLISECONDS
+
import scala.concurrent.duration.FiniteDuration
+import scala.util.Random
+
+import Connection.Received
import akka.actor.Actor
-import akka.actor.Props
-import akka.actor.Terminated
-import akka.actor.actorRef2Scala
import akka.actor.ActorLogging
-import scala.util.Random
+import akka.actor.Props
+import akka.util.ByteString
class MockConnection extends Actor with ActorLogging with Connection {
import Connection._
import context._
val messageInterval = FiniteDuration(500, MILLISECONDS)
-
+
override def preStart() = {
context.system.scheduler.schedule(messageInterval, messageInterval) {
val data = MockPackets.random()
-
+
this.log.debug("sending mock flight data: " + data.mkString("(", ",", ")"))
- clients foreach (_ ! Received(data))
+ sendAll(Received(ByteString(data)))
}
}
- def receive = {
- case Connection.Register => register(sender)
- case Terminated(client) => unregister(client)
- }
+ def receive = registration
}
@@ -35,17 +34,14 @@ object MockConnection {
def apply = Props(classOf[MockConnection])
}
-object MockPackets {
-
- def random() = {
- Random.nextInt(2) match {
- case 0 => invalidCrc
- case 1 => invalidOverflow
- }
-
+object MockPackets {
+
+ def random() = Random.nextInt(2) match {
+ case 0 => invalidCrc
+ case 1 => invalidOverflow
}
-
- val invalidCrc = Array(254,1,123,13,13).map(_.toByte)
+
+ val invalidCrc = Array(254, 1, 123, 13, 13).map(_.toByte)
val invalidOverflow = {
val data = Array.fill[Byte](1006)(42)
data(0) = -2
@@ -53,5 +49,5 @@ object MockPackets {
data(1) = -1
data
}
-
+
} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala b/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala
index f1d3186..3b08215 100644
--- a/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala
+++ b/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala
@@ -1,73 +1,95 @@
package vfd.uav
+import java.util.concurrent.TimeUnit.MILLISECONDS
+
+import scala.concurrent.duration.FiniteDuration
+
import com.github.jodersky.flow.Parity
+import com.github.jodersky.flow.Serial
import com.github.jodersky.flow.SerialSettings
import akka.actor.Actor
+import akka.actor.ActorRef
import akka.actor.Props
+import akka.actor.Terminated
+import akka.actor.actorRef2Scala
+import akka.io.IO
+import akka.util.ByteString
-class SerialConnection(id: Byte, heartbeat: Int, port: String, settings: SerialSettings) extends Actor with Connection {
+class SerialConnection(id: Byte, heartbeat: Option[FiniteDuration], port: String, settings: SerialSettings) extends Actor with Connection {
import context._
-
- //TODO implement actor logic
- def receive = {
- case _ => ()
- }
-/*
+ val Heartbeat = ByteString(
+ Array(-2, 9, -121, 20, -56, 0, 0, 0, 0, 0, 2, 0, 0, 3, 3, -112, 76).map(_.toByte))
+
override def preStart() = {
- context.system.scheduler.schedule(messageInterval, messageInterval){
- self ! Connection.Write(Array(-2, 9, -121, 20, -56, 0, 0, 0, 0, 0, 2, 0, 0, 3, 3, -112, 76).map(_.toByte))
+ heartbeat foreach { interval =>
+ context.system.scheduler.schedule(interval, interval, self, Connection.Send(Heartbeat))
}
}
+ def _closed: Receive = {
- def receive = closed
-
- def closed: Receive = {
- case Connection.Register(client) =>
- register(client)
- IO(Serial) ! Open(port, settings)
+ case Connection.Register =>
+ register(sender)
+ IO(Serial) ! Serial.Open(port, settings)
context become opening
- case Terminated(client) if (clients contains client) => unregister(client)
-
- case Connection.Write(data) =>
- IO(Serial) ! Open(port, settings)
+ case Connection.Send(_) =>
+ IO(Serial) ! Serial.Open(port, settings)
context become opening
}
- def opening: Receive = {
- case Connection.Register(client) => register(client)
- case Terminated(client) if (clients contains client) => unregister(client)
-
- case Connection.Write(data) =>
+ def _opening: Receive = {
case Serial.CommandFailed(cmd: Serial.Open, reason) =>
- Log(reason)
- //for (c <- clients) client ! Error //TODO send proper error code
+ sendAll(Connection.Closed(reason.toString))
context become closed
- case Serial.Opened(settings) =>
- val operator = sender
- context watch operator
- context become open(operator)
+ case Serial.Opened(_) =>
+ context watch (sender)
+ context become opened(sender)
+
+ case Connection.Send(_) => () // ignore
+ /*
+ * During opening, any outgoing messages are discarded.
+ * By using some kind of message stashing, maybe messages could be treated
+ * once the port has been opened. However, in such a case failure also needs
+ * to be considered complicating the protocol. Since opening is typically
+ * quite fast and since mavlink uses heartbeats and acknowledgements (in certain
+ * circumstances) anyway, keeping messages is not really required.
+ */
}
- def open(operator: ActorRef): Receive = {
- case Terminated(`operator`) =>
- //for (client <- clients) ! Error //TODO send error code
+ def _opened(operator: ActorRef): Receive = {
+
+ case Terminated(`operator`) =>
+ sendAll(Connection.Closed("Serial connection crashed."))
+ context become closed
+
+ case Serial.Closed =>
+ sendAll(Connection.Closed("Serial connection was closed."))
context become closed
- case Connection.Write(data) => operator ! ByteString(data)
+ case Serial.Received(bstr) =>
+ sendAll(Connection.Received(bstr))
+
+ case Connection.Send(bstr) =>
+ operator ! Serial.Write(bstr)
+
}
-*/
+
+ def receive = closed
+ def closed = _closed orElse registration
+ def opening = _opening orElse registration
+ def opened(op: ActorRef) = _opened(op) orElse registration
+
}
object SerialConnection {
- def apply(id: Byte, hearbeat: Int, port: String, baud: Int, tsb: Boolean, parity: Int) = {
+ def apply(id: Byte, heartbeat: Int, port: String, baud: Int, tsb: Boolean, parity: Int) = {
val settings = SerialSettings(
baud,
8,
@@ -76,8 +98,9 @@ object SerialConnection {
case 0 => Parity.None
case 1 => Parity.Odd
case 2 => Parity.Even
- }
- )
- Props(classOf[SerialConnection], id, hearbeat, port, settings)
+ })
+ val hb = if (heartbeat == 0) None else Some(FiniteDuration(heartbeat, MILLISECONDS))
+
+ Props(classOf[SerialConnection], id, hb, port, settings)
}
} \ No newline at end of file