diff options
author | Jakob Odersky <jodersky@gmail.com> | 2014-10-01 18:34:11 +0200 |
---|---|---|
committer | Jakob Odersky <jodersky@gmail.com> | 2014-10-01 18:34:11 +0200 |
commit | 6b0d380e6fb1d91267acd27b52e996450ed9b06f (patch) | |
tree | 8ccea19de49200fd559e296563d744d625e3e57f | |
parent | 36445516358a2e6a69057c0b10507103a538b54c (diff) | |
download | mavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.tar.gz mavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.tar.bz2 mavigator-6b0d380e6fb1d91267acd27b52e996450ed9b06f.zip |
implement connection to uav (dirty but works)
-rw-r--r-- | backend/app/controllers/Application.scala | 40 | ||||
-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-- | backend/conf/routes | 1 | ||||
-rw-r--r-- | frontend/src/main/scala/Frontend.scala | 38 | ||||
-rw-r--r-- | project/Dependencies.scala | 6 |
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 |