diff options
-rw-r--r-- | backend/app/controllers/Application.scala | 52 | ||||
-rw-r--r-- | backend/app/uav/UavPlugin.scala | 34 | ||||
-rw-r--r-- | backend/app/uav/connections.scala | 123 | ||||
-rw-r--r-- | backend/conf/play.plugins | 1 | ||||
-rw-r--r-- | project/Build.scala | 61 | ||||
-rw-r--r-- | project/Dependencies.scala | 11 | ||||
-rw-r--r-- | project/util.scala | 32 | ||||
-rw-r--r-- | vfd-backend/.gitignore (renamed from backend/.gitignore) | 0 | ||||
-rw-r--r-- | vfd-backend/app/controllers/Application.scala | 31 | ||||
-rw-r--r-- | vfd-backend/app/plugins/UavPlugin.scala | 44 | ||||
-rw-r--r-- | vfd-backend/app/views/index.scala.html (renamed from backend/app/views/index.scala.html) | 0 | ||||
-rw-r--r-- | vfd-backend/app/views/main.scala.html (renamed from backend/app/views/main.scala.html) | 3 | ||||
-rw-r--r-- | vfd-backend/app/views/panels/eicas.scala.html (renamed from backend/app/views/panels/eicas.scala.html) | 0 | ||||
-rw-r--r-- | vfd-backend/app/views/panels/pfd.scala.html (renamed from backend/app/views/panels/pfd.scala.html) | 0 | ||||
-rw-r--r-- | vfd-backend/conf/application.conf (renamed from backend/conf/application.conf) | 6 | ||||
-rw-r--r-- | vfd-backend/conf/play.plugins | 1 | ||||
-rw-r--r-- | vfd-backend/conf/routes (renamed from backend/conf/routes) | 0 | ||||
-rw-r--r-- | vfd-backend/public/fonts/DIGITAL.TXT (renamed from backend/public/fonts/DIGITAL.TXT) | 0 | ||||
-rw-r--r-- | vfd-backend/public/fonts/DS-DIGIB.TTF (renamed from backend/public/fonts/DS-DIGIB.TTF) | bin | 24896 -> 24896 bytes | |||
-rw-r--r-- | vfd-backend/public/fonts/DS-DIGII.TTF (renamed from backend/public/fonts/DS-DIGII.TTF) | bin | 24676 -> 24676 bytes | |||
-rw-r--r-- | vfd-backend/public/fonts/DS-DIGIT.TTF (renamed from backend/public/fonts/DS-DIGIT.TTF) | bin | 25480 -> 25480 bytes | |||
-rw-r--r-- | vfd-backend/public/fonts/ds-digi.ttf (renamed from backend/public/fonts/ds-digi.ttf) | bin | 24448 -> 24448 bytes | |||
-rw-r--r-- | vfd-backend/public/images/instruments/altitude.svg (renamed from backend/public/images/instruments/altitude.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/instruments/attitude.svg (renamed from backend/public/images/instruments/attitude.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/instruments/distance.svg (renamed from backend/public/images/instruments/distance.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/instruments/generic.svg (renamed from backend/public/images/instruments/generic.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/instruments/heading.svg (renamed from backend/public/images/instruments/heading.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/green-off.svg (renamed from backend/public/images/leds/green-off.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/green-on.svg (renamed from backend/public/images/leds/green-on.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/none.svg (renamed from backend/public/images/leds/none.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/red-off.svg (renamed from backend/public/images/leds/red-off.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/red-on.svg (renamed from backend/public/images/leds/red-on.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/leds/yellow-on.svg (renamed from backend/public/images/leds/yellow-on.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/logo-invert.svg (renamed from backend/public/images/logo.svg) | 0 | ||||
-rw-r--r-- | vfd-backend/public/images/logo.svg | 87 | ||||
-rw-r--r-- | vfd-backend/public/stylesheets/main.css (renamed from backend/public/stylesheets/main.css) | 0 | ||||
-rw-r--r-- | vfd-frontend/.gitignore (renamed from frontend/.gitignore) | 0 | ||||
-rw-r--r-- | vfd-frontend/src/main/scala/Frontend.scala (renamed from frontend/src/main/scala/Frontend.scala) | 3 | ||||
-rw-r--r-- | vfd-shared/src/main/scala/vfd/uav/DataFrame.scala | 18 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/Connection.scala | 27 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala | 43 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/FcuConnection.scala | 53 | ||||
-rw-r--r-- | vfd-uav/src/main/scala/vfd/uav/Framer.scala | 70 |
43 files changed, 451 insertions, 249 deletions
diff --git a/backend/app/controllers/Application.scala b/backend/app/controllers/Application.scala deleted file mode 100644 index 1abaee0..0000000 --- a/backend/app/controllers/Application.scala +++ /dev/null @@ -1,52 +0,0 @@ -package controllers - -import uav._ -import play.api._ -import play.api.mvc._ -import play.api.Play.current -import play.api.mvc.WebSocket.FrameFormatter - -import play.api.libs.functional.syntax._ -import play.api.libs.json._ - - -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")) - } - - - - implicit val dataWrites: Writes[UavConnection.Data] = ( - (__ \ "roll").write[Double] and - (__ \ "pitch").write[Double] and - (__ \ "heading").write[Double] and - (__ \ "altitude").write[Double] and - (__ \ "temperature").write[Double])(unlift(UavConnection.Data.unapply)) - - implicit val dataReads: Reads[UavConnection.Data] = ( - (__ \ "roll").read[Double] and - (__ \ "pitch").read[Double] and - (__ \ "heading").read[Double] and - (__ \ "altitude").read[Double] and - (__ \ "temperature").read[Double])(UavConnection.Data.apply _) - - implicit val dataFormat = Format(dataReads, dataWrites) - - - //implicit val inEventFormat = Json.format[uav.UavConnection.Data] - //implicit val outEventFormat = Json.format[uav.Data] - - - - def index() = Action { - Ok(views.html.index()) - } - - implicit val dataFrameFormatter = FrameFormatter.jsonFrame[UavConnection.Data] - - - def socket = WebSocket.acceptWithActor[String, UavConnection.Data] { request => - out => use[UavPlugin].register(out) - } -}
\ No newline at end of file diff --git a/backend/app/uav/UavPlugin.scala b/backend/app/uav/UavPlugin.scala deleted file mode 100644 index 060e3dc..0000000 --- a/backend/app/uav/UavPlugin.scala +++ /dev/null @@ -1,34 +0,0 @@ -package uav - -import akka.actor._ -import play.api._ -import play.api.libs.concurrent.Akka - -class UavPlugin(app: Application) extends Plugin { - - lazy val uav: ActorRef = Akka.system(app).actorOf(UavConnection.fcu, name = "uav") - - def register(out: ActorRef): Props = Repeater(out, uav) - - -} - -class Repeater(out: ActorRef, uav: ActorRef) extends Actor { - - override def preStart = { - uav ! UavConnection.Register - } - - def receive = { - case msg => sender match { - case `out` => uav ! msg - case `uav` => out ! msg - case _ => throw new RuntimeException("Unknown sender") - } - } - - } - -object Repeater { - def apply(out: ActorRef, uav: ActorRef) = Props(classOf[Repeater], out, uav) -}
\ No newline at end of file diff --git a/backend/app/uav/connections.scala b/backend/app/uav/connections.scala deleted file mode 100644 index a922519..0000000 --- a/backend/app/uav/connections.scala +++ /dev/null @@ -1,123 +0,0 @@ -package uav - -import akka.actor.Actor -import akka.actor.ActorRef -import akka.actor.Props -import akka.actor.Terminated -import scala.concurrent.duration.FiniteDuration -import scala.collection.mutable.ArrayBuffer -import java.util.concurrent.TimeUnit._ -import akka.io.IO -import com.github.jodersky.flow._ -import com.github.jodersky.flow.Serial - -object UavConnection { - def dummy = Props(classOf[DummyConnection]) - def fcu = Props(classOf[FcuConnection]) - - trait Event - trait Command - case object Register extends Command - - case class Data( - roll: Double, - pitch: Double, - heading: Double, - altitude: Double, - temperature: Double - ) extends Event -} - -trait UavConnection {selfs: Actor => - private val _clients = new ArrayBuffer[ActorRef] - def clients = _clients.toSeq - def register(client: ActorRef) = { - _clients += client; - selfs.context.watch(client) - } - def unregister(client: ActorRef) = _clients -= client -} - -class DummyConnection extends Actor with UavConnection { - import UavConnection._ - import context._ - - var time = 0.0 - val messageInterval = FiniteDuration(20, MILLISECONDS) - - def flightData(time: Double) = { - val speed = 5.0 / 1000 - val roll = 5.0/180*math.Pi - val pitch = 10.0/180*math.Pi - Data( - roll, - pitch, - (roll * time * speed) % math.Pi, - (pitch * time * speed), - 22 - ) - } - - - override def preStart() = { - context.system.scheduler.schedule(messageInterval, messageInterval){ - time += messageInterval.toMillis - clients foreach (_ ! flightData(time)) - } - } - - def receive = { - case Register => register(sender) - case Terminated(client) => unregister(client) - case _ => () - } - -} - - -class FcuConnection extends Actor with UavConnection { - import UavConnection._ - import context._ - - val port = "/dev/ttyACM0" - val settings = SerialSettings( - baud = 9600, - characterSize = 8, - twoStopBits = false, - parity = Parity.None - ) - - override def preStart() = { - IO(Serial) ! Serial.Open(port, settings) - } - - def receive = { - case Register => register(sender) - case Terminated(client) => unregister(client) - case Serial.CommandFailed(cmd: Serial.Open, reason: AccessDeniedException) => println("you're not allowed to open that port!") - case Serial.CommandFailed(cmd: Serial.Open, reason) => println("could not open port for some other reason: " + reason) - case Serial.Opened(settings) => { - val operator = sender - - } - case Serial.Received(bstr) => - val str = (new String(bstr.toArray, "UTF-8")).trim - - - val LinePattern = ".*[(](.+)[)].*".r - val Number = "([-]?\\d+[.]\\d+?)".r - val Components = "(.+),(.+),(.+),(.+),(.+)".r - - str match { - case LinePattern(Components(Number(r),Number(p),Number(h),Number(a),Number(t))) => - val data = Data(r.toDouble, p.toDouble, h.toDouble, a.toDouble, t.toDouble) - println(data) - for (client <- clients) { - client ! data - } - case _ => println("unknown message: " + str) - } - } - -} - diff --git a/backend/conf/play.plugins b/backend/conf/play.plugins deleted file mode 100644 index 2211f4a..0000000 --- a/backend/conf/play.plugins +++ /dev/null @@ -1 +0,0 @@ - 10000:uav.UavPlugin
\ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index af78ff5..9c6789b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,66 +1,65 @@ import sbt._ import sbt.Keys._ +import util._ import play._ import play.PlayImport.PlayKeys._ import scala.scalajs.sbtplugin.ScalaJSPlugin import scala.scalajs.sbtplugin.ScalaJSPlugin.ScalaJSKeys._ +import Dependencies._ object ApplicationBuild extends Build { val common = Seq( scalaVersion := "2.11.2", - scalacOptions ++= Seq("-feature") + scalacOptions ++= Seq("-feature"), + unmanagedSourceDirectories in Compile += (baseDirectory in ThisBuild).value / "vfd-shared" / "src" / "main" / "scala", + unmanagedResourceDirectories in Compile += (baseDirectory in ThisBuild).value / "vfd-shared" / "src" / "main" / "resources" ) lazy val root = Project("root", file(".")).aggregate( + uav, backend, frontend ) + lazy val uav = ( + Project("vfd-uav", file("vfd-uav")) + settings(common: _*) + settings( + libraryDependencies ++= Seq( + akkaActor, + flow, + flowNative + ) + ) + ) + lazy val backend = ( - Project("vfd-backend", file("backend")) + Project("vfd-backend", file("vfd-backend")) enablePlugins(PlayScala) settings(common: _*) settings( - libraryDependencies ++= Dependencies.backend + libraryDependencies ++= Seq( + bootstrap, + fontawesome, + jquery + ) ) + dependsOn(uav) dependsOnJs(frontend) ) lazy val frontend = ( - Project("vfd-frontend", file("frontend")) + Project("vfd-frontend", file("vfd-frontend")) settings(ScalaJSPlugin.scalaJSSettings: _*) settings(common: _*) settings( - libraryDependencies ++= Dependencies.frontend + libraryDependencies ++= Seq( + rx, + dom + ) ) ) - - - implicit class ScalaJSPlayProject(val project: Project) { - def dependsOnJs(reference: Project): Project = project.settings( - resourceGenerators in Compile += Def.task{ - val outDir: File = (resourceManaged in Compile).value / "public" / "lib" - - val optimized: Seq[File] = (fastOptJS in (reference, Compile)).value.allCode.map(_.path).map(file(_)) - - val outFiles = optimized.map(file => outDir / file.name) - - for ((opt, out) <- optimized zip outFiles) { - if (!out.exists || out.lastModified < opt.lastModified) { - IO.copyFile(opt, out, true) - val map = opt.getParentFile / (out.name + ".map") - IO.copyFile(map, outDir / map.name) - } - } - outFiles - }.taskValue, - playMonitoredFiles ++= (watchSources in reference).value.map(_.getCanonicalPath) - ) - } - - - }
\ No newline at end of file diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6e30920..73b07ac 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,17 +3,16 @@ import scala.scalajs.sbtplugin.ScalaJSPlugin._ object Dependencies { - val flow = "com.github.jodersky" %% "flow" % "2.0.4" - val flowNative = "com.github.jodersky" % "flow-native" % "2.0.4" + val akkaActor = "com.typesafe.akka" %% "akka-actor" % "2.3.6" - val dom = "org.scala-lang.modules.scalajs" %%%! "scalajs-dom" % "0.6" - val rx = "com.scalarx" %%%! "scalarx" % "0.2.5" + val flow = "com.github.jodersky" %% "flow" % "2.0.6" + val flowNative = "com.github.jodersky" % "flow-native" % "2.0.6" val bootstrap = "org.webjars" % "bootstrap" % "3.2.0" val fontawesome = "org.webjars" % "font-awesome" % "4.2.0" val jquery = "org.webjars" % "jquery" % "2.1.1" + val dom = "org.scala-lang.modules.scalajs" %%%! "scalajs-dom" % "0.6" + val rx = "com.scalarx" %%%! "scalarx" % "0.2.5" - def backend = Seq(bootstrap, fontawesome, jquery, flow, flowNative) - def frontend = Seq(dom, rx) }
\ No newline at end of file diff --git a/project/util.scala b/project/util.scala new file mode 100644 index 0000000..595d7fe --- /dev/null +++ b/project/util.scala @@ -0,0 +1,32 @@ +import sbt._ +import sbt.Keys._ +import play._ +import play.PlayImport.PlayKeys._ +import scala.scalajs.sbtplugin.ScalaJSPlugin +import scala.scalajs.sbtplugin.ScalaJSPlugin.ScalaJSKeys._ + +package object util { + + implicit class ScalaJSPlayProject(val project: Project) { + def dependsOnJs(reference: Project): Project = project.settings( + resourceGenerators in Compile += Def.task{ + val outDir: File = (resourceManaged in Compile).value / "public" / "lib" + + val optimized: Seq[File] = (fastOptJS in (reference, Compile)).value.allCode.map(_.path).map(file(_)) + + val outFiles = optimized.map(file => outDir / file.name) + + for ((opt, out) <- optimized zip outFiles) { + if (!out.exists || out.lastModified < opt.lastModified) { + IO.copyFile(opt, out, true) + val map = opt.getParentFile / (out.name + ".map") + IO.copyFile(map, outDir / map.name) + } + } + outFiles + }.taskValue, + playMonitoredFiles ++= (watchSources in reference).value.map(_.getCanonicalPath) + ) + } + +}
\ No newline at end of file diff --git a/backend/.gitignore b/vfd-backend/.gitignore index ed1de16..ed1de16 100644 --- a/backend/.gitignore +++ b/vfd-backend/.gitignore diff --git a/vfd-backend/app/controllers/Application.scala b/vfd-backend/app/controllers/Application.scala new file mode 100644 index 0000000..5a473b4 --- /dev/null +++ b/vfd-backend/app/controllers/Application.scala @@ -0,0 +1,31 @@ +package controllers + +import play.api._ +import play.api.mvc._ +import play.api.Play.current +import play.api.mvc.WebSocket.FrameFormatter + +import play.api.libs.functional.syntax._ +import play.api.libs.json._ + +import vfd.uav.DataFrame +import plugins.UavPlugin + + +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 index() = Action { + Ok(views.html.index()) + } + + implicit val dataFrameFormat = Json.format[DataFrame] + implicit val dataFrameFormatter = FrameFormatter.jsonFrame[DataFrame] + + def socket = WebSocket.acceptWithActor[String, DataFrame] { request => + out => use[UavPlugin].register(out) + } +}
\ No newline at end of file diff --git a/vfd-backend/app/plugins/UavPlugin.scala b/vfd-backend/app/plugins/UavPlugin.scala new file mode 100644 index 0000000..a94ed9d --- /dev/null +++ b/vfd-backend/app/plugins/UavPlugin.scala @@ -0,0 +1,44 @@ +package plugins + +import akka.actor._ +import play.api._ +import play.api.libs.concurrent.Akka +import vfd.uav._ + +class UavPlugin(app: Application) extends Plugin { + + object conf { + private val config = app.configuration.getConfig("uav") + val connection = config.flatMap(_.getString("connection")).getOrElse("mock") + val port = config.flatMap(_.getString("port")).getOrElse("/dev/ttyACM0") + val baud = config.flatMap(_.getInt("baud")).getOrElse(9600) + } + + lazy val connection: ActorRef = { + val props = conf.connection match { + case "mock" => Connection.dummy + case "fcu" => Connection.fcu(conf.port, conf.baud) + case _ => throw new RuntimeException("Unknown connection type.") + } + Akka.system(app).actorOf(props, name = "uav") + } + + def register(out: ActorRef): Props = Repeater(out, connection) + +} + +class Repeater(out: ActorRef, connection: ActorRef) extends Actor { + + override def preStart = { + connection ! Connection.Register + } + + def receive = { + case Connection.NewDataFrame(df) => out ! df + } + + } + +object Repeater { + def apply(out: ActorRef, connection: ActorRef) = Props(classOf[Repeater], out, connection) +}
\ No newline at end of file diff --git a/backend/app/views/index.scala.html b/vfd-backend/app/views/index.scala.html index 654c2a1..654c2a1 100644 --- a/backend/app/views/index.scala.html +++ b/vfd-backend/app/views/index.scala.html diff --git a/backend/app/views/main.scala.html b/vfd-backend/app/views/main.scala.html index 86f1384..db894d2 100644 --- a/backend/app/views/main.scala.html +++ b/vfd-backend/app/views/main.scala.html @@ -9,6 +9,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <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("stylesheets/main.css")"> </head> @@ -25,7 +26,7 @@ <span class="icon-bar"></span> </button> <a class="navbar-brand" href="@routes.Application.index"> - <img style="max-height: 32px;" src="@routes.Assets.at("images/logo.svg")" alt="logo"> + <!-- <img style="max-height: 100%;" src="@routes.Assets.at("images/logo-invert.svg")" alt="logo"> --> Virtual Flight Deck </a> </div> diff --git a/backend/app/views/panels/eicas.scala.html b/vfd-backend/app/views/panels/eicas.scala.html index 211adba..211adba 100644 --- a/backend/app/views/panels/eicas.scala.html +++ b/vfd-backend/app/views/panels/eicas.scala.html diff --git a/backend/app/views/panels/pfd.scala.html b/vfd-backend/app/views/panels/pfd.scala.html index 9c89d6a..9c89d6a 100644 --- a/backend/app/views/panels/pfd.scala.html +++ b/vfd-backend/app/views/panels/pfd.scala.html diff --git a/backend/conf/application.conf b/vfd-backend/conf/application.conf index ba58ed1..96478c0 100644 --- a/backend/conf/application.conf +++ b/vfd-backend/conf/application.conf @@ -60,3 +60,9 @@ logger.play=INFO # Logger provided to your application: logger.application=DEBUG +# UAV +uav.connection=mock +uav.port="/dev/ttyACM0" +uav.baud=9600 + + diff --git a/vfd-backend/conf/play.plugins b/vfd-backend/conf/play.plugins new file mode 100644 index 0000000..40c58dd --- /dev/null +++ b/vfd-backend/conf/play.plugins @@ -0,0 +1 @@ + 10000:plugins.UavPlugin
\ No newline at end of file diff --git a/backend/conf/routes b/vfd-backend/conf/routes index b211b07..b211b07 100644 --- a/backend/conf/routes +++ b/vfd-backend/conf/routes diff --git a/backend/public/fonts/DIGITAL.TXT b/vfd-backend/public/fonts/DIGITAL.TXT index 484dd47..484dd47 100644 --- a/backend/public/fonts/DIGITAL.TXT +++ b/vfd-backend/public/fonts/DIGITAL.TXT diff --git a/backend/public/fonts/DS-DIGIB.TTF b/vfd-backend/public/fonts/DS-DIGIB.TTF Binary files differindex 064ad47..064ad47 100644 --- a/backend/public/fonts/DS-DIGIB.TTF +++ b/vfd-backend/public/fonts/DS-DIGIB.TTF diff --git a/backend/public/fonts/DS-DIGII.TTF b/vfd-backend/public/fonts/DS-DIGII.TTF Binary files differindex 2aae3d8..2aae3d8 100644 --- a/backend/public/fonts/DS-DIGII.TTF +++ b/vfd-backend/public/fonts/DS-DIGII.TTF diff --git a/backend/public/fonts/DS-DIGIT.TTF b/vfd-backend/public/fonts/DS-DIGIT.TTF Binary files differindex 65642f9..65642f9 100644 --- a/backend/public/fonts/DS-DIGIT.TTF +++ b/vfd-backend/public/fonts/DS-DIGIT.TTF diff --git a/backend/public/fonts/ds-digi.ttf b/vfd-backend/public/fonts/ds-digi.ttf Binary files differindex 0925877..0925877 100644 --- a/backend/public/fonts/ds-digi.ttf +++ b/vfd-backend/public/fonts/ds-digi.ttf diff --git a/backend/public/images/instruments/altitude.svg b/vfd-backend/public/images/instruments/altitude.svg index 24146a1..24146a1 100644 --- a/backend/public/images/instruments/altitude.svg +++ b/vfd-backend/public/images/instruments/altitude.svg diff --git a/backend/public/images/instruments/attitude.svg b/vfd-backend/public/images/instruments/attitude.svg index 7f0452c..7f0452c 100644 --- a/backend/public/images/instruments/attitude.svg +++ b/vfd-backend/public/images/instruments/attitude.svg diff --git a/backend/public/images/instruments/distance.svg b/vfd-backend/public/images/instruments/distance.svg index 1f4d6ff..1f4d6ff 100644 --- a/backend/public/images/instruments/distance.svg +++ b/vfd-backend/public/images/instruments/distance.svg diff --git a/backend/public/images/instruments/generic.svg b/vfd-backend/public/images/instruments/generic.svg index 02e4caa..02e4caa 100644 --- a/backend/public/images/instruments/generic.svg +++ b/vfd-backend/public/images/instruments/generic.svg diff --git a/backend/public/images/instruments/heading.svg b/vfd-backend/public/images/instruments/heading.svg index 83cf17b..83cf17b 100644 --- a/backend/public/images/instruments/heading.svg +++ b/vfd-backend/public/images/instruments/heading.svg diff --git a/backend/public/images/leds/green-off.svg b/vfd-backend/public/images/leds/green-off.svg index 1c227fd..1c227fd 100644 --- a/backend/public/images/leds/green-off.svg +++ b/vfd-backend/public/images/leds/green-off.svg diff --git a/backend/public/images/leds/green-on.svg b/vfd-backend/public/images/leds/green-on.svg index c358e3a..c358e3a 100644 --- a/backend/public/images/leds/green-on.svg +++ b/vfd-backend/public/images/leds/green-on.svg diff --git a/backend/public/images/leds/none.svg b/vfd-backend/public/images/leds/none.svg index 85bc475..85bc475 100644 --- a/backend/public/images/leds/none.svg +++ b/vfd-backend/public/images/leds/none.svg diff --git a/backend/public/images/leds/red-off.svg b/vfd-backend/public/images/leds/red-off.svg index ecd3ca5..ecd3ca5 100644 --- a/backend/public/images/leds/red-off.svg +++ b/vfd-backend/public/images/leds/red-off.svg diff --git a/backend/public/images/leds/red-on.svg b/vfd-backend/public/images/leds/red-on.svg index e7fffea..e7fffea 100644 --- a/backend/public/images/leds/red-on.svg +++ b/vfd-backend/public/images/leds/red-on.svg diff --git a/backend/public/images/leds/yellow-on.svg b/vfd-backend/public/images/leds/yellow-on.svg index 3b271b0..3b271b0 100644 --- a/backend/public/images/leds/yellow-on.svg +++ b/vfd-backend/public/images/leds/yellow-on.svg diff --git a/backend/public/images/logo.svg b/vfd-backend/public/images/logo-invert.svg index eb38e8f..eb38e8f 100644 --- a/backend/public/images/logo.svg +++ b/vfd-backend/public/images/logo-invert.svg diff --git a/vfd-backend/public/images/logo.svg b/vfd-backend/public/images/logo.svg new file mode 100644 index 0000000..b905aad --- /dev/null +++ b/vfd-backend/public/images/logo.svg @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="64" + height="64" + id="svg2985" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="logo.svg" + viewBox="0 0 64 64"> + <defs + id="defs2987" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="11" + inkscape:cx="39.433014" + inkscape:cy="29.099049" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:window-width="1920" + inkscape:window-height="1029" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" /> + <metadata + id="metadata2990"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <path + style="fill:#222222;fill-opacity:1;stroke:#222222;stroke-opacity:1" + d="m 15.863636,5.6278409 c -5.595824,0 -10.144886,4.5490621 -10.144886,10.1448861 0,5.595824 4.549062,10.144886 10.144886,10.144886 5.595824,0 10.144886,-4.549062 10.144886,-10.144886 0,-5.595824 -4.549062,-10.1448861 -10.144886,-10.1448861 z m 0,1.7765239 c 4.619372,0 8.368362,3.7489902 8.368362,8.3683622 0,4.619372 -3.74899,8.368362 -8.368362,8.368362 -4.619372,0 -8.3683621,-3.74899 -8.3683621,-8.368362 0,-4.619372 3.7489901,-8.3683622 8.3683621,-8.3683622 z" + id="path2993" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path3008" + d="m 47.863636,5.6278409 c -5.595824,0 -10.144886,4.5490621 -10.144886,10.1448861 0,5.595824 4.549062,10.144886 10.144886,10.144886 5.595824,0 10.144886,-4.549062 10.144886,-10.144886 0,-5.595824 -4.549062,-10.1448861 -10.144886,-10.1448861 z m 0,1.7765239 c 4.619372,0 8.368362,3.7489902 8.368362,8.3683622 0,4.619372 -3.74899,8.368362 -8.368362,8.368362 -4.619372,0 -8.368362,-3.74899 -8.368362,-8.368362 0,-4.619372 3.74899,-8.3683622 8.368362,-8.3683622 z" + style="fill:#222222;fill-opacity:1;stroke:#222222;stroke-opacity:1" /> + <path + style="fill:#222222;fill-opacity:1;stroke:#222222;stroke-opacity:1" + d="m 47.863636,37.627841 c -5.595824,0 -10.144886,4.549062 -10.144886,10.144886 0,5.595824 4.549062,10.144886 10.144886,10.144886 5.595824,0 10.144886,-4.549062 10.144886,-10.144886 0,-5.595824 -4.549062,-10.144886 -10.144886,-10.144886 z m 0,1.776524 c 4.619372,0 8.368362,3.74899 8.368362,8.368362 0,4.619372 -3.74899,8.368362 -8.368362,8.368362 -4.619372,0 -8.368362,-3.74899 -8.368362,-8.368362 0,-4.619372 3.74899,-8.368362 8.368362,-8.368362 z" + id="path3010" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path3012" + d="m 15.863636,37.627841 c -5.595824,0 -10.1448856,4.549062 -10.1448856,10.144886 0,5.595824 4.5490616,10.144886 10.1448856,10.144886 5.595824,0 10.144886,-4.549062 10.144886,-10.144886 0,-5.595824 -4.549062,-10.144886 -10.144886,-10.144886 z m 0,1.776524 c 4.619372,0 8.368362,3.74899 8.368362,8.368362 0,4.619372 -3.74899,8.368362 -8.368362,8.368362 -4.619372,0 -8.3683616,-3.74899 -8.3683616,-8.368362 0,-4.619372 3.7489896,-8.368362 8.3683616,-8.368362 z" + style="fill:#222222;fill-opacity:1;stroke:#222222;stroke-opacity:1" /> + <path + style="fill:#222222;stroke:#222222;stroke-width:1.79999994999999990;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1" + d="M 22.454545,22.090909 41.363636,41" + id="path3014" + inkscape:connector-curvature="0" /> + <path + style="fill:#222222;stroke:#222222;stroke-width:1.79999994999999990;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;fill-opacity:1" + d="m 22.090909,41.090909 19,-19" + id="path3016" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/backend/public/stylesheets/main.css b/vfd-backend/public/stylesheets/main.css index 815d2b9..815d2b9 100644 --- a/backend/public/stylesheets/main.css +++ b/vfd-backend/public/stylesheets/main.css diff --git a/frontend/.gitignore b/vfd-frontend/.gitignore index ed1de16..ed1de16 100644 --- a/frontend/.gitignore +++ b/vfd-frontend/.gitignore diff --git a/frontend/src/main/scala/Frontend.scala b/vfd-frontend/src/main/scala/Frontend.scala index 0e3d554..9a628f6 100644 --- a/frontend/src/main/scala/Frontend.scala +++ b/vfd-frontend/src/main/scala/Frontend.scala @@ -31,8 +31,9 @@ class Frontend(attitudeSelector: String, azimuthSelector: String, altitudeSelect val connection = new dom.WebSocket("ws://localhost:9000/socket"); connection.onmessage = (e: dom.MessageEvent) => { + Console.println(e.data); val data = js.JSON.parse(e.data.asInstanceOf[String]).asInstanceOf[Data] - Console.println(data.roll); + roll.setAttribute("transform", "rotate(" + data.roll.toDouble + ")"); pitch.setAttribute("transform", "translate(0, " + data.pitch.toDouble + ")"); heading.setAttribute("transform", "rotate(" + data.heading.toDouble + ")"); diff --git a/vfd-shared/src/main/scala/vfd/uav/DataFrame.scala b/vfd-shared/src/main/scala/vfd/uav/DataFrame.scala new file mode 100644 index 0000000..5e1bcb7 --- /dev/null +++ b/vfd-shared/src/main/scala/vfd/uav/DataFrame.scala @@ -0,0 +1,18 @@ +package vfd.uav + +/** + * Data from UAV. + * SI units unless indicated otherwise + * @param roll roll angle [rad] + * @param pitch pitch angle (to horizon) [rad] + * @param heading heading angle to magnetic north [rad] + * @param altitude altitude to mean sea level [m] + * @param temperature ambient temperature [deg C] + */ +case class DataFrame( + roll: Double, + pitch: Double, + heading: Double, + altitude: Double, + temperature: Double +)
\ 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 new file mode 100644 index 0000000..b4e6493 --- /dev/null +++ b/vfd-uav/src/main/scala/vfd/uav/Connection.scala @@ -0,0 +1,27 @@ +package vfd.uav + +import akka.actor.Actor +import akka.actor.ActorRef +import akka.actor.Props +import scala.collection.mutable.ArrayBuffer + +object Connection { + def dummy = Props(classOf[DummyConnection]) + def fcu(port: String, baud: Int) = Props(classOf[FcuConnection], port, baud) + + trait Event + trait Command + case object Register extends Command + case class NewDataFrame(df: DataFrame) extends Event + +} + +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 +} diff --git a/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala b/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala new file mode 100644 index 0000000..bf8714f --- /dev/null +++ b/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala @@ -0,0 +1,43 @@ +package vfd.uav + +import akka.actor.Actor +import akka.actor.Terminated +import scala.concurrent.duration.FiniteDuration +import java.util.concurrent.TimeUnit._ + +class DummyConnection extends Actor with Connection { + import context._ + + var time = 0.0 + val messageInterval = FiniteDuration(20, MILLISECONDS) + + def flightData(time: Double) = { + val speed = 5.0 / 1000 + val roll = 5.0/180*math.Pi + val pitch = 10.0/180*math.Pi + Connection.NewDataFrame(DataFrame( + roll, + pitch, + (roll * time * speed) % math.Pi, + (pitch * time * speed), + 22 + )) + } + + + 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) + } + +} + + + diff --git a/vfd-uav/src/main/scala/vfd/uav/FcuConnection.scala b/vfd-uav/src/main/scala/vfd/uav/FcuConnection.scala new file mode 100644 index 0000000..f9f267d --- /dev/null +++ b/vfd-uav/src/main/scala/vfd/uav/FcuConnection.scala @@ -0,0 +1,53 @@ +package vfd.uav + +import akka.actor.Actor +import akka.actor.Props +import akka.actor.Terminated +import akka.io.IO +import com.github.jodersky.flow._ +import com.github.jodersky.flow.Serial + + +class FcuConnection(port: String, baud: Int) extends Actor with Connection { + import context._ + + val settings = SerialSettings( + baud = this.baud, + characterSize = 8, + twoStopBits = false, + parity = Parity.None + ) + + override def preStart() = { + IO(Serial) ! Serial.Open(port, settings) + } + + def receive = { + case Connection.Register => register(sender) + case Terminated(client) => unregister(client) + case Serial.CommandFailed(cmd: Serial.Open, reason: AccessDeniedException) => println("you're not allowed to open that port!") + case Serial.CommandFailed(cmd: Serial.Open, reason) => println("could not open port for some other reason: " + reason) + case Serial.Opened(settings) => { + val operator = sender + + } + case Serial.Received(bstr) => + val str = (new String(bstr.toArray, "UTF-8")).trim + + + val LinePattern = ".*[(](.+)[)].*".r + val Number = "([-]?\\d+[.]\\d+?)".r + val Components = "(.+),(.+),(.+),(.+),(.+)".r + + str match { + case LinePattern(Components(Number(r),Number(p),Number(h),Number(a),Number(t))) => + val data = Connection.NewDataFrame(DataFrame(r.toDouble, p.toDouble, h.toDouble, a.toDouble, t.toDouble)) + println(data) + for (client <- clients) { + client ! data + } + case _ => println("unknown message: " + str) + } + } + +} diff --git a/vfd-uav/src/main/scala/vfd/uav/Framer.scala b/vfd-uav/src/main/scala/vfd/uav/Framer.scala new file mode 100644 index 0000000..f587c9e --- /dev/null +++ b/vfd-uav/src/main/scala/vfd/uav/Framer.scala @@ -0,0 +1,70 @@ +package vfd.uav + +import scala.collection.mutable.ArrayBuffer + +class Framer { + final val MTU: Int = 1024 + + final val START: Byte = 0xfd.toByte + final val STOP: Byte = 0xfe.toByte + final val ESCAPE: Byte = 0xff.toByte + + final val WAITING = 0 + final val RECEIVING = 1 + final val ESCAPING = 2 + + private val data = new Array[Byte](MTU) + private var index = 0 + private var state = WAITING + + private def add(byte: Byte): Unit = { + data(index) = byte + index += 1 + if (index >= MTU) index = 0 + } + + private def clear(): Unit = index = 0 + + + def push(byte: Byte): Option[Array[Byte]] = state match { + case WAITING => + if (byte == START) { + clear() + state = RECEIVING + } + None + + case RECEIVING => byte match { + case START => + clear() + state = RECEIVING + None + case ESCAPE => + state = ESCAPING + None + case STOP => + state = WAITING + Some(java.util.Arrays.copyOfRange(data, 0, index)) + + case _ => + add(byte) + None + } + case ESCAPING => + add(byte) + state = RECEIVING + None + } + + def push(bytes: Array[Byte]): Seq[Array[Byte]] = { + val messages = new ArrayBuffer[Array[Byte]] + + for (byte <- bytes) push(byte) match { + case None => () + case Some(message) => messages += message + } + + messages + } + +}
\ No newline at end of file |