aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2014-11-07 19:19:44 +0100
committerJakob Odersky <jodersky@gmail.com>2014-11-07 19:19:44 +0100
commit1c733a1c18ab154e477c42818c48dd2c74866b13 (patch)
tree8dbb6687ed2a4f0c9206de72d8fb9d4919245af2
parent2915f8bf09b4d4db6f5e1b574ba988a0fc0fb90a (diff)
downloadmavigator-1c733a1c18ab154e477c42818c48dd2c74866b13.tar.gz
mavigator-1c733a1c18ab154e477c42818c48dd2c74866b13.tar.bz2
mavigator-1c733a1c18ab154e477c42818c48dd2c74866b13.zip
cleanup
-rw-r--r--vfd-backend/app/controllers/Application.scala9
-rw-r--r--vfd-backend/app/plugins/UavPlugin.scala10
-rw-r--r--vfd-backend/app/views/index.scala.html16
-rw-r--r--vfd-backend/app/views/main.scala.html22
-rw-r--r--vfd-backend/app/views/panels/eicas.scala.html35
-rw-r--r--vfd-backend/app/views/panels/pfd.scala.html31
-rw-r--r--vfd-backend/app/views/uav.scala.html27
-rw-r--r--vfd-backend/conf/application.conf2
-rw-r--r--vfd-backend/conf/routes1
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala30
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/Main.scala79
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala52
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala12
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala155
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala36
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala11
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala14
-rw-r--r--vfd-mavlink/src/main/scala/org/mavlink/Packet.scala1
-rw-r--r--vfd-mavlink/src/main/scala/org/mavlink/Parser.scala20
-rw-r--r--vfd-mavlink/src/main/scala/org/mavlink/messages/Message.scala18
-rw-r--r--vfd-mavlink/src/main/scala/org/mavlink/messages/messages.scala9
-rw-r--r--vfd-mavlink/src/main/scala/org/mavlink/payload.scala21
-rw-r--r--vfd-uav/src/main/scala/org/mavlink/BufferPayloadReader.scala36
-rw-r--r--vfd-uav/src/main/scala/org/mavlink/package.scala7
-rw-r--r--vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala39
-rw-r--r--vfd-uav/src/main/scala/vfd/uav/MockConnection.scala57
26 files changed, 342 insertions, 408 deletions
diff --git a/vfd-backend/app/controllers/Application.scala b/vfd-backend/app/controllers/Application.scala
index 1c1fe13..6739d3c 100644
--- a/vfd-backend/app/controllers/Application.scala
+++ b/vfd-backend/app/controllers/Application.scala
@@ -14,9 +14,16 @@ object Application extends Controller {
def use[A <: Plugin](implicit app: Application, m: Manifest[A]) = {
app.plugin[A].getOrElse(throw new RuntimeException(m.runtimeClass.toString + " plugin should be available at this point"))
}
+
+ def uav = use[UavPlugin]
+
def index = Action { implicit request =>
- Ok(views.html.index(routes.Application.mavlink.webSocketURL()))
+ Redirect(routes.Application.uav(0))
+ }
+
+ def uav(sysId: Int) = Action { implicit request =>
+ Ok(views.html.uav(routes.Application.mavlink.webSocketURL(), sysId))
}
def mavlink = WebSocket.acceptWithActor[Array[Byte], Array[Byte]] { implicit request =>
diff --git a/vfd-backend/app/plugins/UavPlugin.scala b/vfd-backend/app/plugins/UavPlugin.scala
index 19be049..998f445 100644
--- a/vfd-backend/app/plugins/UavPlugin.scala
+++ b/vfd-backend/app/plugins/UavPlugin.scala
@@ -8,8 +8,8 @@ import play.api.Application
import play.api.Plugin
import play.api.libs.concurrent.Akka
import vfd.uav.Connection
-import vfd.uav.DummyConnection
import vfd.uav.SerialConnection
+import vfd.uav.MockConnection
class UavPlugin(app: Application) extends Plugin {
@@ -25,7 +25,7 @@ class UavPlugin(app: Application) extends Plugin {
val props = tpe match {
case "mock" =>
- DummyConnection.apply
+ MockConnection.apply
case "serial" =>
val serial = config.flatMap(_.getConfig("serial"))
@@ -55,11 +55,7 @@ class Repeater(out: ActorRef, connection: ActorRef) extends Actor {
}
def receive = {
- case msg =>
- if (sender == connection)
- out ! msg
- else
- connection ! msg
+ case Connection.Received(bytes) => out ! bytes
}
} \ No newline at end of file
diff --git a/vfd-backend/app/views/index.scala.html b/vfd-backend/app/views/index.scala.html
deleted file mode 100644
index eaae450..0000000
--- a/vfd-backend/app/views/index.scala.html
+++ /dev/null
@@ -1,16 +0,0 @@
-@(socket: String)
-
-@main("Main"){
-
- <div id="app">
- Loading...
- </div>
-
- <script type="text/javascript">
- window.onload = function () {
- var launcher = new Launcher
- launcher.launch('app', '@routes.Assets.at("")', '@socket')
- }
- </script>
-}
-
diff --git a/vfd-backend/app/views/main.scala.html b/vfd-backend/app/views/main.scala.html
index beb7dbd..8537db6 100644
--- a/vfd-backend/app/views/main.scala.html
+++ b/vfd-backend/app/views/main.scala.html
@@ -1,4 +1,4 @@
-@(title: String)(content: Html)
+@(title: String, info: String)(content: Html)
<!DOCTYPE html>
@@ -10,7 +10,8 @@
<title>VFD - @title</title>
<link rel="shortcut icon" href="@routes.Assets.at("images/logo.svg")">
- <link rel="stylesheet" media="screen" href="@routes.Assets.at("lib/bootstrap/css/bootstrap.css")">
+ <link rel="stylesheet" media="screen" href="@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")">
+ <link rel="stylesheet" media="screen" href="@routes.Assets.at("lib/font-awesome/css/font-awesome.min.css")">
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
</head>
<body>
@@ -33,20 +34,9 @@
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navbar-main-collapse">
- <ul class="nav navbar-nav">
- </ul>
- <ul class="nav navbar-nav navbar-right">
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">options<b class="caret"></b></a>
- <ul class="dropdown-menu">
- <li>
- <form method="POST" action="">
- <button type="submit" style="width: 100%;" class="btn btn-default">Sign Out</button>
- </form>
- </li>
- </ul>
- </li>
- </ul>
+ <div class="nav navbar-nav navbar-right">
+ <p class="navbar-text">@info</p>
+ </div>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
diff --git a/vfd-backend/app/views/panels/eicas.scala.html b/vfd-backend/app/views/panels/eicas.scala.html
deleted file mode 100644
index 211adba..0000000
--- a/vfd-backend/app/views/panels/eicas.scala.html
+++ /dev/null
@@ -1,35 +0,0 @@
-@()
-
-@led(condition: String) = @{
- "images/leds/" + (condition match {
- case "error" => "red-on.svg"
- case "warn" => "yellow-on.svg"
- case "good" => "green-on.svg"
- case "unknown" => "none.svg"
- case _ => "a"
- })
-}
-
-@instrument(name: String, condition: String, status: String) = {
- <tr>
- <td>@name</td>
- <td>
- <img src="@routes.Assets.at(led(condition))" alt="@condition" width="15px">
- </td>
- <td>
- <span class="status @condition">
- @status
- </span>
- </td>
- </tr>
-}
-
-
-<!-- <table class="control-table">
- @instrument("engine1", "unknown", "0 rpm")
- @instrument("engine2", "unknown", "1000 rpm")
- @instrument("engine3", "unknown", "2080 rpm")
- @instrument("engine4", "unknown", "1000 rpm")
- @instrument("battery", "unknown", "3000Wh")
- @instrument("power", "unknown", "2000W")
-</table --> \ No newline at end of file
diff --git a/vfd-backend/app/views/panels/pfd.scala.html b/vfd-backend/app/views/panels/pfd.scala.html
deleted file mode 100644
index 9c89d6a..0000000
--- a/vfd-backend/app/views/panels/pfd.scala.html
+++ /dev/null
@@ -1,31 +0,0 @@
-@()
-
-@instrument(name: String) = {
- @defining("images/instruments/" + name + ".svg") { location =>
- <object id="@name" type="image/svg+xml" data="@routes.Assets.at(location)" width="100%">Error loading image.</object>
- }
-}
-
-
-<div class="row">
- <div class="col-lg-4">
-
- </div>
- <div class="col-lg-4">
- @instrument("attitude")
- </div>
- <div class="col-lg-4">
- @instrument("altitude")
- </div>
-</div>
-<div class="row">
- <div class="col-lg-4">
-
- </div>
- <div class="col-lg-4">
- @instrument("heading")
- </div>
- <div class="col-lg-4">
- @instrument("distance")
- </div>
-</div> \ No newline at end of file
diff --git a/vfd-backend/app/views/uav.scala.html b/vfd-backend/app/views/uav.scala.html
new file mode 100644
index 0000000..6ce3007
--- /dev/null
+++ b/vfd-backend/app/views/uav.scala.html
@@ -0,0 +1,27 @@
+@(socket: String, remoteSystemId: Int)
+
+@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>"
+ </div>
+
+ <div id="app" data-socketUrl="@socket" data-remoteSystemId="@remoteSystemId.toString">
+ Loading...
+ </div>
+
+ <script type="text/javascript">
+ window.onload = function () {
+ try {
+ var launcher = new Launcher('app', '@routes.Assets.at("")');
+ launcher.main();
+ } catch(err) {
+ document.getElementById("scalajsError").style.display = "block";
+ document.getElementById("scalajsErrorMessage").innerHTML = err;
+ console.log(err)
+ }
+ }
+ </script>
+}
+
diff --git a/vfd-backend/conf/application.conf b/vfd-backend/conf/application.conf
index 1559b89..27586a2 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=serial
+uav.connection.type=mock
# Mavlink component id used by this connection
# (not the web frontend), in case it needs to inject messages
diff --git a/vfd-backend/conf/routes b/vfd-backend/conf/routes
index 45b6ab9..eea8d8e 100644
--- a/vfd-backend/conf/routes
+++ b/vfd-backend/conf/routes
@@ -4,6 +4,7 @@
# Home page
GET / controllers.Application.index
+GET /uav/:sysId controllers.Application.uav(sysId: Int)
GET /mavlink controllers.Application.mavlink
# Map static resources from the /public folder to the /assets URL path
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala b/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
index 2818bfb..e1715d7 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
@@ -4,21 +4,33 @@ import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom
-import vfd.frontend.util.Application
+import vfd.frontend.util.Environment
@JSExport
-class Launcher {
+class Launcher(rootId: String, assetsBase: String) {
- @JSExport
- def launch(rootId: String, assetsBase: String, mavlinkSocketUrl: String) = {
+ lazy val env = new Environment {
val root = dom.document.getElementById(rootId)
- val app = new Application(root, assetsBase)
- val frontend = new Main(mavlinkSocketUrl)(app)
+ def asset(file: String) = assetsBase + "/" + file
+ }
+
+ @JSExport
+ def main() = {
+ import env._
+
+ val args: Seq[(String, String)] = for (
+ i <- 0 until root.attributes.length;
+ attr = root.attributes.item(i);
+ if attr.name.startsWith("data-")
+ ) yield {
+ attr.name.drop(5) -> attr.value
+ }
- while (root.hasChildNodes) {
- root.removeChild(root.firstChild)
+ while (env.root.hasChildNodes) {
+ env.root.removeChild(env.root.firstChild)
}
- frontend.main()
+
+ Main.main(args.toMap)(env)
}
} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
index 24666de..e94b94e 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
@@ -3,94 +3,49 @@ package vfd.frontend
import scala.scalajs.js
import scala.scalajs.js.Any.fromFunction1
import scala.scalajs.js.Any.fromString
-
import org.mavlink.Packet
import org.mavlink.Parser
import org.scalajs.dom
-
import rx.Rx
import rx.Var
import rx.Var
-import scalatags.JsDom.all.`class`
-import scalatags.JsDom.all.div
-import scalatags.JsDom.all.stringAttr
-import vfd.frontend.ui.Panels
-import vfd.frontend.util.Application
-
-class Main(socketUrl: String)(implicit app: Application) {
-
- def main() = {
-
- //websocket conveying mavlink data
- val connection = new dom.WebSocket(socketUrl)
-
- //reactive propagation of mavlink events
- val packet: Var[Packet] = Var(Packet.Empty)
- val crcErrors: Var[Int] = Var(0)
+import scalatags.JsDom.all._
- val parser = new Parser(
- p => {
- Rx { packet() = p }
- dom.console.log("got packet: seq " + (p.seq.toInt & 0xff) + ", mid " + (p.messageId.toInt & 0xff))
- },
- () => {
- //Rx{crcErrors() += 1}
- dom.console.log("crc error")
- })
+import vfd.frontend.ui.panels
+import vfd.frontend.util.Environment
- connection.binaryType = "arraybuffer";
- connection.onmessage = (e: dom.MessageEvent) => {
+object Main {
- val buffer = e.data.asInstanceOf[js.typedarray.ArrayBuffer]
- val dv = new js.typedarray.DataView(buffer)
+ def main(args: Map[String, String])(implicit env: Environment) = {
+ val socketUrl = args("socketurl")
+ val remoteSystemId = args("remotesystemid").toInt
- for (i <- 0 until dv.byteLength) {
- parser.push(dv.getInt8(i))
-
- }
-
- }
+ val socket = new MavlinkSocket(socketUrl, remoteSystemId)
val element = div(`class` := "container-fluid")(
div(`class` := "row")(
div(`class` := "col-xs-12")(
div(`class` := "panel panel-default")(
div(`class` := "panel-body")(
- Panels.autopilot)))),
+ "autopilot mode")))),
div(`class` := "row")(
div(`class` := "col-xs-4")(
div(`class` := "panel panel-default")(
div(`class` := "panel-body")(
- Panels.secondary()))),
+ "navigation"))),
div(`class` := "col-xs-5")(
div(`class` := "panel panel-default")(
div(`class` := "panel-body")(
- //Panels.primary(input)
- Panels.mavlink(packet, crcErrors)))),
+ "primary"))),
div(`class` := "col-xs-3")(
div(`class` := "panel panel-default")(
div(`class` := "panel-body")(
- Panels.eicas())))))
+ panels.Communication(
+ socket.stats.packets,
+ socket.stats.crcErrors,
+ socket.stats.overflows,
+ socket.stats.wrongIds))))))
- app.root.appendChild(element.render)
+ env.root.appendChild(element.render)
}
-
- /*
- def alert() = {
- val image = "/assets/images/leds/led.svg"
- val off = "#782121"
- val on = "#ff0000"
- val controls = div(
- `object`("data".attr := image, `type` := "image/svg+xml", width:= "32px")(
- "Cannot load"
- ),
- audio(
- "autoplay".attr:="true",
- source(src:="/assets/audio/alarm.ogg", `type`:="audio/ogg")
- )
-
- ).render
- controls
- }*/
-
} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
new file mode 100644
index 0000000..81e6667
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
@@ -0,0 +1,52 @@
+package vfd.frontend
+
+import scala.scalajs.js
+import scala.scalajs.js.Any.fromFunction1
+
+import org.mavlink.Packet
+import org.mavlink.Parser
+import org.scalajs.dom
+
+import rx.core.Rx
+import rx.core.Var
+
+class MavlinkSocket(url: String, remoteSystemId: Int) {
+
+ val packet: Var[Packet] = Var(Packet.Empty)
+
+ object stats {
+ val crcErrors = Var(0)
+ val overflows = Var(0)
+ val wrongIds = Var(0)
+ val packets = Var(0)
+ }
+
+ private val parser = new Parser(
+ pckt => {
+ pckt match {
+ case Packet(seq, `remoteSystemId`, compId, msgId, payload) =>
+ packet() = pckt
+ stats.packets() += 1
+ case _ => stats.wrongIds() += 1
+ }
+ },
+ err => {
+ err match {
+ case Parser.ParseErrors.CrcError => stats.crcErrors() += 1
+ case Parser.ParseErrors.OverflowError => stats.overflows() += 1
+ }
+ })
+
+ private val connection = new dom.WebSocket(url)
+
+ connection.binaryType = "arraybuffer";
+ connection.onmessage = (e: dom.MessageEvent) => {
+ val buffer = e.data.asInstanceOf[js.typedarray.ArrayBuffer]
+ val dv = new js.typedarray.DataView(buffer)
+
+ for (i <- 0 until dv.byteLength) {
+ parser.push(dv.getInt8(i))
+ }
+ }
+
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
index ce8a5e9..a340c15 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
@@ -15,11 +15,11 @@ import scalatags.JsDom.all.stringStyle
import scalatags.JsDom.all.style
import scalatags.JsDom.all.`type`
import scalatags.JsDom.all.width
-import vfd.frontend.util.Application
+import vfd.frontend.util.Environment
object Components {
- def led(color: Rx[String], size: String)(implicit app: Application) = {
+ def led(color: Rx[String], size: String)(implicit app: Environment) = {
val elem = `object`(`type` := "image/svg+xml", "data".attr := app.asset("leds/led.svg"), width := size)(
"Error loading image.").render
@@ -30,7 +30,7 @@ object Components {
elem
}
- private def instrument(name: String)(implicit app: Application) = {
+ private def instrument(name: String)(implicit app: Environment) = {
val path = app.asset("images/instruments/" + name + ".svg")
`object`(`type` := "image/svg+xml", "data".attr := path, width := "100%")(
"Error loading image " + name).render
@@ -41,7 +41,7 @@ object Components {
elem)
}
- def attitude(pitchRoll: Rx[(Double, Double)], size: String)(implicit app: Application) = {
+ def attitude(pitchRoll: Rx[(Double, Double)], size: String)(implicit app: Environment) = {
val inst = instrument("attitude")
Obs(pitchRoll, skipInitial = true) {
val svg = inst.contentDocument
@@ -53,7 +53,7 @@ object Components {
frame(inst, size)
}
- def altitude(value: Rx[Double], size: String)(implicit app: Application) = {
+ def altitude(value: Rx[Double], size: String)(implicit app: Environment) = {
val inst = instrument("altitude")
Obs(value, skipInitial = true) {
val svg = inst.contentDocument
@@ -63,7 +63,7 @@ object Components {
frame(inst, size)
}
- def heading(value: Rx[Double], size: String)(implicit app: Application) = {
+ def heading(value: Rx[Double], size: String)(implicit app: Environment) = {
val inst = instrument("heading")
Obs(value, skipInitial = true) {
val svg = inst.contentDocument
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala
deleted file mode 100644
index 9141fc6..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala
+++ /dev/null
@@ -1,155 +0,0 @@
-package vfd.frontend.ui
-
-import org.mavlink.Packet
-import org.mavlink.messages.Heartbeat
-import org.mavlink.messages.Message
-
-import rx.Rx
-import rx.Rx
-import rx.ops.RxOps
-import scalatags.JsDom.all.ExtendedString
-import scalatags.JsDom.all.button
-import scalatags.JsDom.all.byteFrag
-import scalatags.JsDom.all.`class`
-import scalatags.JsDom.all.div
-import scalatags.JsDom.all.height
-import scalatags.JsDom.all.iframe
-import scalatags.JsDom.all.img
-import scalatags.JsDom.all.intFrag
-import scalatags.JsDom.all.p
-import scalatags.JsDom.all.span
-import scalatags.JsDom.all.src
-import scalatags.JsDom.all.stringAttr
-import scalatags.JsDom.all.stringFrag
-import scalatags.JsDom.all.stringStyle
-import scalatags.JsDom.all.table
-import scalatags.JsDom.all.tbody
-import scalatags.JsDom.all.td
-import scalatags.JsDom.all.th
-import scalatags.JsDom.all.thead
-import scalatags.JsDom.all.tr
-import scalatags.JsDom.all.`type`
-import scalatags.JsDom.all.width
-import vfd.frontend.util.Application
-import vfd.frontend.util.Framework.RxStr
-import vfd.frontend.util.Framework.rxMod
-import vfd.frontend.util.richRx
-
-object Panels {
-
- def primary(input: Rx[Message])(implicit app: Application) = {
- val foos = input.only[Heartbeat]
- div(
- Components.heading(foos.map(_ => 0), "33%"),
- Components.attitude(input map (i => (21, 40)), "33%"),
- Components.altitude(input map (_ => 0), "33%"))
- }
-
- def mavlink(packet: Rx[Packet], crc: Rx[Int])(implicit app: Application) = {
- div(
- p(
- "CRC errors: ",
- span(
- Rx { crc() })),
- table(`class` := "table")(
- thead(
- tr(
- th("System"),
- th("Component"),
- th("Message"),
- th("Payload Length"))),
- tbody(
- Rx {
- tr(
- td(packet().systemId),
- td(packet().componentId),
- td(packet().messageId),
- td(packet().payload.length))
- })))
- }
-
- def secondary()(implicit app: Application) = div(
- iframe(
- width := "100%",
- height := "350px",
- "frameborder".attr := "0",
- "scrolling".attr := "no",
- "marginheight".attr := "0",
- "marginwidth".attr := "0",
- src := "http://www.openstreetmap.org/export/embed.html?bbox=6.5611016750335684%2C46.51718501017836%2C6.570038795471191%2C46.520577350893525&amp;layer=mapnik"),
- table(`class` := "table")(
- tr(
- td("UAV Position"),
- td("N13.1234 E1234.23465")),
- tr(
- td("Base Position"),
- td("N13.1234 E1234.23465")),
- tr(
- td("Distance to UAV"),
- td("200 m")),
- tr(
- td("Total flight distance"),
- td("12.3 km")),
- tr(
- td("Groundspeed"),
- td("23 km/h")),
- tr(
- td("---"),
- td("")),
- tr(
- td("Below"),
- td("180 cm"))))
-
- def eicas()(implicit app: Application) = {
- table(`class` := "table")(
- tr(
- td("Link Server"),
- td("3"),
- td("ms"),
- td(img(src := "/assets/images/leds/red-off.svg", width := "16px"))),
- tr(
- td("Link UAV"),
- td("-80"),
- td("dB(m)"),
- td(img(src := "/assets/images/leds/red-on.svg", width := "16px"))),
- tr(
- td("---"),
- td(""),
- td(""),
- td("")),
- tr(
- td("Full Charge"),
- td("5.000"),
- td("Ah"),
- td()),
- tr(
- td("Charge"),
- td("4.800"),
- td("Ah"),
- td(img(src := "/assets/images/leds/red-off.svg", width := "16px"))),
- tr(
- td("Current"),
- td("80"),
- td("A"),
- td(img(src := "/assets/images/leds/yellow-on.svg", width := "16px"))),
- tr(
- td("Endurance"),
- td("14"),
- td("min"),
- td(img(src := "/assets/images/leds/none.svg", width := "16px"))),
- tr(
- td("GPS"),
- td("5"),
- td("satellites"),
- td(img(src := "/assets/images/leds/none.svg", width := "16px"))))
- }
-
- def autopilot = div(`class` := "btn-toolbar")(
- div(`class` := "btn-group")(
- button(`type` := "button", `class` := "btn btn-default")("Auto"),
- button(`type` := "button", `class` := "btn btn-default")("Position"),
- button(`type` := "button", `class` := "btn btn-default")("Attitude")),
- div(`class` := "btn-group")(
- button(`type` := "button", `class` := "btn btn-default")(
- span(`class` := "label label-default")("Manual"))))
-} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala
new file mode 100644
index 0000000..a26348e
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala
@@ -0,0 +1,36 @@
+package vfd.frontend.ui.panels
+
+import rx.Rx
+import rx.Rx
+import scalatags.JsDom.all.`class`
+import scalatags.JsDom.all.div
+import scalatags.JsDom.all.intFrag
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.stringFrag
+import scalatags.JsDom.all.table
+import scalatags.JsDom.all.tbody
+import scalatags.JsDom.all.td
+import scalatags.JsDom.all.tr
+import vfd.frontend.util.Environment
+import vfd.frontend.util.Framework.RxStr
+import rx.core.Var
+import rx.core.Obs
+
+object Communication {
+
+ def apply(packets: Rx[Int], crcs: Rx[Int], overflows: Rx[Int], wrongIds: Rx[Int])(implicit app: Environment) = {
+ div(
+ "Packets",
+ table(`class` := "table")(
+ tr(
+ td("OK"),
+ td(packets),
+ td("CRC"),
+ td(crcs),
+ td("OFLW"),
+ td(packets),
+ td("BID"),
+ td(packets))))
+ }
+
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala
deleted file mode 100644
index 3b4e733..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package vfd.frontend.util
-
-import org.scalajs.dom.HTMLElement
-
-class Application(element: HTMLElement, assetsBase: String) {
-
- def root = element
-
- def asset(file: String): String = assetsBase + "/" + file
-
-} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala
new file mode 100644
index 0000000..fdd908a
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala
@@ -0,0 +1,14 @@
+package vfd.frontend.util
+
+import org.scalajs.dom.HTMLElement
+
+/** Represents an application's environment */
+trait Environment {
+
+ /** The application's root element. */
+ def root: HTMLElement
+
+ /** Retrieve an asset's URL based on its file location. */
+ def asset(file: String): String
+
+} \ No newline at end of file
diff --git a/vfd-mavlink/src/main/scala/org/mavlink/Packet.scala b/vfd-mavlink/src/main/scala/org/mavlink/Packet.scala
index 839e5d4..b5b932d 100644
--- a/vfd-mavlink/src/main/scala/org/mavlink/Packet.scala
+++ b/vfd-mavlink/src/main/scala/org/mavlink/Packet.scala
@@ -39,6 +39,7 @@ case class Packet(
object Packet {
final val Stx: Byte = (0xfe).toByte;
+ final val MaxPayloadLength: Int = 255
final val MessageIdCrcEnds: Seq[Byte] = Array(
50, 124, 137, 0, 237, 217, 104, 119, 0, 0,
diff --git a/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala b/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala
index 74562de..9cc5090 100644
--- a/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala
+++ b/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala
@@ -16,9 +16,15 @@ object Parser {
case object GotCrc1 extends State
case object GotPayload extends State
}
+
+ object ParseErrors {
+ sealed trait ParseError
+ case object CrcError extends ParseError
+ case object OverflowError extends ParseError
+ }
}
-class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
+class Parser(receiver: Packet => Unit, error: Parser.ParseErrors.ParseError => Unit) {
import Parser._
private var state: ParseStates.State = ParseStates.Idle
@@ -43,6 +49,7 @@ class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
}
case GotStx =>
+ inbound.crc = new Crc()
inbound.length = (c & 0xff)
inbound.crc = inbound.crc.next(c)
state = GotLength
@@ -75,6 +82,10 @@ class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
case GotMsgId =>
inbound.payload += c
inbound.crc = inbound.crc.next(c)
+ if(inbound.payload.length >= Packet.MaxPayloadLength) {
+ state = Idle
+ error(ParseErrors.OverflowError)
+ }
if (inbound.payload.length >= inbound.length) {
state = GotPayload
}
@@ -86,8 +97,7 @@ class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
if (c == Packet.Stx) {
state = GotStx
}
- inbound.crc = new Crc()
- crcErrorReceiver()
+ error(ParseErrors.CrcError)
} else {
state = GotCrc1
}
@@ -98,8 +108,7 @@ class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
if (c == Packet.Stx) {
state = GotStx
}
- inbound.crc = new Crc()
- crcErrorReceiver()
+ error(ParseErrors.CrcError)
} else {
val packet = Packet(
inbound.seq,
@@ -108,7 +117,6 @@ class Parser(receiver: Packet => Unit, crcErrorReceiver: () => Unit) {
inbound.messageId,
inbound.payload)
state = Idle
- inbound.crc = new Crc()
inbound.payload = new ArrayBuffer[Byte]()
receiver(packet)
}
diff --git a/vfd-mavlink/src/main/scala/org/mavlink/messages/Message.scala b/vfd-mavlink/src/main/scala/org/mavlink/messages/Message.scala
index 91e446c..f22d0b3 100644
--- a/vfd-mavlink/src/main/scala/org/mavlink/messages/Message.scala
+++ b/vfd-mavlink/src/main/scala/org/mavlink/messages/Message.scala
@@ -1,16 +1,8 @@
package org.mavlink.messages
-trait Message {}
+import org.mavlink.PayloadReader
+import org.mavlink.Packet
+import org.mavlink.PayloadBuilder
-case class Heartbeat(
- `type`: Int,
- autopilot: Int,
- baseMode: Int,
- customMode: Int,
- systemStatus: Int,
- mavlinkVersion: Int
-) extends Message
-
-object Message {
-
-} \ No newline at end of file
+trait Message
+ \ No newline at end of file
diff --git a/vfd-mavlink/src/main/scala/org/mavlink/messages/messages.scala b/vfd-mavlink/src/main/scala/org/mavlink/messages/messages.scala
new file mode 100644
index 0000000..422949f
--- /dev/null
+++ b/vfd-mavlink/src/main/scala/org/mavlink/messages/messages.scala
@@ -0,0 +1,9 @@
+package org.mavlink.messages
+
+case class Heartbeat(
+ customMode: Int,
+ `type`: Byte,
+ autopilot: Byte,
+ baseMode: Byte,
+ systemStatus: Byte,
+ mavlinkVersion: Byte) extends Message \ No newline at end of file
diff --git a/vfd-mavlink/src/main/scala/org/mavlink/payload.scala b/vfd-mavlink/src/main/scala/org/mavlink/payload.scala
new file mode 100644
index 0000000..63473f0
--- /dev/null
+++ b/vfd-mavlink/src/main/scala/org/mavlink/payload.scala
@@ -0,0 +1,21 @@
+package org.mavlink
+
+trait PayloadReader {
+ def nextInt8: Byte
+ def nextInt16: Short
+ def nextInt32: Int
+ def nextInt64: Long
+ def nextFloat: Float
+ def nextDouble: Double
+ def nextChar: Char
+}
+
+trait PayloadBuilder {
+ def writeInt8(x: Byte)
+ def writeInt16(x: Short)
+ def writeInt32(x: Int)
+ def writeInt64(x: Long)
+ def writeFloat(x: Float)
+ def writeDouble(x: Double)
+ def writeChar(x: Char)
+} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/org/mavlink/BufferPayloadReader.scala b/vfd-uav/src/main/scala/org/mavlink/BufferPayloadReader.scala
new file mode 100644
index 0000000..c552a96
--- /dev/null
+++ b/vfd-uav/src/main/scala/org/mavlink/BufferPayloadReader.scala
@@ -0,0 +1,36 @@
+package org.mavlink
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+class BufferedPayloadReader(payload: Array[Byte]) extends PayloadReader {
+ private val buffer = ByteBuffer.wrap(payload)
+
+ //mavlink uses little endian
+ buffer.order(ByteOrder.LITTLE_ENDIAN)
+
+ def nextInt8 = buffer.get()
+ def nextInt16 = buffer.getShort()
+ def nextInt32 = buffer.getInt()
+ def nextInt64 = buffer.getLong()
+ def nextFloat = buffer.getFloat()
+ def nextDouble = buffer.getDouble()
+ def nextChar = buffer.getChar()
+
+}
+
+class BufferedPayloadBuilder(payload: Array[Byte]) extends PayloadBuilder {
+ private val buffer = ByteBuffer.wrap(payload)
+
+ //mavlink uses little endian
+ buffer.order(ByteOrder.LITTLE_ENDIAN)
+
+ def writeInt8(x: Byte) = buffer.put(x)
+ def writeInt16(x: Short) = buffer.putShort(x)
+ def writeInt32(x: Int) = buffer.putInt(x)
+ def writeInt64(x: Long) = buffer.putLong(x)
+ def writeFloat(x: Float) = buffer.putFloat(x)
+ def writeDouble(x: Double) = buffer.putDouble(x)
+ def writeChar(x: Char) = buffer.putChar(x)
+
+} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/org/mavlink/package.scala b/vfd-uav/src/main/scala/org/mavlink/package.scala
new file mode 100644
index 0000000..080648d
--- /dev/null
+++ b/vfd-uav/src/main/scala/org/mavlink/package.scala
@@ -0,0 +1,7 @@
+package org
+
+package object mavlink {
+
+
+
+} \ No newline at end of file
diff --git a/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala b/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala
deleted file mode 100644
index 7cf7bb1..0000000
--- a/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala
+++ /dev/null
@@ -1,39 +0,0 @@
-package vfd.uav
-
-import java.util.concurrent.TimeUnit.MILLISECONDS
-
-import scala.concurrent.duration.FiniteDuration
-
-import akka.actor.Actor
-import akka.actor.Props
-import akka.actor.Terminated
-import akka.actor.actorRef2Scala
-
-class DummyConnection extends Actor with Connection {
- import context._
-
- var time = 0.0
- val messageInterval = FiniteDuration(50, MILLISECONDS)
-
- def flightData(time: Double) = {
- new Array[Byte](10)
- }
-
- override def preStart() = {
- context.system.scheduler.schedule(messageInterval, messageInterval) {
- time += messageInterval.toMillis
- clients foreach (_ ! flightData(time))
- }
- }
-
- def receive = {
- case Connection.Register => register(sender)
- case Terminated(client) => unregister(client)
- }
-
-}
-
-object DummyConnection {
- def apply = Props(classOf[DummyConnection])
-}
-
diff --git a/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala b/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala
new file mode 100644
index 0000000..94a14db
--- /dev/null
+++ b/vfd-uav/src/main/scala/vfd/uav/MockConnection.scala
@@ -0,0 +1,57 @@
+package vfd.uav
+
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import scala.concurrent.duration.FiniteDuration
+import akka.actor.Actor
+import akka.actor.Props
+import akka.actor.Terminated
+import akka.actor.actorRef2Scala
+import akka.actor.ActorLogging
+import scala.util.Random
+
+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))
+ }
+ }
+
+ def receive = {
+ case Connection.Register => register(sender)
+ case Terminated(client) => unregister(client)
+ }
+
+}
+
+object MockConnection {
+ def apply = Props(classOf[MockConnection])
+}
+
+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 invalidOverflow = {
+ val data = Array.fill[Byte](1006)(42)
+ data(0) = -2
+ data(1) = 2
+ data(1) = -1
+ data
+ }
+
+} \ No newline at end of file