From 1c733a1c18ab154e477c42818c48dd2c74866b13 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Fri, 7 Nov 2014 19:19:44 +0100 Subject: cleanup --- vfd-backend/app/controllers/Application.scala | 9 +- vfd-backend/app/plugins/UavPlugin.scala | 10 +- vfd-backend/app/views/index.scala.html | 16 --- vfd-backend/app/views/main.scala.html | 22 +-- vfd-backend/app/views/panels/eicas.scala.html | 35 ----- vfd-backend/app/views/panels/pfd.scala.html | 31 ----- vfd-backend/app/views/uav.scala.html | 27 ++++ vfd-backend/conf/application.conf | 2 +- vfd-backend/conf/routes | 1 + .../src/main/scala/vfd/frontend/Launcher.scala | 30 ++-- .../src/main/scala/vfd/frontend/Main.scala | 79 +++-------- .../main/scala/vfd/frontend/MavlinkSocket.scala | 52 +++++++ .../main/scala/vfd/frontend/ui/Components.scala | 12 +- .../src/main/scala/vfd/frontend/ui/Panels.scala | 155 --------------------- .../vfd/frontend/ui/panels/Communication.scala | 36 +++++ .../main/scala/vfd/frontend/util/Application.scala | 11 -- .../main/scala/vfd/frontend/util/Environment.scala | 14 ++ .../src/main/scala/org/mavlink/Packet.scala | 1 + .../src/main/scala/org/mavlink/Parser.scala | 20 ++- .../main/scala/org/mavlink/messages/Message.scala | 18 +-- .../main/scala/org/mavlink/messages/messages.scala | 9 ++ .../src/main/scala/org/mavlink/payload.scala | 21 +++ .../scala/org/mavlink/BufferPayloadReader.scala | 36 +++++ vfd-uav/src/main/scala/org/mavlink/package.scala | 7 + .../src/main/scala/vfd/uav/DummyConnection.scala | 39 ------ .../src/main/scala/vfd/uav/MockConnection.scala | 57 ++++++++ 26 files changed, 342 insertions(+), 408 deletions(-) delete mode 100644 vfd-backend/app/views/index.scala.html delete mode 100644 vfd-backend/app/views/panels/eicas.scala.html delete mode 100644 vfd-backend/app/views/panels/pfd.scala.html create mode 100644 vfd-backend/app/views/uav.scala.html create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala create mode 100644 vfd-mavlink/src/main/scala/org/mavlink/messages/messages.scala create mode 100644 vfd-mavlink/src/main/scala/org/mavlink/payload.scala create mode 100644 vfd-uav/src/main/scala/org/mavlink/BufferPayloadReader.scala create mode 100644 vfd-uav/src/main/scala/org/mavlink/package.scala delete mode 100644 vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala create mode 100644 vfd-uav/src/main/scala/vfd/uav/MockConnection.scala 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"){ - -
- Loading... -
- - -} - 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) @@ -10,7 +10,8 @@ VFD - @title - + + @@ -33,20 +34,9 @@ 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) = { - - @name - - @condition - - - - @status - - - -} - - - \ 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 => - Error loading image. - } -} - - -
-
- -
-
- @instrument("attitude") -
-
- @instrument("altitude") -
-
-
-
- -
-
- @instrument("heading") -
-
- @instrument("distance") -
-
\ 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){ + + + +
+ Loading... +
+ + +} + 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&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 -- cgit v1.2.3