diff options
author | Jakob Odersky <jodersky@gmail.com> | 2014-12-16 00:09:12 +0100 |
---|---|---|
committer | Jakob Odersky <jodersky@gmail.com> | 2014-12-16 00:09:12 +0100 |
commit | 458971f834a3af0dbf2fffe527352fa11e7d8168 (patch) | |
tree | 226675f8402f2c099cb15b21cab28eb8784f1c96 | |
parent | 84c641d12187183466df936eaa7c1637d861cf62 (diff) | |
download | mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.tar.gz mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.tar.bz2 mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.zip |
generate mavlink files in build
30 files changed, 538 insertions, 382 deletions
diff --git a/concise.xml b/concise.xml new file mode 100644 index 0000000..9be16f7 --- /dev/null +++ b/concise.xml @@ -0,0 +1,93 @@ +<?xml version='1.0'?> +<mavlink> + <version>1</version> + <enums> + <enum name="MAV_STATE"> + <entry value="0" name="MAV_STATE_UNINIT"> + <description>Uninitialized system, state is unknown.</description> + </entry> + <entry value="1" name="MAV_STATE_BOOT"> + <description>System is booting up.</description> + </entry> + <entry value="2" name="MAV_STATE_CALIBRATING"> + <description>System is calibrating and not flight-ready.</description> + </entry> + <entry value="3" name="MAV_STATE_STANDBY"> + <description>System is grounded and on standby. It can be launched any time.</description> + </entry> + <entry value="4" name="MAV_STATE_ACTIVE"> + <description>System is active and might be already airborne. Motors are engaged.</description> + </entry> + <entry value="5" name="MAV_STATE_CRITICAL"> + <description>System is in a non-normal flight mode. It can however still navigate.</description> + </entry> + <entry value="6" name="MAV_STATE_EMERGENCY"> + <description>System is in a non-normal flight mode. It lost control over parts or over the whole airframe. It is in mayday and going down.</description> + </entry> + <entry value="7" name="MAV_STATE_POWEROFF"> + <description>System just initialized its power-down sequence, will shut down now.</description> + </entry> + </enum> + </enums> + <messages> + <message id="0" name="HEARTBEAT"> + <description>The heartbeat message shows that a system is present and responding.</description> + <field type="uint8_t" name="system_state" enum="MAV_STATE">Global state of system.</field> + </message> + <message id="1" name="POWER"> + <description>Information about the main power source.</description> + <field type="uint16_t" name="voltage">Voltage of the source (mV)</field> + </message> + <message id="2" name="IMU"> + <description>The IMU readings in a NED body frame</description> + <field type="int32_t" name="xacc">X acceleration (mm/s^2)</field> + <field type="int32_t" name="yacc">Y acceleration (mm/s^2)</field> + <field type="int32_t" name="zacc">Z acceleration (mm/s^2)</field> + <field type="int32_t" name="xgyro">Angular speed around X axis (mrad / sec)</field> + <field type="int32_t" name="ygyro">Angular speed around Y axis (mrad / sec)</field> + <field type="int32_t" name="zgyro">Angular speed around Z axis (mrad / sec)</field> + <field type="int32_t" name="xmag">X Magnetic field (uT)</field> + <field type="int32_t" name="ymag">Y Magnetic field (uT)</field> + <field type="int32_t" name="zmag">Z Magnetic field (uT)</field> + <field type="int32_t" name="alt">Altitude to mean sea level (mm)</field> + <field type="uint32_t" name="temperature">Ambient temperature (mK)</field> + </message> + <message id="3" name="DISTANCE"> + <description>Information on distance sensors</description> + <field type="int16_t" name="relative_alt">Relative altitude to ground (mm)</field> + </message> + <message name="PING" id="4"> + <description>Ping a target system, usually used to determine latency.</description> + <field type="uint8_t" name="target_system">System ID</field> + <field type="uint8_t" name="target_component">Component ID</field> + </message> + <message name="ACK" id="5"> + <description>Acknowledgement packet</description> + <field type="uint8_t" name="target_system">System ID</field> + <field type="uint8_t" name="target_component">Component ID</field> + </message> + <message id="70" name="RC_CHANNELS_OVERRIDE"> + <description>The RAW values of the RC channels sent to the MAV to override info received from the RC radio. A value of UINT16_MAX means no change to that channel. A value of 0 means control of that channel should be released back to the RC radio. The standard PPM modulation is as follows: 1000 microseconds: 0%, 2000 microseconds: 100%. Individual receivers/transmitters might violate this specification.</description> + <field type="uint8_t" name="target_system">System ID</field> + <field type="uint8_t" name="target_component">Component ID</field> + <field type="uint16_t" name="chan1_raw">RC channel 1 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan2_raw">RC channel 2 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan3_raw">RC channel 3 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan4_raw">RC channel 4 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan5_raw">RC channel 5 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan6_raw">RC channel 6 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan7_raw">RC channel 7 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + <field type="uint16_t" name="chan8_raw">RC channel 8 value, in microseconds. A value of UINT16_MAX means to ignore this field.</field> + </message> + <message name="RADIO_STATUS" id="109"> + <description>Status generated by radio</description> + <field type="uint8_t" name="rssi">local signal strength</field> + <field type="uint8_t" name="remrssi">remote signal strength</field> + <field type="uint8_t" name="txbuf">how full the tx buffer is as a percentage</field> + <field type="uint8_t" name="noise">background noise level</field> + <field type="uint8_t" name="remnoise">remote background noise level</field> + <field type="uint16_t" name="rxerrors">receive errors</field> + <field type="uint16_t" name="fixed">count of error corrected packets</field> + </message> + </messages> +</mavlink> diff --git a/project/Build.scala b/project/Build.scala index 2728c48..4ca4b3b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -7,18 +7,16 @@ import scala.scalajs.sbtplugin.ScalaJSPlugin import scala.scalajs.sbtplugin.ScalaJSPlugin.ScalaJSKeys._ import Dependencies._ +import com.github.jodersky.sbt.mavlink.MavlinkKeys._ +import com.github.jodersky.sbt.SbtMavlink object ApplicationBuild extends Build { //settings common to all projects val common = Seq( scalaVersion := "2.11.4", - scalacOptions ++= Seq("-feature", "-deprecation") - ) - - //settings for js/jvm projects using shared sources - val shared = Seq( - unmanagedSourceDirectories in Compile += (scalaSource in (mavlink, Compile)).value + scalacOptions ++= Seq("-feature", "-deprecation"), + mavlinkDialect := file(".") / "concise.xml" ) lazy val root = Project("root", file(".")).aggregate( @@ -27,15 +25,10 @@ object ApplicationBuild extends Build { frontend ) - lazy val mavlink = ( - Project("vfd-mavlink", file("vfd-mavlink")) - settings(common: _*) - ) - lazy val uav = ( Project("vfd-uav", file("vfd-uav")) + enablePlugins(SbtMavlink) settings(common: _*) - settings(shared: _*) settings( libraryDependencies ++= Seq( akkaActor, @@ -48,6 +41,7 @@ object ApplicationBuild extends Build { lazy val backend = ( Project("vfd-backend", file("vfd-backend")) enablePlugins(PlayScala) + enablePlugins(SbtMavlink) settings(common: _*) settings( resolvers += Resolver.url("scala-js-releases", url("http://dl.bintray.com/content/scala-js/scala-js-releases"))(Resolver.ivyStylePatterns), @@ -64,8 +58,8 @@ object ApplicationBuild extends Build { lazy val frontend = ( Project("vfd-frontend", file("vfd-frontend")) settings(ScalaJSPlugin.scalaJSSettings: _*) + enablePlugins(SbtMavlink) settings(common: _*) - settings(shared: _*) settings( libraryDependencies ++= Seq( rx, diff --git a/project/build.properties b/project/build.properties index be6c454..748703f 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.5 +sbt.version=0.13.7 diff --git a/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Main.scala b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Main.scala new file mode 100644 index 0000000..5f77c1a --- /dev/null +++ b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Main.scala @@ -0,0 +1,41 @@ +package com.github.jodersky.mavlink + +import scala.xml.XML +import com.github.jodersky.mavlink.parsing.Protocol +import java.io.FileWriter +import java.io.BufferedWriter +import scalax.file.Path +import java.io.File + +object Main { + + def prettify(str: String) = str.replaceAll("(\\s*\n)(\\s*\n)+", "\n\n") + + private def processTemplates(protocol: Protocol, rootOut: Path) = { + val root = rootOut / Path.fromString("org/mavlink") + val mappings: List[(String, Path)] = List( + org.mavlink.txt.Crc().body -> Path("Crc.scala"), + org.mavlink.txt.Packet(protocol.messages).body -> Path("Packet.scala"), + org.mavlink.txt.Parser().body -> Path("Parser.scala"), + org.mavlink.messages.txt.Message(protocol.messages).body -> Path.fromString("messages/messages.scala") + ) + + for ((str, file) <- mappings) yield { + val out = root / file + out.createFile(true, false) + out.write(prettify(str)) + out + } + } + + def run(dialect: File, out: File) = { + val xml = XML.loadFile(dialect) + val protocol = Protocol.parse(xml) + processTemplates(protocol, Path(out)).map(_.fileOption.get) + } + + def main(args: Array[String]): Unit = { + run(new File(args(0)), new File(args(1))) + } + +}
\ No newline at end of file diff --git a/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Crc.scala b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Crc.scala new file mode 100644 index 0000000..c54b7e9 --- /dev/null +++ b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Crc.scala @@ -0,0 +1,24 @@ +package com.github.jodersky.mavlink.parsing + +case class Crc(val crc: Int = 0xffff) extends AnyVal { + + def accumulate(datum: Byte): Crc = { + val d = datum & 0xff + var tmp = d ^ (crc & 0xff) + tmp ^= (tmp << 4) & 0xff; + Crc( + ((crc >> 8) & 0xff) ^ (tmp << 8) ^ (tmp << 3) ^ ((tmp >> 4) & 0xff)) + } + + def accumulate(data: Seq[Byte]): Crc = { + var next = this + for (d <- data) { + next = next.accumulate(d) + } + next + } + + def lsb: Byte = crc.toByte + def msb: Byte = (crc >> 8).toByte + +}
\ No newline at end of file diff --git a/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/FieldTypes.scala b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/FieldTypes.scala new file mode 100644 index 0000000..0cf29b4 --- /dev/null +++ b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/FieldTypes.scala @@ -0,0 +1,27 @@ +package com.github.jodersky.mavlink.parsing + +import scala.xml.Node + +object FieldTypes { + + sealed trait Type { + def width: Int + def scalaType: String + def nativeType: String + } + + case class IntType(signed: Boolean, width: Int, scalaType: String, nativeType: String) extends Type + + def parse(xml: Node): Type = xml.text match { + case "int8_t" => IntType(true, 1, "Byte", xml.text) + case "int16_t" => IntType(true, 2, "Short", xml.text) + case "int32_t" => IntType(true, 4, "Int", xml.text) + case "int64_t" => IntType(true, 8, "Long", xml.text) + case "uint8_t" => IntType(false, 1, "Byte", xml.text) + case "uint16_t" => IntType(false, 2, "Short", xml.text) + case "uint32_t" => IntType(false, 4, "Int", xml.text) + case "uint64_t" => IntType(false, 8, "Long", xml.text) + case _ => Parser.fatal("unsupported type \"" + xml.text + "\"", xml) + } + +}
\ No newline at end of file diff --git a/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Name.scala b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Name.scala new file mode 100644 index 0000000..6f5b6a7 --- /dev/null +++ b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/Name.scala @@ -0,0 +1,14 @@ +package com.github.jodersky.mavlink.parsing + +object Name { + + def words(raw: String) = raw.split("\\s+|_") + + def className(raw: String): String = words(raw.toLowerCase()).map(_.capitalize).mkString("") + + def varName(raw: String) = { + val (head, tail) = className(raw).splitAt(1) + head.toLowerCase() + tail + } + +}
\ No newline at end of file diff --git a/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/parsing.scala b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/parsing.scala new file mode 100644 index 0000000..e14e0c3 --- /dev/null +++ b/project/mavlink-library/src/main/scala/com/github/jodersky/mavlink/parsing/parsing.scala @@ -0,0 +1,85 @@ +package com.github.jodersky.mavlink.parsing + +import scala.xml.Node +import scala.util.Try +import FieldTypes._ +import Parser._ +import Name._ + +object Parser { + def fatal(message: String, xml: Node) = throw new RuntimeException("Cannot parse message definition: " + message + " " + xml) +} + +case class Protocol(version: String, enums: Seq[Enum], messages: Seq[Message]) +object Protocol { + def parse(xml: Node) = { + val root = xml \\ "mavlink" + val version = (root \ "version").text + val enums = (root \ "enums" \ "_") map Enum.parse + val messages = (root \ "messages" \ "_") map Message.parse + Protocol(version, enums, messages) + } +} + +case class Enum(name: String, entries: Seq[EnumEntry]) { + def scalaName = className(name) +} +object Enum { + def parse(xml: Node) = { + val name = (xml \ "@name").headOption.map(_.text).getOrElse(fatal("no name defined", xml)) + val entries = (xml \ "entry") map EnumEntry.parse + Enum(name, entries) + } +} + +case class EnumEntry(value: Int, name: String, description: String) { + def scalaName = className(name) +} +object EnumEntry { + def parse(xml: Node) = { + val valueString = (xml \ "@value").headOption.map(_.text).getOrElse(fatal("no value defined", xml)) + val value = Try { Integer.parseInt(valueString) }.getOrElse(fatal("value must be an integer", xml)) + val name = (xml \ "@name").headOption.map(_.text).getOrElse(fatal("no name defined", xml)) + val description = (xml \ "description").text + EnumEntry(value, name, description) + } +} + +case class Message(id: Byte, name: String, description: String, fields: Seq[Field]) { + def scalaName = className(name) + def orderedFields = fields.sortBy(_.tpe.width)(Ordering[Int].reverse) + + lazy val checksum = { + var c = new Crc() + c = c.accumulate((name + " ").getBytes) + for (field <- orderedFields) { + c = c.accumulate((field.tpe.nativeType + " ").getBytes) + c = c.accumulate((field.name + " ").getBytes) + } + (c.lsb ^ c.msb).toByte + } +} +object Message { + def parse(xml: Node): Message = { + val idString = (xml \ "@id").headOption.map(_.text).getOrElse(fatal("no id defined", xml)) + val id = Try { Integer.parseInt(idString).toByte }.getOrElse(fatal("id must be an integer", xml)) + val name = (xml \ "@name").headOption.map(_.text).getOrElse(fatal("no name defined", xml)) + val description = (xml \ "description").text + val fields = (xml \ "field") map Field.parse + Message(id, name, description, fields) + } +} + +case class Field(tpe: Type, name: String, enum: Option[String], description: String) { + def scalaName = varName(name) +} +object Field { + def parse(xml: Node): Field = { + val tpeNode = (xml \ "@type").headOption.getOrElse(fatal("no type defined", xml)) + val tpe = FieldTypes.parse(tpeNode) + val name = (xml \ "@name").headOption.map(_.text).getOrElse(fatal("no name defined", xml)) + val enum = (xml \ "@enum").headOption.map(_.text) + val description = (xml).text + Field(tpe, name, enum, description) + } +}
\ No newline at end of file diff --git a/vfd-mavlink/src/main/scala/org/mavlink/Crc.scala b/project/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt index e11645e..61b5c6f 100644 --- a/vfd-mavlink/src/main/scala/org/mavlink/Crc.scala +++ b/project/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt @@ -1,3 +1,5 @@ +@() + package org.mavlink /** @@ -7,7 +9,7 @@ package org.mavlink */ case class Crc(val crc: Int = 0xffff) extends AnyVal { - def next(datum: Byte): Crc = { + def accumulate(datum: Byte): Crc = { val d = datum & 0xff var tmp = d ^ (crc & 0xff) tmp ^= (tmp << 4) & 0xff; diff --git a/project/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt new file mode 100644 index 0000000..6f96b40 --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt @@ -0,0 +1,59 @@ +@(messages: Seq[parsing.Message]) + +@messageCrcs() = @{ + for (i <- 0 to 255) yield { + val crc = messages.find(_.id == i).map(_.checksum).getOrElse(0) + crc.toString + } +} + +package org.mavlink + +case class Packet( + seq: Byte, + systemId: Byte, + componentId: Byte, + messageId: Byte, + payload: Seq[Byte] +) { + + lazy val crc = { + var c = new Crc() + c = c.accumulate(payload.length.toByte) + c = c.accumulate(seq) + c = c.accumulate(systemId) + c = c.accumulate(componentId) + c = c.accumulate(messageId) + for (p <- payload) { + c = c.accumulate(p) + } + c = c.accumulate(Packet.extraCrc(messageId)) + c + } + + def toSeq: Seq[Byte] = Seq( + Packet.Stx, + payload.length.toByte, + seq, + systemId, + componentId, + messageId + ) ++ payload ++ Seq( + crc.lsb, + crc.msb + ) + +} + +object Packet { + + final val Stx: Byte = (0xfe).toByte + final val MaxPayloadLength: Int = @messages.map(_.fields.map(_.tpe.width).sum).max + + final val ExtraCrcs: Seq[Byte] = @messageCrcs().mkString("Array[Byte](", ", ", ")") + + def extraCrc(id: Byte) = ExtraCrcs(id & 0xff) + + final val Empty = Packet(0, 0, 0, -1, Array(0: Byte)) + +}
\ No newline at end of file diff --git a/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala b/project/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt index 3cd9dc3..f0e30dc 100644 --- a/vfd-mavlink/src/main/scala/org/mavlink/Parser.scala +++ b/project/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt @@ -1,3 +1,5 @@ +@() + package org.mavlink import scala.collection.mutable.ArrayBuffer @@ -51,27 +53,27 @@ class Parser(receiver: Packet => Unit, error: Parser.ParseErrors.ParseError => U case GotStx => inbound.crc = new Crc() inbound.length = (c & 0xff) - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) state = GotLength case GotLength => inbound.seq = c; - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) state = GotSeq case GotSeq => inbound.systemId = c - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) state = GotSysId case GotSysId => inbound.componentId = c - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) state = GotCompId case GotCompId => inbound.messageId = c - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) if (inbound.length == 0) { state = GotPayload } else { @@ -81,7 +83,7 @@ class Parser(receiver: Packet => Unit, error: Parser.ParseErrors.ParseError => U case GotMsgId => inbound.payload += c - inbound.crc = inbound.crc.next(c) + inbound.crc = inbound.crc.accumulate(c) if(inbound.payload.length >= Packet.MaxPayloadLength) { state = Idle error(ParseErrors.OverflowError) @@ -91,7 +93,7 @@ class Parser(receiver: Packet => Unit, error: Parser.ParseErrors.ParseError => U } case GotPayload => - inbound.crc = inbound.crc.next(Packet.MessageIdCrcEnds(inbound.messageId & 0xff)) + inbound.crc = inbound.crc.accumulate(Packet.extraCrc(inbound.messageId)) if (c != inbound.crc.lsb) { state = Idle if (c == Packet.Stx) { diff --git a/project/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt new file mode 100644 index 0000000..77ad6d2 --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt @@ -0,0 +1,5 @@ +@() + +/** + * This file has been machine generated. + */
\ No newline at end of file diff --git a/project/mavlink-library/src/main/twirl/org/mavlink/messages/Message.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/messages/Message.scala.txt new file mode 100644 index 0000000..1507df4 --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/messages/Message.scala.txt @@ -0,0 +1,70 @@ +@(messages: Seq[parsing.Message]) + +@extract(field: parsing.Field, offset: Int) = { + @field.tpe.width match { + case 1 => {payload(@offset)} + case 2 => {((payload(@offset)) | ((payload(@{offset+1}) & 0xff) << 8)).toShort} + case 4 => {(payload(@offset)) | ((payload(@{offset+1}) & 0xff) << 8) | ((payload(@{offset+2}) & 0xff) << 16) | ((payload(@{offset+3}) & 0xff) << 24)} + case 8 => {payload(@offset) | + ((payload(@{offset+2}) & 0xffl) << 8) | + ((payload(@{offset+3}) & 0xffl) << 16) | + ((payload(@{offset+4}) & 0xffl) << 24) | + ((payload(@{offset+5}) & 0xffl) << 32) | + ((payload(@{offset+6}) & 0xffl) << 40) | + ((payload(@{offset+7}) & 0xffl) << 48) | + ((payload(@{offset+8}) & 0xffl) << 56)} + } +} + +@insert(field: parsing.Field, offset: Int) = { + @defining("m." + field.scalaName) { attr => + @if(field.tpe.width >= 1) { arr(@offset) = (@attr & 0xff).toByte } + @if(field.tpe.width >= 2) { arr(@{offset+1}) = ((@attr >> 8) & 0xff).toByte } + @if(field.tpe.width >= 3) { arr(@{offset+2}) = ((@attr >> 16) & 0xff).toByte } + @if(field.tpe.width >= 4) { arr(@{offset+3}) = ((@attr >> 24) & 0xff).toByte } + @if(field.tpe.width >= 5) { arr(@{offset+4}) = ((@attr >> 32) & 0xff).toByte } + @if(field.tpe.width >= 6) { arr(@{offset+5}) = ((@attr >> 40) & 0xff).toByte } + @if(field.tpe.width >= 7) { arr(@{offset+6}) = ((@attr >> 48) & 0xff).toByte } + @if(field.tpe.width >= 8) { arr(@{offset+7}) = ((@attr >> 56) & 0xff).toByte } + } +} + +@org.mavlink.txt._header() + +package org.mavlink.messages + +sealed trait Message +@for(message <- messages) { + @_message_class(message) +} +case class Unknown(id: Byte, payload: Seq[Byte]) extends Message + +object Message { + def unpack(id: Byte, payload: Seq[Byte]) = id match { + @for(message <- messages) { + case @message.id => + @defining(message.orderedFields){ ordered => + @defining(ordered.map(_.tpe.width).scanLeft(0)(_ + _)){ offsets => + @for((field, offset) <- ordered zip offsets) { + val @field.scalaName: @field.tpe.scalaType = @extract(field, offset) + }}} + @{message.scalaName}@message.fields.map(_.scalaName).mkString("(", ", ", ")") + } + + case id => Unknown(id, payload) + } + + def pack(message: Message): (Byte, Seq[Byte]) = message match { + @for(message <- messages) { + case m: @message.scalaName => + val arr = new Array[Byte](@message.fields.map(_.tpe.width).sum) + @defining(message.orderedFields) { ordered => + @defining(ordered.map(_.tpe.width).scanLeft(0)(_ + _)){ offsets => + @for((field, offset) <- ordered zip offsets) { + @insert(field, offset) + } + (@message.id, arr) + }}} + case u: Unknown => (u.id, u.payload) + } +}
\ No newline at end of file diff --git a/project/mavlink-library/src/main/twirl/org/mavlink/messages/_message_class.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/messages/_message_class.scala.txt new file mode 100644 index 0000000..78c8326 --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/messages/_message_class.scala.txt @@ -0,0 +1,14 @@ +@(message: parsing.Message) + +@arguments(fields: Seq[parsing.Field]) = @{ + fields.map(f => f.scalaName + ": " + f.tpe.scalaType) +} + +@comment(paragraphs: Seq[String]) = @{ + paragraphs.mkString("/**\n * ", "\n * ", "\n */") +} + +@defining(message.description.grouped(120).toSeq){intro => +@defining(message.fields.map(field => "@param " + field.name + " " + field.description)){ fields => +@comment(intro ++ fields)}} +case class @{message.scalaName}@arguments(message.fields).mkString("(", ", ", ")") extends Message
\ No newline at end of file diff --git a/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/SbtMavlink.scala b/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/SbtMavlink.scala new file mode 100644 index 0000000..58692cd --- /dev/null +++ b/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/SbtMavlink.scala @@ -0,0 +1,24 @@ +package com.github.jodersky.sbt + +import sbt._ +import Keys._ +import plugins._ +import mavlink.MavlinkKeys._ +import sbt.Project.Initialize +import com.github.jodersky.mavlink.Main + +object SbtMavlink extends AutoPlugin { + + override def requires = JvmPlugin + + lazy val generate = Def.task[Seq[File]] { + streams.value.log.info("Generating mavlink files...") + Main.run((mavlinkDialect in Compile).value, (mavlinkTarget in Compile).value).map(_.getAbsoluteFile) + } + + override val projectSettings: Seq[Setting[_]] = Seq( + mavlinkTarget in Compile := (sourceManaged in Compile).value, + mavlinkGenerate in Compile := generate.value, + sourceGenerators in Compile += (mavlinkGenerate in Compile).taskValue + ) +} diff --git a/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/mavlink/MavlinkKeys.scala b/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/mavlink/MavlinkKeys.scala new file mode 100644 index 0000000..5cbffd4 --- /dev/null +++ b/project/mavlink-plugin/src/main/scala/com/github/jodersky/sbt/mavlink/MavlinkKeys.scala @@ -0,0 +1,14 @@ +package com.github.jodersky.sbt +package mavlink + +import sbt._ +import sbt.Keys._ +import java.io.File + +object MavlinkKeys { + + lazy val mavlinkDialect = settingKey[File]("Dialect definition from which to generate files.") + lazy val mavlinkTarget = settingKey[File]("Target directory to contain all generated mavlink files.") + lazy val mavlinkGenerate = taskKey[Seq[File]]("Generate mavlink files.") + +}
\ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt deleted file mode 100644 index 2c768c0..0000000 --- a/project/plugins.sbt +++ /dev/null @@ -1,7 +0,0 @@ -resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" - -// The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.4") - -// Scala.js plugin -addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5")
\ No newline at end of file diff --git a/project/project/BuildBuild.scala b/project/project/BuildBuild.scala new file mode 100644 index 0000000..201507e --- /dev/null +++ b/project/project/BuildBuild.scala @@ -0,0 +1,35 @@ +import sbt._ +import sbt.Keys._ +import play.twirl.sbt.SbtTwirl +import play.twirl.sbt.Import._ + +object BuildBuild extends Build { + + lazy val root = ( + Project("root", file(".")) + settings( + addSbtPlugin("com.typesafe.play" %% "sbt-plugin" % "2.3.4"), + addSbtPlugin("org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5") + ) + dependsOn(mavlinkPlugin) + ) + + lazy val mavlinkLibrary = ( + Project("mavlink-library", file("mavlink-library")) + enablePlugins(SbtTwirl) + settings( + libraryDependencies += "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.2", + TwirlKeys.templateImports += "com.github.jodersky.mavlink._" + ) + ) + + lazy val mavlinkPlugin = ( + Project("mavlink-plugin", file("mavlink-plugin")) + settings( + sbtPlugin := true + ) + dependsOn(mavlinkLibrary) + ) + +} + diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt new file mode 100644 index 0000000..7b458b6 --- /dev/null +++ b/project/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
\ No newline at end of file diff --git a/vfd-frontend/src/main/scala/org/mavlink/js.scala b/vfd-frontend/src/main/scala/org/mavlink/js.scala deleted file mode 100644 index b9bcd2b..0000000 --- a/vfd-frontend/src/main/scala/org/mavlink/js.scala +++ /dev/null @@ -1,69 +0,0 @@ -package org.mavlink - -import scala.language.implicitConversions -import scala.scalajs.js.JSConverters.array2JSRichGenTrav -import scala.scalajs.js.typedarray.DataView -import scala.scalajs.js.typedarray.Int8Array - -import org.mavlink.messages.PayloadReader -import org.mavlink.messages.PayloadWriter - -package object messages { - import org.mavlink.messages.PayloadReader - import org.mavlink.messages.PayloadWriter - - implicit def mkReader(s: Seq[Byte]) = new BufferedPayloadReader(s.toArray) - implicit def mkWriter(a: Array[Byte]) = new BufferedPayloadWriter(a) - -} - -package messages { - - class BufferedPayloadReader(payload: Array[Byte]) extends PayloadReader { - private val buffer = new Int8Array(payload.toJSArray) - private val view = new DataView(buffer.buffer) - private var pos = 0 - - def int8 = { - val r = view.getInt8(pos) - pos += 1 - r - } - def int16 = { - val r = view.getInt16(pos, true) - pos += 2 - r - } - def int32 = { - val r = view.getInt32(pos, true) - pos += 4 - r - } - def int64 = { - val l = int32 - val m = int32 - (m.asInstanceOf[Long] << 32) | l.asInstanceOf[Long] - } - def float = { - val r = view.getFloat32(pos, true) - pos += 4 - r - } - def double = { - val r = view.getFloat64(pos, true) - pos += 8 - r - } - - } - - class BufferedPayloadWriter(payload: Array[Byte]) extends PayloadWriter { - def int8(x: Byte) = ??? - def int16(x: Short) = ??? - def int32(x: Int) = ??? - def int64(x: Long) = ??? - def float(x: Float) = ??? - def double(x: Double) = ??? - } -} - diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala index d9f42c4..5c22a4b 100644 --- a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala +++ b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala @@ -1,7 +1,6 @@ package vfd.frontend import org.mavlink.messages.Message - import rx.ops.RxOps import scalatags.JsDom.all.ExtendedString import scalatags.JsDom.all.button @@ -17,6 +16,7 @@ import scalatags.JsDom.all.stringStyle import scalatags.JsDom.all.width import vfd.frontend.ui.panels import vfd.frontend.util.Environment +import rx.core.Obs object Main { @@ -29,6 +29,10 @@ object Main { val message = socket.packet.map { p => Message.unpack(socket.packet().messageId, socket.packet().payload) } + + Obs(message) { + println(message().toString()) + } val element = div(`class` := "container-fluid")( div(`class` := "row")( diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala index ab58263..738a84a 100644 --- a/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala +++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala @@ -1,8 +1,6 @@ package vfd.frontend.ui.panels -import org.mavlink.messages.Attitude import org.mavlink.messages.Message -import org.mavlink.messages.Pressure import rx.core.Obs import rx.core.Rx @@ -20,10 +18,6 @@ object Primary { Obs(message) { message() match { - case Attitude(time, roll, pitch, h) => - pitchRoll() = (pitch, roll) - heading() = h - case Pressure(time, p, _, t) => altitude() = p case _ => () } } diff --git a/vfd-mavlink/.classpath b/vfd-mavlink/.classpath deleted file mode 100644 index d097af6..0000000 --- a/vfd-mavlink/.classpath +++ /dev/null @@ -1,9 +0,0 @@ -<classpath> - <classpathentry kind="src" path="src/main/scala" output="target/scala-2.10/classes"/> - <classpathentry kind="src" path="src/main/java" output="target/scala-2.10/classes"/> - <classpathentry kind="src" path="src/test/scala" output="target/scala-2.10/test-classes"/> - <classpathentry kind="src" path="src/test/java" output="target/scala-2.10/test-classes"/> - <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="output" path="bin"/> -</classpath>
\ No newline at end of file diff --git a/vfd-mavlink/.project b/vfd-mavlink/.project deleted file mode 100644 index c05ef5d..0000000 --- a/vfd-mavlink/.project +++ /dev/null @@ -1,13 +0,0 @@ -<projectDescription> - <name>vfd-mavlink</name> - <buildSpec> - <buildCommand> - <name>org.scala-ide.sdt.core.scalabuilder</name> - </buildCommand> - </buildSpec> - <natures> - <nature>org.scala-ide.sdt.core.scalanature</nature> - <nature>org.eclipse.jdt.core.javanature</nature> - </natures> - <linkedResources> </linkedResources> -</projectDescription>
\ 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 deleted file mode 100644 index 59f91eb..0000000 --- a/vfd-mavlink/src/main/scala/org/mavlink/Packet.scala +++ /dev/null @@ -1,75 +0,0 @@ -package org.mavlink - -case class Packet( - seq: Byte, - systemId: Byte, - componentId: Byte, - messageId: Byte, - payload: Seq[Byte] -) { - - lazy val crc = { - var c = new Crc() - c = c.next(payload.length.toByte) - c = c.next(seq) - c = c.next(systemId) - c = c.next(componentId) - c = c.next(messageId) - for (p <- payload) { - c = c.next(p) - } - c = c.next(Packet.MessageIdCrcEnds(messageId & 0xff)) - c - } - - def toSeq: Seq[Byte] = Seq( - Packet.Stx, - payload.length.toByte, - seq, - systemId, - componentId, - messageId - ) ++ payload ++ Seq( - crc.lsb, - crc.msb - ) - -} - -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, - 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, - 214, 159, 220, 168, 24, 23, 170, 144, 67, 115, - 39, 246, 185, 104, 237, 244, 222, 212, 9, 254, - 230, 28, 28, 132, 221, 232, 11, 153, 41, 39, - 0, 0, 0, 0, 15, 3, 0, 0, 0, 0, - 0, 153, 183, 51, 82, 118, 148, 21, 0, 243, - 124, 0, 0, 38, 20, 158, 152, 143, 0, 0, - 0, 106, 49, 22, 143, 140, 5, 150, 0, 231, - 183, 63, 54, 0, 0, 0, 0, 0, 0, 0, - 175, 102, 158, 208, 56, 93, 211, 108, 32, 185, - 84, 0, 0, 124, 119, 4, 76, 128, 56, 116, - 134, 237, 203, 250, 87, 203, 220, 25, 226, 0, - 29, 223, 85, 6, 229, 203, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 154, 49, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 8, 204, - 49, 170, 44, 83, 46, 0 - ).map(_.toByte) - - final val Empty = Packet(0, 0, 0, -1, Array(0: Byte)) - -}
\ No newline at end of file diff --git a/vfd-mavlink/src/main/scala/org/mavlink/enums/SystemStatus.scala b/vfd-mavlink/src/main/scala/org/mavlink/enums/SystemStatus.scala deleted file mode 100644 index 69d0889..0000000 --- a/vfd-mavlink/src/main/scala/org/mavlink/enums/SystemStatus.scala +++ /dev/null @@ -1,30 +0,0 @@ -package org.mavlink.enums - -object SystemStatus extends Enumeration { - type SystemStatus = Value - - /** Uninitialized system, state is unknown. */ - val Uninitialized = Value(0) - - /** System is booting up. */ - val Booting = Value(1) - - /** System is calibrating and not flight-ready. */ - val Calibrating = Value(2) - - /** System is grounded and ready to fly. */ - val Standby = Value(3) - - /** System is active an might already be airborne. */ - val Active = Value(4) - - /** System is in a non-normal flight mode. It can however still navigate. */ - val Critical = Value(5) - - /** System is in a non-normal flight mode. It lost control over parts or over the whole airframe. It is in mayday and going down. */ - val Emergency = Value(6) - - /** System just initialized its power-down sequence, will shut down now. */ - val Poweroff = Value(7) - -}
\ No newline at end of file diff --git a/vfd-mavlink/src/main/scala/org/mavlink/messages/message.scala b/vfd-mavlink/src/main/scala/org/mavlink/messages/message.scala deleted file mode 100644 index cf8694a..0000000 --- a/vfd-mavlink/src/main/scala/org/mavlink/messages/message.scala +++ /dev/null @@ -1,73 +0,0 @@ -package org.mavlink.messages - -import org.mavlink.Packet - -import org.mavlink.enums.SystemStatus -import org.mavlink.enums.SystemStatus._ - -sealed trait Message -case class Heartbeat(`type`: Byte, autopilot: Byte, baseMode: Byte, customMode: Int, systemStatus: SystemStatus, mavlinkVersion: Byte) extends Message -case class RadioStatus(rssi: Byte, remoteRssi: Byte, txBuf: Byte, noise: Byte, remoteNoise: Byte, rxErrors: Short, fixed: Short) extends Message -case class Attitude(time: Int, roll: Float, pitch: Float, heading: Float) extends Message -case class Pressure(time: Int, pressure: Float, diffPressure: Float, temperature: Short) extends Message -case class Battery(id: Byte, function: Byte, `type`: Byte, temperature: Short, - voltages: Seq[Short], current: Short, currentConsumed: Int, energyConsumed: Int, remaining: Byte) extends Message -case class Unknown(id: Int, payload: Seq[Byte]) extends Message - -object Message { - - def unpack(id: Byte, payload: Seq[Byte])(implicit mkReader: Seq[Byte] => PayloadReader): Message = { - val r = mkReader(payload) - - id & 0xff match { - case 0 => - val cm = r.int32 - Heartbeat(r.int8, r.int8, r.int8, cm, SystemStatus(r.int8), r.int8) - case 29 => - Pressure(r.int32, r.float, r.float, r.int16) - case 30 => - Attitude(r.int32, r.float, r.float, r.float) - case 109 => - val re = r.int16 - val fi = r.int16 - RadioStatus(r.int8, r.int8, r.int8, r.int8, r.int8, re, fi) - case 147 => - val cc = r.int32 - val ec = r.int32 - val t = r.int16 - val v = for (i <- 0 until 10) yield r.int16 - val c = r.int16 - val id = r.int8 - val fct = r.int8 - val tpe = r.int8 - val rm = r.int8 - Battery(id, fct, tpe, t, v, c, cc, ec, rm) - - case u => Unknown(u, payload) - } - } - - def pack(m: Message)(implicit mkWriter: Array[Byte] => PayloadWriter): (Byte, Seq[Byte]) = { - val (id, size) = m match { - case _: Heartbeat => (0, 9) - case _: RadioStatus => (109, 9) - case u: Unknown => (u.id, u.payload.length) - } - - val arr = new Array[Byte](size) - val w = mkWriter(arr) - - m match { - case Heartbeat(tp, a, b, cm, s, mv) => w.int32(cm); w.int8(tp); w.int8(b); w.int8(s.id.toByte); w.int8(mv); - case RadioStatus(r, rr, tx, n, rn, rx, fi) => w.int16(rx); w.int16(fi); w.int8(r); w.int8(rr); w.int8(tx); w.int8(n); w.int8(rn); - case Unknown(_, payload) => for (p <- payload) w.int8(p) - } - - (id.toByte, arr) - - } - - - -} -
\ No newline at end of file diff --git a/vfd-mavlink/src/main/scala/org/mavlink/messages/payload.scala b/vfd-mavlink/src/main/scala/org/mavlink/messages/payload.scala deleted file mode 100644 index 76076b2..0000000 --- a/vfd-mavlink/src/main/scala/org/mavlink/messages/payload.scala +++ /dev/null @@ -1,19 +0,0 @@ -package org.mavlink.messages - -trait PayloadReader { - def int8: Byte - def int16: Short - def int32: Int - def int64: Long - def float: Float - def double: Double -} - -trait PayloadWriter { - def int8(x: Byte) - def int16(x: Short) - def int32(x: Int) - def int64(x: Long) - def float(x: Float) - def double(x: Double) -}
\ No newline at end of file diff --git a/vfd-uav/src/main/scala/org/mavlink/jvm.scala b/vfd-uav/src/main/scala/org/mavlink/jvm.scala deleted file mode 100644 index ac711be..0000000 --- a/vfd-uav/src/main/scala/org/mavlink/jvm.scala +++ /dev/null @@ -1,48 +0,0 @@ -package org.mavlink - -import scala.language.implicitConversions - -import java.nio.ByteBuffer -import java.nio.ByteOrder - -package object messages { - import org.mavlink.messages.PayloadReader - import org.mavlink.messages.PayloadWriter - - implicit def mkReader(s: Seq[Byte]) = new BufferedPayloadReader(s.toArray) - implicit def mkWriter(a: Array[Byte]) = new BufferedPayloadWriter(a) - -} - -package messages { - - class BufferedPayloadReader(payload: Array[Byte]) extends PayloadReader { - private val buffer = ByteBuffer.wrap(payload) - - //mavlink uses little endian - buffer.order(ByteOrder.LITTLE_ENDIAN) - - def int8 = buffer.get() - def int16 = buffer.getShort() - def int32 = buffer.getInt() - def int64 = buffer.getLong() - def float = buffer.getFloat() - def double = buffer.getDouble() - - } - - class BufferedPayloadWriter(payload: Array[Byte]) extends PayloadWriter { - private val buffer = ByteBuffer.wrap(payload) - - //mavlink uses little endian - buffer.order(ByteOrder.LITTLE_ENDIAN) - - def int8(x: Byte) = buffer.put(x) - def int16(x: Short) = buffer.putShort(x) - def int32(x: Int) = buffer.putInt(x) - def int64(x: Long) = buffer.putLong(x) - def float(x: Float) = buffer.putFloat(x) - def double(x: Double) = buffer.putDouble(x) - } -} - diff --git a/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala b/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala index 7fe9d23..857169e 100644 --- a/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala +++ b/vfd-uav/src/main/scala/vfd/uav/SerialConnection.scala @@ -1,21 +1,24 @@ package vfd.uav import java.util.concurrent.TimeUnit.MILLISECONDS + import scala.concurrent.duration.FiniteDuration + import org.mavlink.Parser import org.mavlink.messages.Message + import com.github.jodersky.flow.Parity import com.github.jodersky.flow.Serial import com.github.jodersky.flow.SerialSettings + import akka.actor.Actor +import akka.actor.ActorLogging import akka.actor.ActorRef import akka.actor.Props import akka.actor.Terminated import akka.actor.actorRef2Scala import akka.io.IO import akka.util.ByteString -import akka.actor.ActorLogging -import org.mavlink.messages.Battery class SerialConnection(id: Byte, heartbeat: Option[FiniteDuration], port: String, settings: SerialSettings) extends Actor with ActorLogging with Connection { import context._ @@ -68,14 +71,8 @@ class SerialConnection(id: Byte, heartbeat: Option[FiniteDuration], port: String val last = new collection.mutable.Queue[Int] - val parser = new Parser(pckt => - Message.unpack(pckt.messageId, pckt.payload) match { - case b: Battery => - last enqueue b.voltages(0) - if (last.size > 20) last.dequeue - println("batt: " + last.sum / last.size) - case _ => ()//println(pckt.messageId) - } + val parser = new Parser(pckt => + println("Received message: " + Message.unpack(pckt.messageId, pckt.payload)) ) |