-
Notifications
You must be signed in to change notification settings - Fork 12
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] - introduction of NMEA message macro #386
base: main
Are you sure you want to change the base?
[CONSULT-469] - introduction of NMEA message macro #386
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with first pass. I like what i am seeing, the code generation is very mechanical and doesn't have too much custom logic.
I see that you generate *_raw function to access fields, was it impossible to reference the field immediately in the code gen? Do you foresee special logic in these functions?Do you mind adding a couple more examples showing up how to use field annotations?
I think conversion is hard to read at the moment, however I recall this is mostly how goNMEA does it so I would leave it as is but add a tickets for potential further work
We should also avoid returning errors in to_readings as much as possible
#max_token | ||
let result = match result { | ||
x if x == max => { return Err(#error_ident::FieldNotPresent(#name_as_string_ident.to_string())); }, | ||
x if x == (max - 1) => { return Err(#error_ident::FieldError(#name_as_string_ident.to_string())); }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting, if the field has an invalid value the err will bubble out and to_readings won't return an hashmap. I think we should return a value here, ideally it should be a string like "invalid" or "absent". This may imply that this function will return a value::Kind so that to_reading don't have extra logic to wrap the error in a value::Kind::String.
How is this case handle in gonmea? if this is how it's done then let's leave it as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Digging through the GoNMEA logic, it appears that the library does not return a message instance if any number field produces an error value. However, what is missing is that number fields less than 4 bits in size use an alternate logic. I'll update with this change
}); | ||
statements | ||
.struct_initialization | ||
.push(quote! { source_id: source_id.unwrap(), }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should remove the unwrap either make it mandatory or default to a sensible value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it shouldn't be an option in the first place, will fix
micro-rdk-nmea-macros/src/utils.rs
Outdated
quote! {#crate_ident::google::protobuf} | ||
} | ||
|
||
pub(crate) fn determine_supported_numeric(field_type: &Type) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_supported_integer_type
micro-rdk-nmea-macros/src/lib.rs
Outdated
use proc_macro2::Span; | ||
use syn::DeriveInput; | ||
|
||
fn get_statements(input: &DeriveInput) -> Result<PgnComposition, TokenStream> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering why you have function that do not take self. It is impossible to have a Struct that holds all statements, fields etc... that can mutate self?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember why, I'll try changing it to an instance method and see if I run into any problems
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also done with a first pass through
micro-rdk-nmea/Cargo.toml
Outdated
micro-rdk = { path = "../micro-rdk", features = ["native"] } | ||
micro-rdk-nmea-macros = { path = "../micro-rdk-nmea-macros" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously micro-rdk
came via a workspace = true
, which I prefer here. I'd expect the same would work for micro-rdk-nmea-macros
if you introduced it in the root Cargo.toml
.
// `MacroAttributes` represents the important information derived from the macro attributes | ||
// attached to the field of a struct using the PgnMessageDerive macro. | ||
// | ||
// bits -> size in bits of the field in the NMEA message byte slice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe :
instead of ->
. Also, maybe, `bits
`, etc. since you did it for `MacroAttributes
` above?
Also, should this be a ///
doc comment?
micro-rdk-nmea/src/lib.rs
Outdated
let mut data = Vec::<u8>::new(); | ||
let res = general_purpose::STANDARD.decode_vec(water_depth_str, &mut data); | ||
assert!(res.is_ok()); | ||
let message = WaterDepth::from_bytes(data[33..].to_vec(), Some(13)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 33 bytes in? What does Some(13)
mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first part is metadata, I'll put a comment in
pub fn src(&self) -> u16 { | ||
self.src | ||
} | ||
|
||
pub fn dst(&self) -> u16 { | ||
self.dst | ||
} | ||
|
||
pub fn priority(&self) -> u16 { | ||
self.priority | ||
} | ||
|
||
pub fn timestamp(&self) -> Timestamp { | ||
self.timestamp.clone() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these trivial accessors needed? Is there some reason that if you had a mut NmeaMessageMetadata
that it should be impossible to alter the values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle, I think data coming from a different source should be left alone as much as possible? I think you wouldn't want to alter the data unless it was absolutely necessary for conversion purposes
@@ -213,16 +213,82 @@ where | |||
} | |||
} | |||
|
|||
// The first 32 bytes of the unparsed message stores metadata appearing in the header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having difficulty lining up this comment with what I found at the link, which seems to talk about 39 bits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should look into this further and not have referenced the link. When examining the raw data that we are currently pushing up (and also looking at the logic in the GoNMEA library), it was clear that the format of the message after processing CAN frames was a 32-byte header followed by the message contents. I admit to a poor understanding of what happens in between receipt of the data from a CAN bus and seeing the data in this format
// this data from the CAN frame). For an explanation of the fields in this header, see | ||
// the section labeled "ISO-11783 and NMEA2000 header" at https://canboat.github.io/canboat/canboat.html | ||
pub struct NmeaMessageMetadata { | ||
timestamp: Timestamp, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why as a proto Timestamp, rather than some non-proto type, something from std, etc.?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, it was laziness. It was already in seconds and milliseconds and it was easier to skip to serializing it for the API immediately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'm going to change it to something not proto
micro-rdk-nmea-macros/src/utils.rs
Outdated
RadPerSecToDegPerSec, | ||
} | ||
|
||
impl TryFrom<String> for UnitConversion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It bothers me a lot that impl<T> TryFrom<T> for UnitConversion where T: AsRef<str> {
cannot work, per https://users.rust-lang.org/t/confusion-interpreting-error-when-using-tryfrom-with-asref/83672.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah me too
micro-rdk-nmea-macros/src/utils.rs
Outdated
|
||
impl TryFrom<String> for UnitConversion { | ||
type Error = TokenStream; | ||
fn try_from(value: String) -> Result<Self, Self::Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't actually need to consume the string here, right? Maybe this should be &str?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, you're right, that's better
@npmenard Regarding the raw accessors: they are not used by the auto-generated code, I included them for ease in debugging potential data issues within a single specific message instance. Should I remove them? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
#[derive(Debug)] | ||
pub(crate) struct MacroAttributes { | ||
pub(crate) bits: Option<usize>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bits
shouldn't be optional
Type::Path(type_path) if type_path.path.is_ident("i64") => 64, | ||
Type::Path(type_path) if type_path.path.is_ident("u64") => 64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort these in
x if x > 4 => quote! { | ||
#max_token | ||
let result = match result { | ||
x if x == max => { return Err(#error_ident::FieldNotPresent(#name_as_string_ident.to_string())); }, | ||
x => { | ||
(x as f64) * #scale_token | ||
} | ||
}; | ||
}, | ||
x if x >= 4 => quote! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These don't look like they are exclusive
This is the first PR actually introducing the procedural macro. As explained in the previous commit, it is recommended that one uses
cargo expand
in the micro-rdk-nmea directory to view the code that is generated by the macro used in the example PGN structs.This PR includes
The next PR will include