diff options
author | Jakob Odersky <jodersky@gmail.com> | 2014-11-09 13:19:34 +0100 |
---|---|---|
committer | Jakob Odersky <jodersky@gmail.com> | 2014-11-09 13:25:25 +0100 |
commit | f37bdb01ecc88618c267deaa80ecacb520001d1a (patch) | |
tree | 02e283fe71461587be8e98f57a8069decd3d1142 | |
parent | 7ebffc568407e950955b9975f06a0164295cbc5a (diff) | |
download | mavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.tar.gz mavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.tar.bz2 mavigator-f37bdb01ecc88618c267deaa80ecacb520001d1a.zip |
implement serial backend functionality
-rw-r--r-- | vfd-backend/app/plugins/UavPlugin.scala | 21 | ||||
-rw-r--r-- | vfd-backend/app/views/uav.scala.html | 7 | ||||
-rw-r--r-- | vfd-backend/conf/application.conf | 2 | ||||
-rw-r--r-- | vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala | 3 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/Connection.scala | 26 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/MockConnection.scala | 40 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala | 101 |
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 |