aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2014-10-01 18:34:11 +0200
committerJakob Odersky <jodersky@gmail.com>2014-10-01 18:34:11 +0200
commit6b0d380e6fb1d91267acd27b52e996450ed9b06f (patch)
tree8ccea19de49200fd559e296563d744d625e3e57f
parent36445516358a2e6a69057c0b10507103a538b54c (diff)
downloadmavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.tar.gz
mavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.tar.bz2
mavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.zip
implement connection to uav (dirty but works)
-rw-r--r--backend/app/controllers/Application.scala40
-rw-r--r--backend/app/uav/UavPlugin.scala34
-rw-r--r--backend/app/uav/connections.scala123
-rw-r--r--backend/conf/play.plugins1
-rw-r--r--backend/conf/routes1
-rw-r--r--frontend/src/main/scala/Frontend.scala38
-rw-r--r--project/Dependencies.scala6
7 files changed, 221 insertions, 22 deletions
diff --git a/backend/app/controllers/Application.scala b/backend/app/controllers/Application.scala
index 6cd886f..1abaee0 100644
--- a/backend/app/controllers/Application.scala
+++ b/backend/app/controllers/Application.scala
@@ -1,12 +1,52 @@
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
new file mode 100644
index 0000000..060e3dc
--- /dev/null
+++ b/backend/app/uav/UavPlugin.scala
@@ -0,0 +1,34 @@
+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
new file mode 100644
index 0000000..a922519
--- /dev/null
+++ b/backend/app/uav/connections.scala
@@ -0,0 +1,123 @@
+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
new file mode 100644
index 0000000..2211f4a
--- /dev/null
+++ b/backend/conf/play.plugins
@@ -0,0 +1 @@
+ 10000:uav.UavPlugin \ No newline at end of file
diff --git a/backend/conf/routes b/backend/conf/routes
index 20fd042..b211b07 100644
--- a/backend/conf/routes
+++ b/backend/conf/routes
@@ -4,6 +4,7 @@
# Home page
GET / controllers.Application.index
+GET /socket controllers.Application.socket
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
diff --git a/frontend/src/main/scala/Frontend.scala b/frontend/src/main/scala/Frontend.scala
index 3a7c6e7..0e3d554 100644
--- a/frontend/src/main/scala/Frontend.scala
+++ b/frontend/src/main/scala/Frontend.scala
@@ -4,6 +4,13 @@ import scala.scalajs.js
import js.annotation.JSExport
import org.scalajs.dom
+class Data extends js.Object {
+ def roll: Double = ???
+ def pitch: Double = ???
+ def heading: Double = ???
+ def altitude: Double = ???
+ def temperature: Double = ???
+}
@JSExport
class Frontend(attitudeSelector: String, azimuthSelector: String, altitudeSelector: String) {
@@ -16,28 +23,21 @@ class Frontend(attitudeSelector: String, azimuthSelector: String, altitudeSelect
@JSExport
def main() = {
- dom.setInterval(() => foo, 50)
- }
-
- var a = 0.0
- var r = 0.0
- var p = 0.0
- var h = 0.0
- def foo() = {
- h += 3
- r += 0.05
- p += 0.1
- a += 0.2
var roll = svgDoc.getElementById("roll");
var pitch = svgDoc.getElementById("pitch");
- pitch.setAttribute("transform", "translate(0, " + 30 * math.sin(p) + ")");
- roll.setAttribute("transform", "rotate(" + 60 * math.sin(r) + ")");
-
- var azimuth = svgDoc2.getElementById("heading");
- azimuth.setAttribute("transform", "rotate(" + h + ")");
-
+ var heading = svgDoc2.getElementById("heading");
var altitude = svgDoc3.getElementById("hand")
- altitude.setAttribute("transform", "rotate(" + a + ")")
+
+ val connection = new dom.WebSocket("ws://localhost:9000/socket");
+
+ connection.onmessage = (e: dom.MessageEvent) => {
+ 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 + ")");
+ altitude.setAttribute("transform", "rotate(" + data.altitude.toDouble * 36 + ")")
+ }
}
} \ No newline at end of file
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 17c96f1..6e30920 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -3,8 +3,8 @@ import scala.scalajs.sbtplugin.ScalaJSPlugin._
object Dependencies {
- val flow = "org.github.jodersky" %% "flow" % "2.0.5"
- val flowNative = "org.github.jodersky" % "flow-native" % "2.0.5"
+ val flow = "com.github.jodersky" %% "flow" % "2.0.4"
+ val flowNative = "com.github.jodersky" % "flow-native" % "2.0.4"
val dom = "org.scala-lang.modules.scalajs" %%%! "scalajs-dom" % "0.6"
val rx = "com.scalarx" %%%! "scalarx" % "0.2.5"
@@ -14,6 +14,6 @@ object Dependencies {
val jquery = "org.webjars" % "jquery" % "2.1.1"
- def backend = Seq(bootstrap, fontawesome, jquery)
+ def backend = Seq(bootstrap, fontawesome, jquery, flow, flowNative)
def frontend = Seq(dom, rx)
} \ No newline at end of file