I would like to write a library that implements the serial (uart) communication protocol for a well known brand of solar chargers. Luckily the protocol specification is publicly available so I don't have to do any reverse engineering. The protocol is very easy: Each ascii encoded frame consists of a magic start, a register address, a command code (get or set), the payload (in case of 'get' this is omitted, otherwise the value to write) and a checksum.
My question is now: How should I deal with the dozens of register definitions? Just hardcode those as an array of structs like this:
pub struct Request {
pub address: u16,
pub command: u8,
pub payload: Option<u16>,
pub checksum: u8,
pub description: &'static str
}
pub const REGS: &'static [Request] = &[
Request {address: 0xabcd, command: 8, payload: Some(0x8000), checksum: 0xab, description: "Current battery voltage" },
Request {address: 0xef23, command: 8, payload: Some(0x8080), checksum: 0xab, description: "Current (mA)"},
];
This way I could match on the address part of an incoming frame. Or maybe get those from a separate .json file via serde? But this would have the drawback that my library might be difficult to use on an embedded device (no_std) that doesn't have a file system. Any other ideas?
Lets take a look how the hyperium
http
crate defines some of its common HTTP
headers, of which there are about 30-50 of.
// Generate constants for all standard HTTP headers. This includes a static hash
// code for the "fast hash" path. The hash code for static headers *do not* have
// to match the text representation of those headers. This is because header
// strings are always converted to the static values (when they match) before
// being hashed. This means that it is impossible to compare the static hash
// code of CONTENT_LENGTH with "content-length".
standard_headers! {
/* [...] */
(Accept, ACCEPT, b"accept");
/* [...] */
(AcceptCharset, ACCEPT_CHARSET, b"accept-charset");
/* [...] */
}
It uses a macro-rule
to parse some predefined syntax, let's look at the standard-headers
macro.
macro_rules! standard_headers {
(
$(
$(#[$docs:meta])*
($konst:ident, $upcase:ident, $name_bytes:literal);
)+
) => {
/* [...] */
$(
$(#[$docs])*
pub const $upcase: HeaderName = HeaderName {
inner: Repr::Standard(StandardHeader::$konst),
};
)+
/* [...] */
}
}
Maybe we can use the existing code and rewrite it to generate the request constants.
macro_rules! define_requests {
(
$(
$(#[$docs:meta])*
($request_name:ident, $address:literal, $description:literal);
)+
) => {
$(
$(#[$docs])*
pub const $request_name: Request = Request {
address: $address,
command: 8,
payload: Some(0x8000),
checksum: 0xab,
description: $description
};
)+
}
}
Now we can define our constant request types using the macro.
define_requests! {
(GET_BATTERY_VOLTAGE, 0xabcd, "Current battery voltage");
(GET_MILI_AMP, 0xef23, "Current (mA)");
}
Check out the code
. To see the generated code, select from the upper left three dots the "HIR" representation and then press the button left to it.