aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-03-19 16:08:46 +0100
committerJakob Odersky <jodersky@gmail.com>2015-03-19 16:08:46 +0100
commit1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6 (patch)
tree287a8e4ce18d3a8c299d7b2a91599a7a48c7b59d
downloadsbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.tar.gz
sbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.tar.bz2
sbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.zip
initial commit
-rw-r--r--.gitignore29
-rw-r--r--README.md3
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala6
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala35
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala45
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala3
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala110
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala16
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala44
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/Assembler.scala.txt19
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt28
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/MavlinkBuffer.scala.txt24
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt59
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt157
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt5
-rw-r--r--mavlink-library/src/main/twirl/org/mavlink/messages/messages.scala.txt120
-rw-r--r--mavlink-library/src/test/resources/concise.xml115
-rw-r--r--mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala16
-rw-r--r--mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/MavlinkKeys.scala14
-rw-r--r--mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala51
-rw-r--r--project/Build.scala42
-rw-r--r--project/plugins.sbt1
22 files changed, 942 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e38fe35
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# sbt
+.cache
+.history/
+.lib/
+dist/*
+target/
+lib_managed/
+src_managed/
+project/boot/
+project/plugins/project/
+
+# scala-ide specific
+/.settings
+/.scala_dependencies
+/.project
+/.classpath
+/.cache
+/.history
+
+# ensime
+.ensime
+
+# general files
+/*.jar
+*.swp
+*.class
+*.log
+*~
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb894af
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# SBT-MAVLink Plugin
+
+This plugin aims to provide generation of Scala sources from MAVLink message definition files. \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala
new file mode 100644
index 0000000..d025285
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala
@@ -0,0 +1,6 @@
+package com.github.jodersky.mavlink
+
+/** Represents the context under which MAVLink scala code was generated. */
+case class Context(
+ version: String
+) \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala
new file mode 100644
index 0000000..6150e48
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala
@@ -0,0 +1,35 @@
+package com.github.jodersky.mavlink
+
+/**
+ * X.25 CRC calculation for MAVlink messages. The checksum must be initialized,
+ * updated with each field of a message, and then finished with the message
+ * id.
+ */
+case class Crc(val crc: Int = 0xffff) extends AnyVal {
+
+ /**
+ * Accumulates data into a new checksum.
+ */
+ 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
+ }
+
+ /** Least significant byte of checksum. */
+ def lsb: Byte = crc.toByte
+
+ /** Most significant byte of checksum. */
+ def msb: Byte = (crc >> 8).toByte
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala
new file mode 100644
index 0000000..06a4909
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala
@@ -0,0 +1,45 @@
+package com.github.jodersky.mavlink
+
+import scala.xml.XML
+import java.io.FileWriter
+import java.io.BufferedWriter
+import scalax.file.Path
+import java.io.File
+
+import trees._
+
+/**
+ * Generates Scala code implementing the MAVLink protocol.
+ * @param dialect a specific MAVLink dialect for which to generate code
+ */
+class Generator(dialect: Dialect) {
+
+ lazy val maxPayloadLength = {
+ val widths = dialect.messages map { msg =>
+ msg.fields.map(_.tpe.sizeof).sum
+ }
+ widths.max
+ }
+
+ lazy val extraCrcs = Array.tabulate[Byte](255){i =>
+ val message = dialect.messages.find(_.id == i)
+ message.map(_.checksum).getOrElse(0)
+ }
+
+ /**
+ * Generates Scala code implementing MAVLink.
+ * @return a list containing proposed Scala file names pointing to their contents
+ */
+ def generate(): List[(String, String)] = {
+ val context = Context(dialect.version)
+
+ List(
+ "org/mavlink/Assembler.scala" -> org.mavlink.txt.Assembler(context).body,
+ "org/mavlink/Crc.scala" -> org.mavlink.txt.Crc(context).body,
+ "org/mavlink/MavlinkBuffer.scala" -> org.mavlink.txt.MavlinkBuffer(context).body,
+ "org/mavlink/Packet.scala" -> org.mavlink.txt.Packet(context, maxPayloadLength, extraCrcs).body,
+ "org/mavlink/Parser.scala" -> org.mavlink.txt.Parser(context).body,
+ "org/mavlink/messages/messages.scala" -> org.mavlink.messages.txt.messages(context, dialect.messages).body
+ )
+ }
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala
new file mode 100644
index 0000000..e224f39
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala
@@ -0,0 +1,3 @@
+package com.github.jodersky.mavlink
+
+class ParseError(message: String) extends RuntimeException(message) \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala
new file mode 100644
index 0000000..e2dda42
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala
@@ -0,0 +1,110 @@
+package com.github.jodersky.mavlink
+
+import scala.language.postfixOps
+
+import scala.xml.Node
+import scala.util.Try
+
+import trees._
+
+/**
+ * Provides means to parse a MAVLink dialect definition into a
+ * scala object representation.
+ */
+object Parser {
+
+ def fatal(error: String, node: Node) = throw new ParseError("Parse error at " + node + ": " + error)
+ def warn(warning: String, node: Node) = Console.err.println("Warning " + node + ": " + warning)
+
+ def parseDialect(node: Node): Dialect = parse(node) match {
+ case p: Dialect => p
+ case _ => fatal("expected mavlink protocol definition", node)
+ }
+
+ def parse(node: Node): Tree = node match {
+ case <field>{_*}</field> =>
+ val description = node.text
+ val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for field", node)
+ val enum = (node \ "@enum").map(_.text).headOption
+ val (tpe, native) = (node \ "@type") map (_.text) headOption match {
+ case Some(t) => (parseType(t, node), t)
+ case None => fatal("no field type specified", node)
+ }
+ Field(tpe, native, name, enum, description)
+
+ case <entry>{_*}</entry> =>
+ val value = (node \ "@value").map(_.text).headOption map { str =>
+ Try { Integer.parseInt(str) } getOrElse fatal("value must be an integer", node)
+ } getOrElse fatal("no value defined", node)
+ val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum entry", node)
+ val description = (node \ "description").text
+ EnumEntry(value, name, description)
+
+ case <enum>{_*}</enum> =>
+ val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum", node)
+ val entries = (node \ "entry").toSet map { n: Node =>
+ parse(n) match {
+ case e: EnumEntry => e
+ case _ => fatal("illegal definition in enum, only entries are allowed", n)
+ }
+ }
+ Enum(name, entries)
+
+ case <message>{_*}</message> =>
+ val id = (node \ "@id").map(_.text).headOption map { str =>
+ val id = Try { Integer.parseInt(str) } getOrElse fatal("id must be an integer", node)
+ if (id < 0 || id > 255) warn("message id is not in the range [0-255]", node)
+ id.toByte
+ } getOrElse fatal("no id defined", node)
+ val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for message", node)
+ val description = (node \ "description").text
+
+ val fields = (node \ "field") map { n: Node =>
+ parse(n) match {
+ case e: Field => e
+ case _ => fatal("illegal definition in message, only fields are allowed", n)
+ }
+ }
+ Message(id, name, description, fields)
+
+ case <mavlink>{_*}</mavlink> =>
+ val version = (node \ "version").text
+
+ val enums = (node \ "enums" \ "_").toSet map { n: Node =>
+ parse(n) match {
+ case e: Enum => e
+ case _ => fatal("illegal definition in enums, only enum declarations are allowed", n)
+ }
+ }
+
+ val messages = (node \ "messages" \ "_").toSet map { n: Node =>
+ parse(n) match {
+ case e: Message => e
+ case e => fatal("illegal definition in messages, only message declarations are allowed", n)
+ }
+ }
+ Dialect(version, enums, messages)
+
+ case x => fatal("unknown", x)
+
+ }
+
+ val ArrayPattern = """(.*)\[(\d+)\]""".r
+ def parseType(typeStr: String, node: Node): Type = typeStr match {
+ case "int8_t" => IntType(1, true)
+ case "int16_t" => IntType(2, true)
+ case "int32_t" => IntType(4, true)
+ case "int64_t" => IntType(8, true)
+ case "uint8_t" => IntType(1, false)
+ case "uint16_t" => IntType(2, false)
+ case "uint32_t" => IntType(4, false)
+ case "uint64_t" => IntType(8, false)
+ case "float" => FloatType(4)
+ case "double" => FloatType(8)
+ case "char" => IntType(1, true)
+ case ArrayPattern("char", l) => StringType(l.toInt)
+ case ArrayPattern(u, l) => ArrayType(parseType(u, node), l.toInt)
+ case unknown => fatal("unknown field type " + unknown, node)
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala
new file mode 100644
index 0000000..38b87e7
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala
@@ -0,0 +1,16 @@
+package com.github.jodersky.mavlink
+
+object StringUtils {
+
+ def camelify(str: String) = {
+ val lower = str.toLowerCase
+ "_([a-z\\d])".r.replaceAllIn(lower, {m => m.group(1).toUpperCase()})
+ }
+
+ def Camelify(str: String) = {
+ val camel = camelify(str)
+ val (head, tail) = camel.splitAt(1)
+ head.toUpperCase + tail
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala
new file mode 100644
index 0000000..49b697b
--- /dev/null
+++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala
@@ -0,0 +1,44 @@
+package com.github.jodersky.mavlink
+
+package trees {
+
+ sealed trait Tree
+
+ case class Dialect(version: String, enums: Set[Enum], messages: Set[Message]) extends Tree
+ case class Enum(name: String, entries: Set[EnumEntry]) extends Tree
+ case class EnumEntry(value: Int, name: String, description: String) extends Tree
+ case class Field(tpe: Type, nativeType: String, name: String, enum: Option[String], description: String) extends Tree
+ case class Message(id: Byte, name: String, description: String, fields: Seq[Field]) extends Tree {
+ def orderedFields = fields.toSeq.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.nativeType + " ").getBytes)
+ c = c.accumulate((field.name + " ").getBytes)
+ }
+ (c.lsb ^ c.msb).toByte
+ }
+ }
+
+ trait Type extends Tree {
+ def width: Int // width in bytes of the type
+ def sizeof: Int = width // size of bytes of the type
+ }
+
+ case class IntType(width: Int, signed: Boolean) extends Type
+ case class FloatType(width: Int) extends Type
+ case class ArrayType(underlying: Type, length: Int) extends Type {
+ def width = underlying.width
+ override def sizeof = width * length
+ }
+ case object CharType extends Type {
+ def width = 1
+ }
+ case class StringType(maxLength: Int) extends Type {
+ val width = 1
+ override def sizeof = width * maxLength
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/Assembler.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/Assembler.scala.txt
new file mode 100644
index 0000000..e3571e6
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/Assembler.scala.txt
@@ -0,0 +1,19 @@
+@(__context: Context)@_header(__context)
+package org.mavlink
+
+import java.nio.ByteBuffer
+
+/**
+ * Utility class for assembling packets with increasing sequence number
+ * originating from given system and component IDs.
+ */
+class Assembler(systemId: Byte, componentId: Byte) {
+ private var seq = 0;
+
+ /** Assemble a given message ID and payload into a packet. */
+ def assemble(messageId: Byte, payload: ByteBuffer): Packet = {
+ val p = Packet(seq.toByte, systemId, componentId, messageId, payload)
+ seq += 1
+ p
+ }
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt
new file mode 100644
index 0000000..f6e3cce
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt
@@ -0,0 +1,28 @@
+@(__context: Context)@_header(__context)
+package org.mavlink
+
+/**
+ * X.25 CRC calculation for MAVlink messages. The checksum must be initialized,
+ * updated with each field of a message, and then finished with the message
+ * id.
+ */
+case class Crc(val crc: Int = 0xffff) extends AnyVal {
+
+ /**
+ * Accumulates data into a new checksum.
+ */
+ 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))
+ }
+
+ /** Least significant byte of checksum. */
+ def lsb: Byte = crc.toByte
+
+ /** Most significant byte of checksum. */
+ def msb: Byte = (crc >> 8).toByte
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/MavlinkBuffer.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/MavlinkBuffer.scala.txt
new file mode 100644
index 0000000..9a01dbe
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/MavlinkBuffer.scala.txt
@@ -0,0 +1,24 @@
+@(__context: Context)@_header(__context)
+package org.mavlink
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+/** Utility functions for using ByteBuffers. */
+class MavlinkBuffer {
+
+ /**
+ * Allocates a ByteBuffer for using in MAVLink message processing.
+ * @@param direct specifies if a direct buffer should be used
+ */
+ def allocate(direct: Boolean = true) = {
+ val buffer = if (direct) {
+ ByteBuffer.allocateDirect(Packet.MaxPayloadLength)
+ } else {
+ val underlying = new Array[Byte](Packet.MaxPayloadLength)
+ ByteBuffer.wrap(underlying)
+ }
+ buffer.order(ByteOrder.LITTLE_ENDIAN) // MAVLink uses little endian
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt
new file mode 100644
index 0000000..a0d11a5
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/Packet.scala.txt
@@ -0,0 +1,59 @@
+@(__context: Context, __maxPayloadLength: Int, __extraCrcs: Seq[Byte])@_header(__context)
+package org.mavlink
+
+import java.nio.ByteBuffer
+
+/**
+ * Represents a MAVLink packet.
+ * Note that due to performance reasons, the packet's payload contents are mutable.
+ * @@param seq sequence number
+ * @@param systemId system ID of sender
+ * @@param componentId component ID of sender
+ * @@param messageId MAVLink message identification (this depends on the dialect used)
+ * @@param payload message contents
+ */
+case class Packet(
+ seq: Byte,
+ systemId: Byte,
+ componentId: Byte,
+ messageId: Byte,
+ payload: ByteBuffer
+) {
+
+ /*
+ def 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)
+ while (payload.)
+ for (p <- payload) {
+ c = c.accumulate(p)
+ }
+ c = c.accumulate(Packet.extraCrc(messageId))
+ c
+ }*/
+}
+
+object Packet {
+
+ /** Start byte signaling the beginning of a packet. */
+ final val Stx: Byte = (0xfe).toByte
+
+ /** Maximum length of a payload contained in a packet. */
+ final val MaxPayloadLength: Int = @__maxPayloadLength
+
+ /** Additional CRCs indexed by message ID (see MAVLink specification). */
+ final val ExtraCrcs: Seq[Byte] = Array[Byte](
+ @__extraCrcs.map(_ formatted "%3d").grouped(10).map(_.mkString(",")).mkString(",\n ")
+ )
+
+ /** Utility function to index ExtraCrcs with a byte. */
+ def extraCrc(id: Byte) = ExtraCrcs(id & 0xff)
+
+ /** An invalid packet with no payload. */
+ def emoty = Packet(0, 0, 0, -128, ByteBuffer.wrap(Array(0: Byte)))
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt
new file mode 100644
index 0000000..ca04ae1
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt
@@ -0,0 +1,157 @@
+@(__context: Context)@_header(__context)
+package org.mavlink
+
+import java.nio.ByteBuffer
+
+object Parser {
+
+ /** Internal parser states. */
+ object States {
+ sealed trait State
+ case object Idle extends State
+ case object GotStx extends State
+ case object GotLength extends State
+ case object GotSeq extends State
+ case object GotSysId extends State
+ case object GotCompId extends State
+ case object GotMsgId extends State
+ case object GotCrc1 extends State
+ case object GotPayload extends State
+ }
+
+ /** Errors that may occur while receiving data. */
+ object Errors {
+ sealed trait Error
+ case object CrcError extends Error
+ case object OverflowError extends Error
+ }
+}
+
+/**
+ * A parser to divide byte streams into mavlink packets.
+ * A parser is created with receiver and error functions and bytes are then fed into it. Once
+ * a valid packet has been received (or an error encountered), the receiver (or error) functions
+ * are called.
+ * Note that due to memory and performance issues, a received packet and payload is
+ * only guaranteed to be valid within the receiver function. After exiting the function, the
+ * underlying packet's data may be overwritten by a new packet.
+ *
+ * @@param buffer a buffer into which the received payload is stored
+ * @@param receiver called when a valid packet has been received
+ * @@param error called when invalid data was received
+ */
+class Parser(payload: ByteBuffer, receiver: Packet => Unit, error: Parser.Errors.Error => Unit = _ => ()) {
+ import Parser._
+
+ private var state: States.State = States.Idle
+
+ private object inbound {
+ var length: Int = 0
+ var seq: Byte = 0
+ var systemId: Byte = 0
+ var componentId: Byte = 0
+ var messageId: Byte = 0
+ var crc: Crc = new Crc()
+ }
+
+ /**
+ * Parses a byte as part of an incoming MAVLink message. May result
+ * in calling receiver or error function.
+ */
+ def push(c: Byte): Unit = {
+ import States._
+
+ state match {
+ case Idle =>
+ if (c == Packet.Stx) {
+ state = GotStx
+ }
+
+ case GotStx =>
+ inbound.crc = new Crc()
+ inbound.length = (c & 0xff)
+ inbound.crc = inbound.crc.accumulate(c)
+ state = GotLength
+
+ case GotLength =>
+ inbound.seq = c;
+ inbound.crc = inbound.crc.accumulate(c)
+ state = GotSeq
+
+ case GotSeq =>
+ inbound.systemId = c
+ inbound.crc = inbound.crc.accumulate(c)
+ state = GotSysId
+
+ case GotSysId =>
+ inbound.componentId = c
+ inbound.crc = inbound.crc.accumulate(c)
+ state = GotCompId
+
+ case GotCompId =>
+ inbound.messageId = c
+ inbound.crc = inbound.crc.accumulate(c)
+ if (inbound.length == 0) {
+ state = GotPayload
+ } else {
+ state = GotMsgId
+ payload.clear()
+ }
+
+ case GotMsgId =>
+ if (!payload.hasRemaining) {
+ state = Idle
+ error(Errors.OverflowError)
+ } else {
+ payload.put(c)
+ inbound.crc = inbound.crc.accumulate(c)
+ if (payload.position >= inbound.length) {
+ state = GotPayload
+ }
+ }
+
+ case GotPayload =>
+ inbound.crc = inbound.crc.accumulate(Packet.extraCrc(inbound.messageId))
+ if (c != inbound.crc.lsb) {
+ state = Idle
+ if (c == Packet.Stx) {
+ state = GotStx
+ }
+ error(Errors.CrcError)
+ } else {
+ state = GotCrc1
+ }
+
+ case GotCrc1 =>
+ if (c != inbound.crc.msb) {
+ state = Idle
+ if (c == Packet.Stx) {
+ state = GotStx
+ }
+ error(Errors.CrcError)
+ } else {
+ val packet = Packet(
+ inbound.seq,
+ inbound.systemId,
+ inbound.componentId,
+ inbound.messageId,
+ payload)
+ state = Idle
+ receiver(packet)
+ }
+ }
+ }
+
+ /**
+ * Parses a sequence of bytes.
+ */
+ def push(bytes: Traversable[Byte]): Unit = for (b <- bytes) push(b)
+
+ /**
+ * Parses all bytes of contained in a byte buffer.
+ */
+ def push(bytes: ByteBuffer): Unit = while(bytes.hasRemaining) {
+ push(bytes.get())
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt
new file mode 100644
index 0000000..3fd5da9
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/_header.scala.txt
@@ -0,0 +1,5 @@
+@(__context: Context)/*
+ * MAVLink Abstraction Layer for Scala.
+ *
+ * This file was automatically generated.
+ */ \ No newline at end of file
diff --git a/mavlink-library/src/main/twirl/org/mavlink/messages/messages.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/messages/messages.scala.txt
new file mode 100644
index 0000000..6f444dc
--- /dev/null
+++ b/mavlink-library/src/main/twirl/org/mavlink/messages/messages.scala.txt
@@ -0,0 +1,120 @@
+@(__context: Context, __messages: Set[Message])@org.mavlink.txt._header(__context)
+package org.mavlink.messages
+
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+
+@__scalaMessageType(message: Message) = {@StringUtils.Camelify(message.name)}
+
+@__scalaFieldName(field: Field) = {@StringUtils.camelify(field.name)}
+
+@__scalaFieldType(tpe: Type) = {@tpe match {
+ case IntType(1, _) => {Byte}
+ case IntType(2, _) => {Short}
+ case IntType(4, _) => {Int}
+ case IntType(8, _) => {Long}
+ case FloatType(4) => {Float}
+ case FloatType(8) => {Double}
+ case StringType(_) => {String}
+ case ArrayType(underlying, _) => {Seq[@__scalaFieldType(underlying)]}
+}}
+@__scalaFieldFormal(field: Field) = {@__scalaFieldName(field): @__scalaFieldType(field.tpe)}
+
+@__bufferReadMethod(buffer: String, tpe: Type) = {@tpe match {
+ case IntType(1, _) => {@{buffer}.get()}
+ case IntType(2, _) => {@{buffer}.getShort()}
+ case IntType(4, _) => {@{buffer}.getInt()}
+ case IntType(8, _) => {@{buffer}.getLong()}
+ case FloatType(4) => {@{buffer}.getFloat()}
+ case FloatType(8) => {@{buffer}.getDouble()}
+ case StringType(maxLength) =>{{
+ val bytes = Array[Byte](@maxLength)
+ @{buffer}.get(bytes, 0, @maxLength)
+ val length = bytes.indexOf(0) match {
+ case -1 => @maxLength
+ case i => i
+ }
+ new String(bytes, 0, length, Charset.forName("UTF-8"))
+ }}
+ case ArrayType(underlying, length) => {for (i <- 0 until @length) yield {@__bufferReadMethod(buffer, underlying)}}
+}}
+@__bufferWriteMethod(buffer: String, data: String, tpe: Type) = {@tpe match {
+ case IntType(1, _) => {@{buffer}.put(@data)}
+ case IntType(2, _) => {@{buffer}.putShort(@data)}
+ case IntType(4, _) => {@{buffer}.putInt(@data)}
+ case IntType(8, _) => {@{buffer}.putLong(@data)}
+ case FloatType(4) => {@{buffer}.putFloat(@data)}
+ case FloatType(8) => {@{buffer}.putDouble(@data)}
+ case StringType(maxLength) => {
+ {
+ val bytes = @{data}.getBytes(Charset.forName("UTF-8"))
+ val endPosition = @{buffer}.position + @maxLength
+ @{buffer}.put(bytes, 0, math.max(bytes.length, @maxLength))
+ while (@{buffer}.position < endPosition) {
+ @{buffer}.put(0: Byte)
+ }
+ }}
+ case ArrayType(underlying, length) => {for (i <- 0 until @length) {@__bufferWriteMethod(buffer, data + "(i)", underlying)}}
+}}
+
+@__commentParagraphs(paragraphs: Seq[String]) = {@paragraphs.mkString("/**\n * ", "\n * ", "\n */")}
+@__comment(message: Message) = {@__commentParagraphs(message.description.grouped(100).toList ++ message.fields.map(field => "@param " + field.name + " " + field.description))}
+
+/** Marker trait for MAVLink messages. All supported messages extend this trait. */
+sealed trait Message
+
+@for(__msg <- __messages) {
+@__comment(__msg)
+case class @{__scalaMessageType(__msg)}(@__msg.fields.map(__scalaFieldFormal).mkString(", ")) extends Message
+}
+
+/**
+ * Wraps an unknown message.
+ * @@param id the ID of the message
+ * @@param payload the message's contents
+ */
+case class Unknown(id: Byte, payload: ByteBuffer) extends Message
+
+/**
+ * Provides utility methods for converting data to and from MAVLink messages.
+ */
+object Message {
+
+ /**
+ * Interprets an ID and payload as a message. The contents must be ordered
+ * according to the MAVLink specification.
+ * Note: this method reads from the provided ByteBuffer and thereby modifies its
+ * internal state.
+ * @@param id ID of the message
+ * @@param payload contents of the message
+ * @@return payload interpreted as a message or 'Unknown' in case of an unknown ID
+ */
+ def unpack(id: Byte, payload: ByteBuffer): Message = id match {
+ @for(__msg <- __messages) {
+ case @__msg.id =>
+ @for(__field <- __msg.orderedFields) {val @__scalaFieldFormal(__field) = @__bufferReadMethod("payload", __field.tpe)
+ }
+ @{__scalaMessageType(__msg)}(@__msg.fields.map(__scalaFieldName(_)).mkString(", "))
+ }
+ case u => Unknown(u, payload)
+ }
+
+ /**
+ * Writes the contents of a message to a ByteBuffer. The message is encoded according
+ * to the MAVLink specification.
+ * @@param message the message to write
+ * @@param payload buffer that will hold the encoded message
+ * @@return id of the encoded message
+ */
+ def pack(message: Message, payload: ByteBuffer): Byte = message match {
+ @for(__msg <- __messages) {
+ case m: @__scalaMessageType(__msg) =>
+ @for(__field <- __msg.orderedFields) {@__bufferWriteMethod("payload", "m." + __scalaFieldName(__field), __field.tpe)
+ }
+ @__msg.id
+ }
+ case u: Unknown =>
+ payload.put(u.payload)
+ u.id
+ }
+} \ No newline at end of file
diff --git a/mavlink-library/src/test/resources/concise.xml b/mavlink-library/src/test/resources/concise.xml
new file mode 100644
index 0000000..fcbdc35
--- /dev/null
+++ b/mavlink-library/src/test/resources/concise.xml
@@ -0,0 +1,115 @@
+<?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 name="MOTOR" id="6">
+ <description>Status of motors</description>
+ <field type="uint8_t" name="m0">m0</field>
+ <field type="uint8_t" name="m1">m1</field>
+ <field type="uint8_t" name="m2">m2</field>
+ <field type="uint8_t" name="m3">m3</field>
+ </message>
+ <message id="30" name="ATTITUDE">
+ <description>The attitude in the aeronautical frame (right-handed, Z-down, X-front, Y-right).</description>
+ <field type="int16_t" name="roll">Roll angle</field>
+ <field type="int16_t" name="pitch">Pitch angle</field>
+ <field type="uint16_t" name="yaw">Yaw angle</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>
+ <message name="TEST_MESSAGE" id="110">
+ <description>Test</description>
+ <field type="uint8_t[2]" name="bytearray">a byte array</field>
+ <field type="float[20]" name="floatarray">a float array</field>
+ <field type="char" name="chars">a char</field>
+ <field type="double" name="doubles">a double</field>
+ <field type="char[20]" name="strings">a string</field>
+ </message>
+ </messages>
+</mavlink>
diff --git a/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala b/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala
new file mode 100644
index 0000000..faf252a
--- /dev/null
+++ b/mavlink-library/src/test/scala/com/github/jodersky/mavlink/MainTest.scala
@@ -0,0 +1,16 @@
+package com.github.jodersky.mavlink
+
+import scala.io.Source
+import scala.xml.XML
+import trees._
+
+object MainTest {
+
+ def main(args: Array[String]): Unit = {
+ val definition = XML.load(getClass.getResource("/concise.xml"))
+ val dialect = Parser.parseDialect(definition)
+ val generator = new Generator(dialect)
+ println(generator.generate())
+ }
+
+} \ No newline at end of file
diff --git a/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/MavlinkKeys.scala b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/MavlinkKeys.scala
new file mode 100644
index 0000000..e09a623
--- /dev/null
+++ b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/MavlinkKeys.scala
@@ -0,0 +1,14 @@
+package com.github.jodersky.mavlink.sbt
+
+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/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala
new file mode 100644
index 0000000..41a6bcf
--- /dev/null
+++ b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala
@@ -0,0 +1,51 @@
+package com.github.jodersky.mavlink.sbt
+
+import com.github.jodersky.mavlink.Parser
+import com.github.jodersky.mavlink.Generator
+import scala.xml.XML
+
+import MavlinkKeys._
+import sbt._
+import sbt.Keys._
+import sbt.plugins._
+
+object SbtMavlink extends AutoPlugin {
+
+ override def trigger = allRequirements
+
+ override def requires = JvmPlugin //this is required as sourceGenerators are otherwise reset
+
+ override lazy val projectSettings: Seq[Setting[_]] = Seq(
+ mavlinkDialect in Compile := (baseDirectory in Compile).value / "conf" / "mavlink.xml",
+ mavlinkTarget in Compile := (sourceManaged in Compile).value,
+ mavlinkGenerate in Compile := generationTask.value,
+ sourceGenerators in Compile += (mavlinkGenerate in Compile).taskValue
+ )
+
+ lazy val generationTask = Def.task[Seq[File]] {
+ val dialectDefinitionFile = (mavlinkDialect in Compile).value
+
+ if (!dialectDefinitionFile.exists) sys.error(
+ "Dialect definition " + dialectDefinitionFile.getAbsolutePath + " does not exist."
+ )
+
+ val dialectDefinition = XML.loadFile(dialectDefinitionFile)
+ val dialect = Parser.parseDialect(dialectDefinition)
+ val pathToSource = (new Generator(dialect)).generate()
+
+ val outDirectory = (mavlinkTarget in Compile).value
+
+ streams.value.log.info("Generating mavlink files...")
+
+ val files = for ((path, source) <- pathToSource) yield {
+ streams.value.log.debug("Generating " + path)
+ val file = outDirectory / path
+ IO.write(file, source)
+ file.getAbsoluteFile
+ }
+
+ streams.value.log.info("Done generating mavlink files")
+ files
+ }
+
+}
diff --git a/project/Build.scala b/project/Build.scala
new file mode 100644
index 0000000..ddf108e
--- /dev/null
+++ b/project/Build.scala
@@ -0,0 +1,42 @@
+import sbt._
+import sbt.Keys._
+import play.twirl.sbt.SbtTwirl
+import play.twirl.sbt.Import._
+
+object ApplicationBuild extends Build {
+
+ val common = Seq(
+ scalaVersion := "2.10.4",
+ scalacOptions ++= Seq("-feature", "-deprecation"),
+ organization := "com.github.jodersky",
+ version := "0.1-SNAPSHOT"
+ )
+
+ lazy val root = Project("root", file(".")).aggregate(
+ library,
+ plugin
+ )
+
+ lazy val library = (
+ Project("mavlink-library", file("mavlink-library"))
+ enablePlugins(SbtTwirl)
+ settings(common: _*)
+ settings(
+ libraryDependencies += "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.2",
+ TwirlKeys.templateImports += "com.github.jodersky.mavlink._",
+ TwirlKeys.templateImports += "com.github.jodersky.mavlink.trees._"
+ )
+ )
+
+ lazy val plugin = (
+ Project("mavlink-plugin", file("mavlink-plugin"))
+ settings(common: _*)
+ settings(
+ sbtPlugin := true,
+ name := "sbt-mavlink"
+ )
+ dependsOn(library)
+ )
+
+}
+
diff --git a/project/plugins.sbt b/project/plugins.sbt
new file mode 100644
index 0000000..7b458b6
--- /dev/null
+++ b/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4") \ No newline at end of file