Navigation Menu

Skip to content

Commit

Permalink
lots of simulator improvements: (attn @mrpollo)
Browse files Browse the repository at this point in the history
# /admin/sim

There is a fairly extensive vehicle simulator.

GET http://localhost:8080/api/v1/admin/sim/std/KEEP/NUMVEHICLES/NUMSECS?api_key=eb34bd67.megadroneshare

Where:
* KEEP is true/false for keeping the missions after the run is over
* NUMVEHICLES is the number of sim vehicles to create
* NUMSECS is the number of seconds to keep those vehicles running
  • Loading branch information
geeksville committed Apr 25, 2014
1 parent 342d4e3 commit 3b13711
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 37 deletions.
11 changes: 11 additions & 0 deletions REST.md
Expand Up @@ -80,6 +80,17 @@ If you do not support cookies, you'll need to attach login & password as query p

Various private admin functions sit here.

# /admin/sim

There is a fairly extensive vehicle simulator.

GET http://localhost:8080/api/v1/admin/sim/std/KEEP/NUMVEHICLES/NUMSECS?api_key=eb34bd67.megadroneshare

Where:
* KEEP is true/false for keeping the missions after the run is over
* NUMVEHICLES is the number of sim vehicles to create
* NUMSECS is the number of seconds to keep those vehicles running

# TODO

* http://localhost:8080/view/5n72bt make it work using only the mission URL
Expand Down
24 changes: 15 additions & 9 deletions src/main/scala/com/geeksville/dapi/AdminController.scala
Expand Up @@ -5,7 +5,6 @@ import akka.actor.Props
import com.geeksville.dapi.test.SimGCSClient
import com.geeksville.dapi.temp.NestorImporter
import com.geeksville.dapi.temp.DoImport
import com.geeksville.dapi.test.RunTest
import com.geeksville.dapi.model.Tables
import com.geeksville.akka.AkkaReflector
import scala.concurrent.Await
Expand All @@ -16,6 +15,7 @@ import scala.xml.Elem
import org.scalatra.atmosphere._
import org.scalatra.swagger.SwaggerSupport
import org.scalatra.swagger.Swagger
import com.geeksville.dapi.test.PlaybackGCSClient

/**
* Special admin operations
Expand All @@ -26,15 +26,13 @@ class AdminController(implicit val swagger: Swagger) extends DroneHubStack with
override protected val applicationName = Some("api/v1/admin")
protected lazy val applicationDescription = s"Adminstrator API operations."

def system = MockAkka.system

lazy val simClient = system.actorOf(Props(new SimGCSClient), "simClient")
lazy val system = MockAkka.system

lazy val nestorImport = system.actorOf(Props(new NestorImporter), "importer")

lazy val akkaReflect = system.actorOf(Props(new AkkaReflector), "akkaReflect")

def host = multiParams("host").headOption.getOrElse("localhost")
def host() = multiParams("host").headOption.getOrElse("localhost")

before() {
requireAdmin()
Expand All @@ -60,16 +58,24 @@ class AdminController(implicit val swagger: Swagger) extends DroneHubStack with
private lazy val simOp = apiOperation[String]("sim") summary "Simulate a flight"

get("/sim/huge", operation(simOp)) {
simClient ! RunTest(host, "bigtest")
lazy val simClient = system.actorOf(Props(new PlaybackGCSClient(host)))
simClient ! PlaybackGCSClient.RunTest("bigtest")
"started sim"
}

get("/sim/full", operation(simOp)) {
simClient ! RunTest(host, "test")
lazy val simClient = system.actorOf(Props(new PlaybackGCSClient(host)))
simClient ! PlaybackGCSClient.RunTest("test")
"started sim"
}

get("/sim/quick", operation(simOp)) {
simClient ! RunTest(host, "quick")
get("/sim/std/:keep/:numVehicles/:numSecs", operation(simOp)) {
val keep = params("keep").toBoolean
val numVehicles = params("numVehicles").toInt
val numSecs = params("numSecs").toInt
val h = host
lazy val simClient = system.actorOf(Props(new SimGCSClient(h, keep)))
simClient ! SimGCSClient.RunTest(numVehicles, numSecs)
"started sim"
}

Expand Down
150 changes: 123 additions & 27 deletions src/main/scala/com/geeksville/dapi/test/SimGCSClient.scala
Expand Up @@ -22,61 +22,152 @@ import akka.pattern.ask
import scala.concurrent.Await
import akka.actor.Identify
import akka.util.Timeout

case class RunTest(host: String = APIConstants.DEFAULT_SERVER, name: String)
import com.geeksville.flight.VehicleSimulator
import org.mavlink.messages.MAVLinkMessage
import akka.actor.ActorContext
import grizzled.slf4j.Logging
import akka.actor.ActorSystem
import scala.util.Random
import com.geeksville.flight.Location

/**
* An integration test that calls into the server as if it was a GCS/vehicle client
*/
class SimGCSClient extends Actor with ActorLogging {
class SimGCSClient(host: String, keep: Boolean) extends Actor with ActorLogging {
import context._

private val webapi = new GCSHooksImpl(host)
private val random = new Random

def receive = {
case RunTest(host, name) =>
case Terminated(_) =>
if (children.isEmpty) // All our vehicles done?
self ! PoisonPill

case SimGCSClient.RunTest(numVehicles, numSeconds) =>
log.error("Running test")
if (name != "quick") fullTest(name, host) else quickTest(host)
runTest(numVehicles, numSeconds)
}

override def postStop() {
webapi.stopMission(keep)
webapi.close()

log.info("Sim test completed")

super.postStop()
}

private def quickTest(host: String) {
using(new GCSHooksImpl(host)) { webapi: GCSHooks =>
private class SimVehicle(val systemId: Int, numSeconds: Int, val numPoints: Int) extends Actor with ActorLogging with VehicleSimulator {
case object SimNext
val interfaceNum = 0
val isControllable = false

val center = (21.2966980, -157.8480360)
val lineAngle = random.nextDouble % (math.Pi * 2)
val maxLen = 0.5 // in degrees
val maxAlt = 100

val loginName = "test-bob"
val email = "test-bob@3drobotics.com"
val password = "sekrit"
var numRemaining = numPoints

// Create user if necessary/possible
if (webapi.isUsernameAvailable(loginName))
webapi.createUser(loginName, password, Some(email))
else
webapi.loginUser(loginName, password)
val uuid = UUID.nameUUIDFromBytes(Array(systemId.toByte))
log.info("Created sim vehicle $systemID: $uuid")
webapi.setVehicleId(uuid.toString, interfaceNum, systemId, isControllable)

webapi.flush()
val interval = numSeconds.toDouble / numPoints
private def scheduleNext() = context.system.scheduler.scheduleOnce(interval seconds, self, SimNext)

val interfaceNum = 0;
val sysId = 1;
webapi.setVehicleId("550e8400-e29b-41d4-a716-446655440000",
interfaceNum, sysId, false);
// Start our sim
scheduleNext()

log.info("Starting mission")
webapi.startMission(true, UUID.randomUUID)
// webapi.filterMavlink(interfaceNum, payload);
/// A fake current position
def curLoc = {
val pos = numRemaining.toDouble / numPoints
val len = maxLen * pos

webapi.stopMission(true)
Location(center._1 + len * math.cos(lineAngle),
center._2 + len * math.sin(lineAngle),
Some(maxAlt * math.sin(pos)))
}

def receive = {
case SimNext =>
if (numRemaining == 0)
self ! PoisonPill
else {
sendMavlink(makeVFRHud(random.nextFloat % 10, random.nextFloat % 10))
sendMavlink(makePosition(curLoc))
numRemaining -= 1
scheduleNext()
}
}

log.info("Test successful")
/**
* m must be a SendYoungest or a MAVLinkMessage
*/
override protected def handlePacket(m: Any) {
//log.debug(s"Sending to server: $m")
m match {
case m: MAVLinkMessage =>
webapi.filterMavlink(interfaceNum, m.encode)
}
}
}

private def runTest(numVehicles: Int, numSeconds: Int) {

val loginName = "test-bob"
val email = "test-bob@3drobotics.com"
val password = "sekrit"

// Create user if necessary/possible
if (webapi.isUsernameAvailable(loginName))
webapi.createUser(loginName, password, Some(email))
else
webapi.loginUser(loginName, password)

webapi.flush()

// How long to run the test
val numPoints = numSeconds * 2

log.info("Starting mission")
webapi.startMission(keep, UUID.randomUUID)

(0 until numVehicles).foreach { i =>
watch(context.actorOf(Props(new SimVehicle(i, numSeconds, numPoints))))
}

// Handle the no vehicle case
if (numVehicles == 0)
self ! PoisonPill
}

}

object PlaybackGCSClient {
case class RunTest(name: String)
}

class PlaybackGCSClient(host: String) extends Actor with ActorLogging {
def receive = {
case PlaybackGCSClient.RunTest(name) =>
log.error("Running test")
fullTest(name)
}

/**
* Creates an fake vehicle which actually calls up and sends real TLOG data/accepts commands
*
* FIXME: Add support for accepting commands
* FIXME: Don't use the old MavlinkEventBus global
*/
private def fullTest(testname: String, host: String) {
private def fullTest(testname: String) {
import context._

log.info("Starting full test vehicle")
val tlog = context.actorOf(Props {
val s = new BufferedInputStream(getClass.getResourceAsStream(testname + ".tlog"), 8192)
val s = new BufferedInputStream(this.getClass.getResourceAsStream(testname + ".tlog"), 8192)
val actor = TlogStreamReceiver.open(s, 10000, autoStart = false) // Play back the file at 10000x the normal speed
actor
}, "tlogsim")
Expand Down Expand Up @@ -107,4 +198,9 @@ class SimGCSClient extends Actor with ActorLogging {
tlog ! MavlinkStreamReceiver.StartMsg
}
}
}

object SimGCSClient extends Logging {
case class RunTest(numVehicles: Int, numSeconds: Int)

}

0 comments on commit 3b13711

Please sign in to comment.