///|
pub struct Ros1Decoder {
layout : CdrDecoder
}
///|
fn validate_ros1_time(
name : String,
value : Map[String, Json],
) -> Unit raise McapError {
if name != "moonmcap_ros1/Time" && name != "moonmcap_ros1/Duration" {
return
}
if value.get("nanosec") is Some(Number(number, ..)) {
if number >= 1000000000.0 ||
(name == "moonmcap_ros1/Time" && number < 0.0) ||
number <= -1000000000.0 {
raise Invalid("ROS1 time nanoseconds out of range")
}
}
}
///|
pub fn ros1_decoder(
name : String,
definition : Bytes,
options? : CdrOptions = cdr_options(),
) -> Ros1Decoder raise McapError {
if options.schema_bytes < 1 ||
options.schema_bytes > 2147482623 ||
definition.length() > options.schema_bytes {
raise Invalid("ROS1 schema exceeds byte budget")
}
let text = @utf8.decode(definition[:]) catch {
_ => raise Invalid("invalid ros1msg UTF-8")
}
let lines = []
let mut time = false
let mut duration = false
let mut header = false
let header_defined = text
.split("\n")
.any(fn(line) { line.trim() == "MSG: std_msgs/Header" }) ||
name == "std_msgs/Header" ||
name == "std_msgs/msg/Header"
for raw in text.split("\n") {
let line = raw.split("#").next().unwrap().trim().to_string()
if line == "" || line.has_prefix("MSG:") || line.has_prefix("====") {
lines.push(line)
continue
}
let pieces = line
.replace_all(old="\t", new=" ")
.split(" ")
.filter(fn(p) { p != "" })
.to_array()
if pieces.length() < 2 {
raise Invalid("ROS1 definition needs type and field name")
}
if pieces[1].contains("=") ||
(pieces.length() > 2 && pieces[2].has_prefix("=")) {
continue
}
if pieces.length() != 2 {
raise Invalid("ROS1 fields cannot have defaults")
}
let token = pieces[0].to_string()
if token.contains("<=") {
raise Invalid("ROS1 bounded sequence/string syntax is unsupported")
}
let (base, suffix) = match token.split_once("[") {
None => (token, "")
Some((base, tail)) => (base.to_string(), "[" + tail.to_string())
}
let base = match base {
"byte" => "int8"
"char" => "uint8"
"time" => {
time = true
"moonmcap_ros1/Time"
}
"duration" => {
duration = true
"moonmcap_ros1/Duration"
}
"Header" => {
header = true
"std_msgs/Header"
}
_ => base
}
lines.push(base + suffix + " " + pieces[1].to_string())
}
let separator = "\n================================================================================\nMSG: "
let mut normalized = lines.join("\n")
if header && !header_defined {
normalized += separator +
"std_msgs/Header\nuint32 seq\nmoonmcap_ros1/Time stamp\nstring frame_id"
time = true
}
if time {
normalized += separator + "moonmcap_ros1/Time\nuint32 sec\nuint32 nanosec"
}
if duration {
normalized += separator + "moonmcap_ros1/Duration\nint32 sec\nint32 nanosec"
}
let expanded = @utf8.encode(normalized[:])
{
layout: cdr_decoder_internal(name, expanded, true, options={
..options,
schema_bytes: options.schema_bytes + 1024,
}),
}
}
///|
pub fn Ros1Decoder::decode(
self : Ros1Decoder,
bytes : Bytes,
) -> Json raise McapError {
if bytes.length() > self.layout.options.message_bytes {
raise Invalid("ROS1 payload exceeds byte budget")
}
let wire : CdrWire = {
bytes,
little: true,
aligned: false,
position: 0,
budget: [self.layout.options.items],
}
let value = self.layout.message(wire, self.layout.name, 1)
if wire.position != bytes.length() {
raise Invalid("trailing ROS1 payload bytes")
}
value
}
///|
pub fn Ros1Decoder::view(
self : Ros1Decoder,
bytes : Bytes,
selection? : DecodeSelection = decode_selection(),
) -> PayloadView raise McapError {
self.layout.view(bytes, selection~)
}