diff --git a/.gitignore b/.gitignore index b03ed33..abcf183 100644 --- a/.gitignore +++ b/.gitignore @@ -23,14 +23,5 @@ mod.rs command_data.rs serverdata.rs -# cangen files generaed by calypsogen.py -decode_master_mapping.rs -decode_data.rs -encode_data.rs -encode_master_mapping.rs -format_data.rs -simulate_data.rs - -# my dumb script -./format -/privatetest/ \ No newline at end of file +# private testing +/privatetest/ diff --git a/Cargo.lock b/Cargo.lock index efaf138..f36a292 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,14 +118,26 @@ name = "calypso" version = "0.1.0" dependencies = [ "bitstream-io", + "calypso-cangen", "clap", + "daedalus", "paho-mqtt", + "phf", "protobuf", "protobuf-codegen", "rand", "socketcan", ] +[[package]] +name = "calypso-cangen" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "serde", +] + [[package]] name = "cc" version = "1.0.83" @@ -172,7 +184,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.85", ] [[package]] @@ -224,6 +236,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "daedalus" +version = "0.1.0" +dependencies = [ + "calypso-cangen", + "proc-macro2", + "quote", + "serde_json", +] + [[package]] name = "either" version = "1.9.0" @@ -323,7 +345,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.85", ] [[package]] @@ -425,6 +447,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "libc" version = "0.2.152" @@ -546,6 +574,48 @@ dependencies = [ "openssl-sys", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -575,9 +645,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -635,9 +705,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -723,26 +793,50 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "serde" -version = "1.0.193" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.85", ] +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -790,9 +884,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -829,7 +923,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.85", ] [[package]] @@ -952,5 +1046,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.85", ] diff --git a/Cargo.toml b/Cargo.toml index 8b7eac4..260f0ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,16 @@ default-run = "calypso" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["libs/daedalus", "libs/calypso-cangen"] + +[workspace.dependencies] +proc-macro2 = "1.0.89" +quote = "1.0.37" +serde = { version = "1.0.214", features = ["derive"] } +serde_json = "1.0.132" + + [dependencies] socketcan = "3.3.0" paho-mqtt = "0.12.5" @@ -15,6 +25,9 @@ protobuf = "3.5.1" bitstream-io = "2.5.3" clap = { version = "4.5.20", features = ["derive", "env"] } rand = "0.8" +phf = { version = "0.11.2", features = ["macros"] } +calypso-cangen = { path = "./libs/calypso-cangen" } +daedalus = { path = "./libs/daedalus" } [build-dependencies] diff --git a/build.rs b/build.rs index dfc6bb2..eebb270 100644 --- a/build.rs +++ b/build.rs @@ -1,23 +1,7 @@ -use std::process::Command; - /* Prebuild script */ fn main() { println!("cargo:rerun-if-changed=Embedded-Base"); - match Command::new("python3").arg("./calypsogen.py").status() { - Ok(status) if status.success() => { - println!("Python script executed successfully"); - } - Ok(status) => { - eprintln!("Python script exited with status: {}", status); - std::process::exit(1); - } - Err(e) => { - eprintln!("Failed to execute Python script: {}", e); - std::process::exit(1); - } - } - protobuf_codegen::Codegen::new() .pure() // All inputs and imports from the inputs must reside in `includes` directories. diff --git a/calypsogen.py b/calypsogen.py deleted file mode 100644 index 9ebd169..0000000 --- a/calypsogen.py +++ /dev/null @@ -1,66 +0,0 @@ -import importlib.util -import json -import sys - -# Full path to the directory containing the cangen module -EMBEDDED_BASE_PATH = "./Embedded-Base" -module_name = "cangen" - -# Full path to the cangen module file -module_path = f"{EMBEDDED_BASE_PATH}/{module_name}/__init__.py" - -# Add the cangen directory to the system's path -sys.path.append(EMBEDDED_BASE_PATH) - -# Load the module -spec = importlib.util.spec_from_file_location(module_name, module_path) -cangen = importlib.util.module_from_spec(spec) -spec.loader.exec_module(cangen) - -decode_data = open("./src/decode_data.rs", "w") -decode_master_mapping = open("./src/decode_master_mapping.rs", "w") - -encode_data = open("./src/encode_data.rs", "w") -encode_master_mapping = open("./src/encode_master_mapping.rs", "w") - -simulate_data = open("./src/simulate_data.rs", "w") - -format_data = open("./src/format_data.rs", "w") - - -bms_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/bms.json", "r")) -mpu_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/mpu.json", "r")) -wheel_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/wheel.json", "r")) -dti_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/dti.json", "r")) -calypso_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/calypso_cmd.json", "r")) -charger_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/charger.json", "r")) -msb_messages = json.load(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/msb.json", "r")) - - -bms_messages.extend(mpu_messages) -bms_messages.extend(wheel_messages) -bms_messages.extend(dti_messages) -bms_messages.extend(charger_messages) -bms_messages.extend(calypso_messages) -bms_messages.extend(msb_messages) - -result = cangen.RustSynthFromJSON().parse_messages(bms_messages) - -decode_data.write(result.decode_data) -decode_data.close() - -decode_master_mapping.write(result.decode_master_mapping) -decode_master_mapping.close() - -encode_data.write(result.encode_data) -encode_data.close() - -encode_master_mapping.write(result.encode_master_mapping) -encode_master_mapping.close() - -simulate_data.write(result.simulate_data) -simulate_data.close() - -format_data.write(result.format_data) -format_data.close() - diff --git a/libs/calypso-cangen/Cargo.toml b/libs/calypso-cangen/Cargo.toml new file mode 100644 index 0000000..51165b5 --- /dev/null +++ b/libs/calypso-cangen/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "calypso-cangen" +version = "0.1.0" +edition = "2021" + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +serde = { workspace = true, features = ["derive"] } diff --git a/libs/calypso-cangen/src/can_gen_decode.rs b/libs/calypso-cangen/src/can_gen_decode.rs new file mode 100644 index 0000000..e168618 --- /dev/null +++ b/libs/calypso-cangen/src/can_gen_decode.rs @@ -0,0 +1,190 @@ +use crate::can_types::*; +use proc_macro2::Literal; +use proc_macro2::TokenStream as ProcMacro2TokenStream; +use quote::{format_ident, quote}; + +/** + * Trait to generate individual decode function for a CANMsg + * For NetField and CANPoint, generates parts of the function + */ +pub trait CANGenDecode { + fn gen_decoder_fn(&mut self) -> ProcMacro2TokenStream; +} + +/** + * Function to generate decoder function for a CANMsg + */ +impl CANGenDecode for CANMsg { + fn gen_decoder_fn(&mut self) -> ProcMacro2TokenStream { + let field_decoders = self + .fields + .iter_mut() + .map(|field| field.gen_decoder_fn()) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + let min_size: usize = self + .fields + .iter() + .map(|field| field.points.iter().map(|point| point.size).sum::()) + .sum::() + / 8; + let fn_name = format_ident!( + "decode_{}", + self.desc.clone().to_lowercase().replace(' ', "_") + ); + + quote! { + pub fn #fn_name(data: &[u8]) -> Vec { + if data.len() < #min_size { return vec![]; } + let mut reader = BitReader::endian(Cursor::new(&data), BigEndian); + let mut result: Vec = Vec::new(); + let mut decoded_points: Vec = Vec::new(); + let mut topic_suffixes: Vec = Vec::new(); + #field_decoders + result + } + } + } +} + +/** + * Function to generate result.push() line for decoding a NetField + */ +impl CANGenDecode for NetField { + fn gen_decoder_fn(&mut self) -> ProcMacro2TokenStream { + match self.send { + // If send exists and is false, then skip this field (i.e. skip all its points) + Some(false) => { + let mut point_skips = ProcMacro2TokenStream::new(); + for point in &self.points { + let size_literal = Literal::usize_unsuffixed(point.size); + let skip_line = quote! { + reader.skip(#size_literal).unwrap(); + }; + point_skips.extend(skip_line); + } + quote! { + #point_skips + } + } + // Otherwise, send it (default) + _ => { + let unit = self.unit.clone(); + + // If topic_append, we need to set up the suffix + match self.topic_append { + Some(true) => { + let mut topic_suffix_point = self.points.remove(0); + let topic_suffix_read = topic_suffix_point.gen_decoder_fn(); + let topic = format_ident!("{}/", self.name); + let point_decoders = self + .points + .iter_mut() + .map(|point| { + let decoder = point.gen_decoder_fn(); + quote! { decoded_points.push(#decoder); } + }) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + quote! { + topic_suffixes.push(#topic_suffix_read); + #point_decoders + result.push( + DecodeData::new(decoded_points.clone(), + #topic + String::from(topic_suffixes.pop()), + #unit) + ); + decoded_points.clear(); + } + } + _ => { + let point_decoders = self + .points + .iter_mut() + .map(|point| { + let decoder = point.gen_decoder_fn(); + quote! { decoded_points.push(#decoder); } + }) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + let topic = self.name.clone(); + quote! { + #point_decoders + result.push( + DecodeData::new(decoded_points.clone(), + #topic, + #unit) + ); + decoded_points.clear(); + } + } + } + } + } + } +} + +/** + * Function to generate formatted reader line for decoding a CANPoint + */ +impl CANGenDecode for CANPoint { + fn gen_decoder_fn(&mut self) -> ProcMacro2TokenStream { + // read_func and read_type to map signedness (read_func for big endian, read_type for little endian) + let size_literal = Literal::usize_unsuffixed(self.size); + let read_func = match self.signed { + Some(true) => quote! { reader.read_signed_in::<#size_literal, i32>().unwrap() }, + _ => quote! { reader.read_in::<#size_literal, u32>().unwrap() }, + }; + let read_type = match self.signed { + Some(true) => { + match self.size { + 0..=8 => quote! { i8 }, + 9..=16 => quote! { i16 }, + _ => quote! { i32 } + } + }, + _ => { + match self.size { + 0..=8 => quote! { u8 }, + 9..=16 => quote! { u16 }, + _ => quote! { u32 } + } + } + }; + + // prefix to call potential format function + let format_prefix = match &self.format { + Some(format) => { + let id = format_ident!("{}_d", format); + quote! { FormatData::#id } + } + _ => quote! {}, + }; + + // Endianness affects which read to use + match self.endianness { + Some(ref s) if s == "little" => { + quote! { + #format_prefix (reader.read_as_to::().unwrap() as f32) + } + } + _ => { + quote! { + #format_prefix (#read_func as f32) + } + } + } + } +} diff --git a/libs/calypso-cangen/src/can_gen_encode.rs b/libs/calypso-cangen/src/can_gen_encode.rs new file mode 100644 index 0000000..51ec618 --- /dev/null +++ b/libs/calypso-cangen/src/can_gen_encode.rs @@ -0,0 +1,146 @@ +use crate::can_types::*; +use proc_macro2::Literal; +use proc_macro2::TokenStream as ProcMacro2TokenStream; +use quote::{format_ident, quote}; + +/** + * Trait to generate ProcMacro2TokenStreams for encode function macro + */ +pub trait CANGenEncode { + fn gen_encoder_fn(&mut self) -> ProcMacro2TokenStream; +} + +/** + * Function to generate encoder function for CANMsg + * Generates nothing if the CANMsg is not Encodable (i.e. no key) + */ +impl CANGenEncode for CANMsg { + fn gen_encoder_fn(&mut self) -> ProcMacro2TokenStream { + match &self.key { + Some(_key) => { + let field_encoders = self + .fields + .iter_mut() + .map(|field| field.gen_encoder_fn()) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + let fn_name = format_ident!( + "encode_{}", + self.desc.clone().to_lowercase().replace(' ', "_") + ); + let id_int = + u32::from_str_radix(self.id.clone().trim_start_matches("0x"), 16).unwrap(); + let ext_ident = self.is_ext.unwrap_or(false); + quote! { + pub fn #fn_name(data: Vec) -> EncodeData { + let mut writer = BitWriter::endian(Vec::new(), BigEndian); + let mut pt_index: usize = 0; + #field_encoders + EncodeData { + value: writer.into_writer(), + id: #id_int, + is_ext: #ext_ident, + } + } + } + }, + None => { + quote! { } + } + } + } +} + +/** + * Function to generate encoder line for NetField + */ +impl CANGenEncode for NetField { + fn gen_encoder_fn(&mut self) -> ProcMacro2TokenStream { + let point_encoders = self + .points + .iter_mut() + .map(|point| point.gen_encoder_fn()) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + quote! { + #point_encoders + } + } +} + +/** + * Function to generate encoder line for CANPoint + */ +impl CANGenEncode for CANPoint { + fn gen_encoder_fn(&mut self) -> ProcMacro2TokenStream { + let size_literal = Literal::usize_unsuffixed(self.size); + let write_type = match self.signed { + Some(true) => { + match self.size { + 0..=8 => quote! { i8 }, + 9..=16 => quote! { i16 }, + _ => quote! { i32 } + } + }, + _ => { + match self.size { + 0..=8 => quote! { u8 }, + 9..=16 => quote! { u16 }, + _ => quote! { u32 } + } + } + }; + let format_prefix = match &self.format { + Some(format) => { + let id = format_ident!("{}_e", format); + quote! { FormatData::#id } + } + _ => quote! { }, + }; + let default_value: f32 = match self.default_value { + Some(default_value) => default_value, + _ => 0f32, + }; + let float_final = quote! { + #format_prefix ( *data.get(pt_index).unwrap_or(&(#default_value)) ) + }; + + let write_line = match self.endianness { + // Little endian + Some(ref s) if s == "little" => { + quote! { + writer.write_as_from::(#float_final as #write_type).unwrap(); + } + }, + // Big endian (default) + _ => { + match self.signed { + // Signed + Some(true) => { + quote! { + writer.write_signed_out::<#size_literal, #write_type>(#float_final as #write_type).unwrap(); + } + }, + // Unsigned (default) + _ => { + quote! { + writer.write_out::<#size_literal, #write_type>(#float_final as #write_type).unwrap(); + } + } + } + } + }; + quote! { + #write_line + pt_index += 1; + } + } +} diff --git a/libs/calypso-cangen/src/can_types.rs b/libs/calypso-cangen/src/can_types.rs new file mode 100644 index 0000000..bf0c7ea --- /dev/null +++ b/libs/calypso-cangen/src/can_types.rs @@ -0,0 +1,44 @@ +use serde::Deserialize; + +// TODO: Implement MsgType + +/** + * Class representing a CAN message + */ +#[derive(Deserialize, Debug)] +pub struct CANMsg { + pub id: String, + pub desc: String, + pub fields: Vec, + pub key: Option, + pub is_ext: Option, + pub sim_freq: Option, +} + +/** + * Class representing a NetField of a CAN message + */ +#[derive(Deserialize, Debug)] +pub struct NetField { + pub name: String, + pub unit: String, + pub points: Vec, + pub send: Option, + pub topic_append: Option, + pub sim_min: Option, + pub sim_max: Option, + pub sim_inc_min: Option, + pub sim_inc_max: Option, +} + +/** + * Class representing a CAN point of a NetField + */ +#[derive(Deserialize, Debug)] +pub struct CANPoint { + pub size: usize, + pub signed: Option, + pub endianness: Option, + pub format: Option, + pub default_value: Option, +} diff --git a/libs/calypso-cangen/src/lib.rs b/libs/calypso-cangen/src/lib.rs new file mode 100644 index 0000000..44f4f19 --- /dev/null +++ b/libs/calypso-cangen/src/lib.rs @@ -0,0 +1,3 @@ +pub mod can_types; +pub mod can_gen_decode; +pub mod can_gen_encode; diff --git a/libs/daedalus/Cargo.toml b/libs/daedalus/Cargo.toml new file mode 100644 index 0000000..0285e59 --- /dev/null +++ b/libs/daedalus/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "daedalus" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +calypso-cangen = { path = "../calypso-cangen" } +proc-macro2.workspace = true +quote.workspace = true +serde_json.workspace = true diff --git a/libs/daedalus/src/lib.rs b/libs/daedalus/src/lib.rs new file mode 100644 index 0000000..44398de --- /dev/null +++ b/libs/daedalus/src/lib.rs @@ -0,0 +1,442 @@ +// #![allow(clippy::all)] +extern crate calypso_cangen; +extern crate proc_macro; +extern crate serde_json; +use calypso_cangen::can_gen_decode::*; +use calypso_cangen::can_gen_encode::*; +use calypso_cangen::can_types::*; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as ProcMacro2TokenStream; +use quote::{quote, format_ident}; +use std::fs; +use std::io::Read; +use std::path::PathBuf; +use std::str::FromStr; + +/** + * Path to CAN spec JSON files + * Used by all daedalus macros + * Filepath is relative to project root (i.e. /Calypso) + */ +const DAEDALUS_CANGEN_SPEC_PATH: &str = "./Embedded-Base/cangen/can-messages"; + +/** + * Macro to generate all the code for decode_data.rs + * - Generates prelude, phf map, and all decode functions + */ +#[proc_macro] +pub fn gen_decode_data(_item: TokenStream) -> TokenStream { + let __decode_prelude = quote! { + use std::io::Cursor; + use bitstream_io::{BigEndian, LittleEndian, BitReader, BitRead}; + use phf::phf_map; + use calypso_cangen::can_types::*; + use crate::data::{DecodeData, FormatData}; + }; + let mut __decode_functions = quote! { + pub fn decode_mock(_data: &[u8]) -> Vec:: { + let result = vec![ + DecodeData::new(vec![0.0], "Calypso/Unknown", "") + ]; + result + } + }; + let mut __decode_map_entries = ProcMacro2TokenStream::new(); + + match fs::read_dir(DAEDALUS_CANGEN_SPEC_PATH) { + Ok(__entries) => { + for __entry in __entries { + match __entry { + Ok(__entry) => { + let __path = __entry.path(); + if __path.is_file() && __path.extension().map_or(false, |ext| ext == "json") + { + __decode_functions.extend(gen_decode_fns(__path.clone())); + __decode_map_entries.extend(gen_decode_mappings(__path.clone())); + } + } + Err(_) => { + eprintln!("Could not generate decode functions and mappings"); + } + } + } + } + Err(_) => { + eprintln!("Could not read from directory"); + } + } + + let __decode_expanded = quote! { + #__decode_prelude + + #__decode_functions + + pub static DECODE_FUNCTION_MAP: phf::Map Vec> = phf_map! { + #__decode_map_entries + }; + }; + TokenStream::from(__decode_expanded) +} + +/** + * Helper function to generate decode phf map entries for a given JSON spec file + */ +fn gen_decode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let mut _entries = ProcMacro2TokenStream::new(); + for mut _msg in _msgs { + let id_int = u32::from_str_radix(_msg.id.clone().trim_start_matches("0x"), 16).unwrap(); + let fn_name = format_ident!( + "decode_{}", + _msg.desc.clone().to_lowercase().replace(' ', "_") + ); + let _entry = quote! { #id_int => #fn_name, }; + _entries.extend(_entry); + } + + quote! { + #_entries + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} + +/** + * Helper function to generate decode functions for a given JSON spec file + */ +fn gen_decode_fns(_path: PathBuf) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let _fns = _msgs + .iter_mut() + .map(|_m| _m.gen_decoder_fn()) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc.extend(ProcMacro2TokenStream::from_str("\n")); + acc + }); + + quote! { + #_fns + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} + + + +/** + * Macro to generate all the code for encode_data.rs + * - Generates prelude, phf map, and all encode functions + */ +#[proc_macro] +pub fn gen_encode_data(_item: TokenStream) -> TokenStream { + let __encode_prelude = quote! { + use bitstream_io::{BigEndian, LittleEndian, BitWriter, BitWrite}; + use phf::phf_map; + use calypso_cangen::can_types::*; + use crate::data::{EncodeData, FormatData}; + }; + let mut __encode_functions = quote! { + pub fn encode_mock(data: Vec) -> EncodeData { + let mut writer = BitWriter::endian(Vec::new(), BigEndian); + writer.write_from::(data.len() as u8).unwrap(); + EncodeData { + value: writer.into_writer(), + id: 2047, + is_ext: false, + } + } + }; + let mut __encode_map_entries = ProcMacro2TokenStream::new(); + let mut __encode_key_list_entries = ProcMacro2TokenStream::new(); + let mut __encode_key_list_size: usize = 0; + + match fs::read_dir(DAEDALUS_CANGEN_SPEC_PATH) { + Ok(__entries) => { + for __entry in __entries { + match __entry { + Ok(__entry) => { + let __path = __entry.path(); + if __path.is_file() && __path.extension().map_or(false, |ext| ext == "json") + { + __encode_functions.extend(gen_encode_fns(__path.clone())); + __encode_map_entries.extend(gen_encode_mappings(__path.clone())); + __encode_key_list_entries.extend(gen_encode_keys(__path.clone(), &mut __encode_key_list_size)); + } + } + Err(_) => { + eprintln!("Could not generate encode functions and mappings"); + } + } + } + } + Err(_) => { + eprintln!("Could not read from directory"); + } + } + + let __encode_expanded = quote! { + #__encode_prelude + + #__encode_functions + + pub static ENCODE_FUNCTION_MAP: phf::Map<&'static str, fn(data: Vec) -> EncodeData> = phf_map! { + #__encode_map_entries + }; + + pub const ENCODABLE_KEY_LIST: [&str; #__encode_key_list_size] = [ + #__encode_key_list_entries + ]; + }; + TokenStream::from(__encode_expanded) +} + +/** + * Helper function to generate encode functions for a given JSON spec file + */ +fn gen_encode_fns(_path: PathBuf) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let _fns = _msgs + .iter_mut() + .map(|_m| _m.gen_encoder_fn()) + .collect::>() + .into_iter() + .fold(ProcMacro2TokenStream::new(), |mut acc, ts| { + acc.extend(ts); + acc + }); + + quote! { + #_fns + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} + +/** + * Helper function to generate encode phf map entries for a given JSON spec file + */ +fn gen_encode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let mut _entries = ProcMacro2TokenStream::new(); + + // Only create encode mappings for CANMsgs with key field + for mut _msg in _msgs { + let _entry = match &_msg.key { + Some(key) => { + let fn_name = format_ident!( + "encode_{}", + _msg.desc.clone().to_lowercase().replace(' ', "_") + ); + quote! { + #key => #fn_name, + } + }, + None => { + quote! { } + } + }; + _entries.extend(_entry); + } + + quote! { + #_entries + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} + +/** + * Helper function to generate encode key list entries for a given JSON spec file + */ +fn gen_encode_keys(_path: PathBuf, _key_list_size: &mut usize) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let mut _entries = ProcMacro2TokenStream::new(); + for mut _msg in _msgs { + let _entry = match &_msg.key { + Some(key) => { + *_key_list_size += 1; + quote! { + #key, + } + }, + None => { + quote! { } + } + }; + _entries.extend(_entry); + } + + quote! { + #_entries + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} + + + +/** + * Macro to generate all the code for simulate_data.rs + * - Generates prelude, all SimulatedComponentAttrs, and all + * SimulatedComponents + */ +#[proc_macro] +pub fn gen_simulate_data(_item: TokenStream) -> TokenStream { + let __simulate_prelude = quote! { + use crate::simulatable_message::{SimulatedComponent, SimulatedComponentAttr}; + }; + let mut __simulate_function_body = quote! { }; + + match fs::read_dir(DAEDALUS_CANGEN_SPEC_PATH) { + Ok(__entries) => { + for __entry in __entries { + match __entry { + Ok(__entry) => { + let __path = __entry.path(); + if __path.is_file() && __path.extension().map_or(false, |ext| ext == "json") + { + __simulate_function_body.extend(gen_simulate_function_body(__path.clone())); + } + } + Err(_) => { + eprintln!("Could not generate simulate function"); + } + } + } + } + Err(_) => { + eprintln!("Could not read from directory"); + } + } + + let __simulate_expanded = quote! { + #__simulate_prelude + + pub fn create_simulated_components() -> Vec { + let mut simulatable_messages: Vec = Vec::new(); + + #__simulate_function_body + + simulatable_messages + } + }; + TokenStream::from(__simulate_expanded) +} + +/** + * Helper function to generate simulate function body for a given JSON spec file + */ +fn gen_simulate_function_body(_path: PathBuf) -> ProcMacro2TokenStream { + match fs::File::open(_path) { + Ok(mut _file) => { + let mut _contents = String::new(); + let _ = _file.read_to_string(&mut _contents); + let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); + let mut _body = ProcMacro2TokenStream::new(); + + for mut _msg in _msgs { + let mut _extend = ProcMacro2TokenStream::new(); + if let Some(_freq) = _msg.sim_freq { + for mut _field in _msg.fields { + let _simulatable: bool = + _field.sim_min.is_some() && + _field.sim_max.is_some() && + _field.sim_inc_min.is_some() && + _field.sim_inc_max.is_some(); + if _simulatable { + let _attr_name = format_ident!( + "{}_attr", + _field.name.clone().to_lowercase().replace(['/', ' ', '-'], "_") + ); + let _sim_min: f32 = _field.sim_min.unwrap_or(-1.0); + let _sim_max: f32 = _field.sim_max.unwrap_or(-1.0); + let _sim_inc_min: f32 = _field.sim_inc_min.unwrap_or(-1.0); + let _sim_inc_max: f32 = _field.sim_inc_max.unwrap_or(-1.0); + let _n_canpoints: u32 = _field.points.len().try_into().unwrap(); + let _id = _msg.id.clone(); + let _component_name = format_ident!( + "{}", + _field.name.clone().to_lowercase().replace(['/', ' ', '-'], "_") + ); + let _name = _field.name.clone(); + let _unit = _field.unit.clone(); + let _component = quote! { + let #_attr_name: SimulatedComponentAttr = SimulatedComponentAttr { + sim_min: #_sim_min, + sim_max: #_sim_max, + sim_inc_min: #_sim_inc_min, + sim_inc_max: #_sim_inc_max, + sim_freq: #_freq, + n_canpoints: #_n_canpoints, + id: #_id.to_string(), + }; + + let #_component_name = SimulatedComponent::new( + #_name.to_string(), + #_unit.to_string(), + #_attr_name + ); + + simulatable_messages.push(#_component_name); + }; + _extend.extend(_component); + } + } + } + + _body.extend(_extend); + } + + quote! { + #_body + } + } + Err(_) => { + eprintln!("Error opening file"); + quote! { } + } + } +} diff --git a/src/data.rs b/src/data.rs index 34fc8aa..d9beb7d 100644 --- a/src/data.rs +++ b/src/data.rs @@ -81,3 +81,41 @@ impl EncodeData { Self { id, value, is_ext } } } + +/** + * Class to contain the data formatting functions + * _d = a func to decode a value + * _e = its counterpart to encode a value for sending on CAN line + */ +pub struct FormatData {} + +impl FormatData { + pub fn divide10_d(value: f32) -> f32 { + value / 10.0 + } + pub fn divide10_e(value: f32) -> f32 { + value * 10.0 + } + + pub fn divide100_d(value: f32) -> f32 { + value / 100.0 + } + pub fn divide100_e(value: f32) -> f32 { + value * 100.0 + } + + pub fn divide10000_d(value: f32) -> f32 { + value / 10000.0 + } + pub fn divide10000_e(value: f32) -> f32 { + value * 10000.0 + } + + /* Acceleration values must be offset by 0.0029 according to datasheet */ + pub fn acceleration_d(value: f32) -> f32 { + value * 0.0029 + } + pub fn acceleration_e(value: f32) -> f32 { + value / 0.0029 + } +} diff --git a/src/decodable_message.rs b/src/decodable_message.rs deleted file mode 100644 index 9d1304e..0000000 --- a/src/decodable_message.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::data::DecodeData; - -use super::decode_master_mapping::get_message_info; -/** - * Wrapper class for an individual message. - */ -pub struct DecodableMessage { - id: u32, - data: Vec, -} - -/** - * Implementation of Message. - */ -impl DecodableMessage { - /** - * Creates a new message with the given timestamp, id, and data. - */ - pub fn new(id: u32, data: Vec) -> Self { - Self { id, data } - } - - /** - * Decodes the message and returns a vector of Data objects. - */ - pub fn decode(&self) -> Vec { - DecodableMessage::decode_message(&self.id, &self.data) - } - - /** - * Decodes the message and returns a vector of Data objects. - * Achieves this by calling the decoder function associated with the message id. - * param timestamp: The timestamp of the message. - * param id: The id of the message. - * param data: The data of the message. - * return: A vector of Data objects. - */ - fn decode_message(id: &u32, data: &[u8]) -> Vec { - let decoder = get_message_info(id).decoder; - let mut decoded_data: Vec = Vec::new(); - let result = decoder(data); - for data in result { - decoded_data.push(data); - } - decoded_data - } -} diff --git a/src/decode_data.rs b/src/decode_data.rs new file mode 100644 index 0000000..8cfcc5a --- /dev/null +++ b/src/decode_data.rs @@ -0,0 +1,4 @@ +#![allow(clippy::all)] +use daedalus::gen_decode_data; + +gen_decode_data!(); diff --git a/src/encodable_message.rs b/src/encodable_message.rs deleted file mode 100644 index d8d3775..0000000 --- a/src/encodable_message.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::data::EncodeData; - -use super::encode_master_mapping::get_message_info; -/** - * Wrapper class for an individual encodable message. - */ -pub struct EncodableMessage { - key: String, - data: Vec, -} - -/** - * Implementation of Message. - */ -impl EncodableMessage { - /** - * Creates a new message with the given string key and value - */ - pub fn new(key: String, data: Vec) -> Self { - Self { key, data } - } - - /** - * Decodes the message and returns a struct which defines a CAN packet - */ - pub fn encode(self) -> EncodeData { - EncodableMessage::encode_message(self.key, self.data) - } - - /** - * Decodes the message and returns a vector of Data objects. - * Achieves this by calling the decoder function associated with the message key. - * param key: The key of the message. - * param data: The data of the message. - * return: A vector of Data objects. - */ - fn encode_message(key: String, data: Vec) -> EncodeData { - let encoder = get_message_info(key).encoder; - encoder(data) - } -} diff --git a/src/encode_data.rs b/src/encode_data.rs new file mode 100644 index 0000000..46d9b99 --- /dev/null +++ b/src/encode_data.rs @@ -0,0 +1,4 @@ +#![allow(clippy::all)] +use daedalus::gen_encode_data; + +gen_encode_data!(); diff --git a/src/lib.rs b/src/lib.rs index c7e64fe..f6d30fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,8 @@ pub mod command_data; pub mod data; -pub mod decodable_message; pub mod decode_data; -pub mod decode_master_mapping; -pub mod encodable_message; pub mod encode_data; -pub mod encode_master_mapping; -pub mod format_data; pub mod mqtt; pub mod serverdata; pub mod simulate_data; -pub mod simulatable_message; \ No newline at end of file +pub mod simulatable_message; diff --git a/src/main.rs b/src/main.rs index 60a6fbe..ba7cc4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,8 @@ use std::{ }; use calypso::{ - command_data, data::DecodeData, data::EncodeData, decodable_message::DecodableMessage, - encodable_message::EncodableMessage, encode_master_mapping::ENCODABLE_KEY_LIST, + command_data, data::DecodeData, data::EncodeData, + decode_data::*, encode_data::*, mqtt::MqttClient, serverdata, }; use clap::Parser; @@ -71,14 +71,15 @@ fn read_can(pub_path: &str, can_interface: &str) -> JoinHandle { // CanDataFrame Ok(CanFrame::Data(data_frame)) => { let data = data_frame.data(); - let message = DecodableMessage::new( - match data_frame.id() { - socketcan::Id::Standard(std) => std.as_raw().into(), - socketcan::Id::Extended(ext) => ext.as_raw(), - }, - data.to_vec(), - ); - message.decode() + let id: u32 = match data_frame.id() { + socketcan::Id::Standard(std) => std.as_raw().into(), + socketcan::Id::Extended(ext) => ext.as_raw() + }; + let decoder_func = match DECODE_FUNCTION_MAP.get(&id) { + Some(func) => *func, + None => decode_mock + }; + decoder_func(data) } // CanRemoteFrame Ok(CanFrame::Remote(remote_frame)) => { @@ -92,9 +93,7 @@ fn read_can(pub_path: &str, can_interface: &str) -> JoinHandle { // CanErrorFrame Ok(CanFrame::Error(error_frame)) => { // Publish enum index of error onto CAN - // TODO: Look into string representation with Display - // TODO: Ask `const` impl for Display or enum? - // Impl from ErrorFrame -> f32 + // TODO: maybe look into better representation? let error_index: f32 = match CanError::from(error_frame) { CanError::TransmitTimeout => 0.0, CanError::LostArbitration(_) => 1.0, @@ -169,8 +168,12 @@ fn read_siren(pub_path: &str, send_map: Arc>>) - // do the default initialization for all, do outside of the thread to save time negotiating when send_can comes up let mut writable_send_map = send_map.write().expect("Could not modify send messages!"); for key in ENCODABLE_KEY_LIST { - let packet = EncodableMessage::new(String::clone(&key.to_owned()), Vec::new()); - let ret = packet.encode(); + let key_owned = key.to_owned(); + let encoder_func = match ENCODE_FUNCTION_MAP.get(&key_owned) { + Some(func) => *func, + None => encode_mock + }; + let ret = encoder_func(Vec::new()); writable_send_map.insert(ret.id, ret); } drop(writable_send_map); @@ -193,8 +196,12 @@ fn read_siren(pub_path: &str, send_map: Arc>>) - } }; - let packet = EncodableMessage::new(String::clone(&key), buf.data); - let ret = packet.encode(); + let encoder_func = match ENCODE_FUNCTION_MAP.get(&key) { + Some(func) => *func, + None => encode_mock + }; + let ret = encoder_func(buf.data); + send_map .write() .expect("Could not modify send messages!") @@ -285,4 +292,4 @@ fn main() { can_handle.join().expect("Decoder failed with "); println!("Decoder ended"); -} \ No newline at end of file +} diff --git a/src/simulate_data.rs b/src/simulate_data.rs new file mode 100644 index 0000000..2d5d126 --- /dev/null +++ b/src/simulate_data.rs @@ -0,0 +1,4 @@ +#![allow(clippy::all)] +use daedalus::gen_simulate_data; + +gen_simulate_data!();