Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CONSULT-469] - introduce macro for PGN registration and NmeaMessage type #408

Merged
merged 10 commits into from
Feb 4, 2025
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>,
acmorrow marked this conversation as resolved.
Show resolved Hide resolved
}

// 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: {:?}",
acmorrow marked this conversation as resolved.
Show resolved Hide resolved
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)*
acmorrow marked this conversation as resolved.
Show resolved Hide resolved
}

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