Skip to content

Commit

Permalink
[CONSULT-469] - introduce macro for PGN registration and NmeaMessage …
Browse files Browse the repository at this point in the history
…type (#408)
  • Loading branch information
gvaradarajan authored Feb 4, 2025
1 parent c015e36 commit fc41626
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 40 deletions.
28 changes: 28 additions & 0 deletions micro-rdk-nmea-macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub(crate) struct MacroAttributes {
pub(crate) is_lookup: bool,
pub(crate) is_fieldset: bool,
pub(crate) length_field: Option<Ident>,
pub(crate) pgn: Option<u32>,
}

// Attempt to deduce the bit size from the data type
Expand Down Expand Up @@ -92,6 +93,7 @@ impl MacroAttributes {
length_field: None,
unit: None,
is_fieldset: false,
pgn: None,
};

for attr in field.attrs.iter() {
Expand Down Expand Up @@ -144,6 +146,32 @@ impl MacroAttributes {
}
};
}
"pgn" => {
macro_attrs.pgn = match &attr.meta {
Meta::NameValue(named) => {
if let Expr::Lit(ref expr_lit) = named.value {
let bits_lit = expr_lit.lit.clone();
if let Lit::Int(bits_lit) = bits_lit {
match bits_lit.base10_parse::<u32>() {
Ok(bits) => Some(bits),
Err(err) => {
return Err(error_tokens(err.to_string().as_str()));
}
}
} else {
return Err(error_tokens("pgn parameter must be int"));
}
} else {
return Err(error_tokens("pgn parameter must be int"));
}
}
_ => {
return Err(error_tokens(
"pgn received unexpected attribute value",
));
}
};
}
"offset" => {
macro_attrs.offset = match &attr.meta {
Meta::NameValue(named) => {
Expand Down
60 changes: 46 additions & 14 deletions micro-rdk-nmea-macros/src/composition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) struct PgnComposition {
pub(crate) parsing_logic: Vec<TokenStream2>,
pub(crate) struct_initialization: Vec<TokenStream2>,
pub(crate) proto_conversion_logic: Vec<TokenStream2>,
pub(crate) pgn_declaration: Option<TokenStream2>,
}

impl PgnComposition {
Expand All @@ -31,31 +32,49 @@ impl PgnComposition {
parsing_logic: vec![],
struct_initialization: vec![],
proto_conversion_logic: vec![],
pgn_declaration: None,
}
}

pub(crate) fn set_pgn_declaration(&mut self, dec: TokenStream2) {
self.pgn_declaration = Some(dec)
}

pub(crate) fn merge(&mut self, mut other: Self) {
self.attribute_getters.append(&mut other.attribute_getters);
self.parsing_logic.append(&mut other.parsing_logic);
self.struct_initialization
.append(&mut other.struct_initialization);
self.proto_conversion_logic
.append(&mut other.proto_conversion_logic);
if other.pgn_declaration.is_some() {
self.pgn_declaration = other.pgn_declaration;
}
}

pub(crate) fn from_field(field: &Field, purpose: CodeGenPurpose) -> Result<Self, TokenStream> {
let mut statements = Self::new();
if let Some(name) = &field.ident {
if name == "source_id" {
let num_ty = &field.ty;
statements.attribute_getters.push(quote! {
pub fn #name(&self) -> #num_ty { self.#name }
});
statements.struct_initialization.push(quote! { source_id, });
return Ok(statements);
let macro_attrs = MacroAttributes::from_field(field)?;

if name == "_pgn" {
if let Some(pgn) = macro_attrs.pgn {
statements.set_pgn_declaration(quote! {
const PGN: u32 = #pgn;
});
statements
.struct_initialization
.push(quote! { _pgn: std::marker::PhantomData, });
return Ok(statements);
} else {
let err_msg = format!(
"pgn field must define pgn attribute, macro attributes: {:?}",
macro_attrs
);
return Err(error_tokens(&err_msg));
}
}

let macro_attrs = MacroAttributes::from_field(field)?;
if macro_attrs.offset != 0 {
let offset = macro_attrs.offset;
statements
Expand Down Expand Up @@ -127,34 +146,47 @@ impl PgnComposition {
Ok(statements)
}

pub(crate) fn into_token_stream(self, input: &DeriveInput) -> TokenStream2 {
pub(crate) fn into_token_stream(
self,
input: &DeriveInput,
) -> Result<TokenStream2, TokenStream> {
let name = &input.ident;
let parsing_logic = self.parsing_logic;
let attribute_getters = self.attribute_getters;
let struct_initialization = self.struct_initialization;
let proto_conversion_logic = self.proto_conversion_logic;
if self.pgn_declaration.is_none() {
return Err(error_tokens("pgn field of type u32 required"));
}
let pgn_declaration = self.pgn_declaration.unwrap();
let (impl_generics, src_generics, src_where_clause) = input.generics.split_for_impl();
let crate_ident = crate::utils::get_micro_nmea_crate_ident();
let error_ident = quote! {#crate_ident::parse_helpers::errors::NmeaParseError};
let mrdk_crate = crate::utils::get_micro_rdk_crate_ident();
quote! {
Ok(quote! {
impl #impl_generics #name #src_generics #src_where_clause {
pub fn from_cursor(mut cursor: #crate_ident::parse_helpers::parsers::DataCursor, source_id: u8) -> Result<Self, #error_ident> {
#(#attribute_getters)*
}

impl #impl_generics #crate_ident::messages::message::Message for #name #src_generics #src_where_clause {
#pgn_declaration

fn from_cursor(mut cursor: #crate_ident::parse_helpers::parsers::DataCursor) -> Result<Self, #error_ident> {
use #crate_ident::parse_helpers::parsers::FieldReader;
#(#parsing_logic)*
Ok(Self {
#(#struct_initialization)*
})
}
#(#attribute_getters)*

pub fn to_readings(self) -> Result<#mrdk_crate::common::sensor::GenericReadingsResult, #error_ident> {

fn to_readings(self) -> Result<#mrdk_crate::common::sensor::GenericReadingsResult, #error_ident> {
let mut readings = std::collections::HashMap::new();
#(#proto_conversion_logic)*
Ok(readings)
}
}
}
})
}

pub(crate) fn into_fieldset_token_stream(self, input: &DeriveInput) -> TokenStream2 {
Expand Down
7 changes: 5 additions & 2 deletions micro-rdk-nmea-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ use proc_macro::TokenStream;
/// annotating the struct fields to customize the parsing/deserializing logic
#[proc_macro_derive(
PgnMessageDerive,
attributes(label, scale, lookup, bits, offset, fieldset, length_field, unit)
attributes(label, scale, lookup, bits, offset, fieldset, length_field, unit, pgn)
)]
pub fn pgn_message_derive(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::DeriveInput);

match PgnComposition::from_input(&input, CodeGenPurpose::Message) {
Ok(statements) => statements.into_token_stream(&input).into(),
Ok(statements) => match statements.into_token_stream(&input) {
Ok(tokens) => tokens.into(),
Err(tokens) => tokens,
},
Err(tokens) => tokens,
}
}
Expand Down
49 changes: 35 additions & 14 deletions micro-rdk-nmea/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ mod tests {
use base64::{engine::general_purpose, Engine};

use crate::{
messages::pgns::{GnssPositionData, GnssSatsInView, TemperatureExtendedRange, WaterDepth},
messages::{
message::Message,
pgns::{
GnssPositionData, GnssSatsInView, TemperatureExtendedRange, WaterDepth,
MESSAGE_DATA_OFFSET,
},
},
parse_helpers::{
enums::{
Gns, GnsIntegrity, GnsMethod, RangeResidualMode, SatelliteStatus, TemperatureSource,
Expand All @@ -26,11 +32,13 @@ mod tests {
let mut data = Vec::<u8>::new();
let res = general_purpose::STANDARD.decode_vec(water_depth_str, &mut data);
assert!(res.is_ok());
let cursor = DataCursor::new(data[33..].to_vec());
let message = WaterDepth::from_cursor(cursor, 13);
let cursor = DataCursor::new(data[MESSAGE_DATA_OFFSET..].to_vec());
let message = WaterDepth::from_cursor(cursor);
assert!(message.is_ok());
let message = message.unwrap();
assert_eq!(message.source_id(), 13);
let source_id = message.source_id();
assert!(source_id.is_ok());
assert_eq!(source_id.unwrap(), 255);
let depth = message.depth();
assert!(depth.is_ok());
assert_eq!(depth.unwrap(), 2.12);
Expand All @@ -49,11 +57,13 @@ mod tests {
let mut data = Vec::<u8>::new();
let res = general_purpose::STANDARD.decode_vec(water_depth_str, &mut data);
assert!(res.is_ok());
let cursor = DataCursor::new(data[33..].to_vec());
let message = WaterDepth::from_cursor(cursor, 13);
let cursor = DataCursor::new(data[MESSAGE_DATA_OFFSET..].to_vec());
let message = WaterDepth::from_cursor(cursor);
assert!(message.is_ok());
let message = message.unwrap();
assert_eq!(message.source_id(), 13);
let source_id = message.source_id();
assert!(source_id.is_ok());
assert_eq!(source_id.unwrap(), 0);
let depth = message.depth();
assert!(depth.is_ok());
assert_eq!(depth.unwrap(), 3.9);
Expand All @@ -73,11 +83,14 @@ mod tests {
let res = general_purpose::STANDARD.decode_vec(temp_str, &mut data);
assert!(res.is_ok());

let cursor = DataCursor::new(data[33..].to_vec());
let message = TemperatureExtendedRange::from_cursor(cursor, 23);
let cursor = DataCursor::new(data[MESSAGE_DATA_OFFSET..].to_vec());
let message = TemperatureExtendedRange::from_cursor(cursor);
assert!(message.is_ok());
let message = message.unwrap();
assert_eq!(message.source_id(), 23);
let source_id = message.source_id();
assert!(source_id.is_ok());
assert_eq!(source_id.unwrap(), 255);

let temp = message.temperature();
assert!(temp.is_ok());
let temp = temp.unwrap();
Expand All @@ -99,11 +112,15 @@ mod tests {
let mut data = Vec::<u8>::new();
let res = general_purpose::STANDARD.decode_vec(gnss_str, &mut data);
assert!(res.is_ok());
let cursor = DataCursor::new(data[33..].to_vec());
let message = GnssPositionData::from_cursor(cursor, 3);
let cursor = DataCursor::new(data[MESSAGE_DATA_OFFSET..].to_vec());
let message = GnssPositionData::from_cursor(cursor);
assert!(message.is_ok());
let message = message.unwrap();

let source_id = message.source_id();
assert!(source_id.is_ok());
assert_eq!(source_id.unwrap(), 58);

let altitude = message.altitude();
assert!(altitude.is_ok());
let altitude = altitude.unwrap();
Expand Down Expand Up @@ -140,12 +157,16 @@ mod tests {
let res = general_purpose::STANDARD.decode_vec(msg_str, &mut data);
assert!(res.is_ok());

let cursor = DataCursor::new(data[33..].to_vec());
let message = GnssSatsInView::from_cursor(cursor, 3);
let cursor = DataCursor::new(data[MESSAGE_DATA_OFFSET..].to_vec());
let message = GnssSatsInView::from_cursor(cursor);
assert!(message.is_ok());
let message = message.unwrap();
println!("message: {:?}", message);

let source_id = message.source_id();
assert!(source_id.is_ok());
assert_eq!(source_id.unwrap(), 162);

let range_residual_mode = message.range_residual_mode();
println!("range_residual_mode: {:?}", range_residual_mode);
assert!(matches!(
Expand Down
52 changes: 52 additions & 0 deletions micro-rdk-nmea/src/messages/message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::collections::HashMap;

use base64::{engine::general_purpose, Engine};
use micro_rdk::{
common::sensor::GenericReadingsResult,
google::protobuf::{value::Kind, Value},
};

use crate::parse_helpers::{errors::NmeaParseError, parsers::DataCursor};

pub trait Message: Sized + Clone {
const PGN: u32;
fn from_cursor(cursor: DataCursor) -> Result<Self, NmeaParseError>;
fn to_readings(self) -> Result<GenericReadingsResult, NmeaParseError>;
fn pgn(&self) -> u32 {
Self::PGN
}
}

#[derive(Debug, Clone)]
pub struct UnparsedNmeaMessageBody {
data: Vec<u8>,
pgn: u32,
}

impl UnparsedNmeaMessageBody {
pub fn from_bytes(data: Vec<u8>, pgn: u32) -> Result<Self, NmeaParseError> {
Ok(Self { data, pgn })
}

pub fn to_readings(self) -> Result<GenericReadingsResult, NmeaParseError> {
let data_string = general_purpose::STANDARD.encode(self.data);
Ok(HashMap::from([
(
"data".to_string(),
Value {
kind: Some(Kind::StringValue(data_string)),
},
),
(
"pgn".to_string(),
Value {
kind: Some(Kind::NumberValue(self.pgn as f64)),
},
),
]))
}

pub fn pgn(&self) -> u32 {
self.pgn
}
}
1 change: 1 addition & 0 deletions micro-rdk-nmea/src/messages/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod message;
pub mod pgns;
Loading

0 comments on commit fc41626

Please sign in to comment.