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 /project/mavlink-library/src/main | |
parent | 84c641d12187183466df936eaa7c1637d861cf62 (diff) | |
download | mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.tar.gz mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.tar.bz2 mavigator-458971f834a3af0dbf2fffe527352fa11e7d8168.zip |
generate mavlink files in build
Diffstat (limited to 'project/mavlink-library/src/main')
11 files changed, 490 insertions, 0 deletions
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/project/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt new file mode 100644 index 0000000..61b5c6f --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/Crc.scala.txt @@ -0,0 +1,23 @@ +@() + +package org.mavlink + +/** + * X.25 CRC calculation for MAVlink messages. The checksum must be initialized, + * updated with witch field of the message, and then finished with the message + * id. + */ +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 lsb: Byte = crc.toByte + def msb: Byte = (crc >> 8).toByte + +}
\ No newline at end of file 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/project/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt b/project/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt new file mode 100644 index 0000000..f0e30dc --- /dev/null +++ b/project/mavlink-library/src/main/twirl/org/mavlink/Parser.scala.txt @@ -0,0 +1,128 @@ +@() + +package org.mavlink + +import scala.collection.mutable.ArrayBuffer + +object Parser { + + object ParseStates { + 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 + } + + object ParseErrors { + sealed trait ParseError + case object CrcError extends ParseError + case object OverflowError extends ParseError + } +} + +class Parser(receiver: Packet => Unit, error: Parser.ParseErrors.ParseError => Unit = _ => ()) { + import Parser._ + + private var state: ParseStates.State = ParseStates.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 payload = new ArrayBuffer[Byte] + var crc: Crc = new Crc() + } + + def push(c: Byte): Unit = { + import ParseStates._ + + 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 + inbound.payload.clear() + } + + case GotMsgId => + inbound.payload += c + inbound.crc = inbound.crc.accumulate(c) + if(inbound.payload.length >= Packet.MaxPayloadLength) { + state = Idle + error(ParseErrors.OverflowError) + } + if (inbound.payload.length >= 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(ParseErrors.CrcError) + } else { + state = GotCrc1 + } + + case GotCrc1 => + if (c != inbound.crc.msb) { + state = Idle + if (c == Packet.Stx) { + state = GotStx + } + error(ParseErrors.CrcError) + } else { + val packet = Packet( + inbound.seq, + inbound.systemId, + inbound.componentId, + inbound.messageId, + inbound.payload) + state = Idle + inbound.payload = new ArrayBuffer[Byte]() + receiver(packet) + } + } + } + +} 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 |