diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 96267e5..1ed8c62 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -19,15 +19,15 @@ jobs: uses: actions/checkout@v3 with: submodules: recursive - - name: Install Python - run: sudo apt-get install python3 - name: Install cargo-audit run: cargo install cargo-audit - name: Build run: cargo build --verbose - name: Test run: cargo test --verbose + - name: Fmt + run: cargo fmt --check - name: Clippy - run: cargo clippy --verbose -- -D warnings + run: cargo clippy --verbose --all -- -D warnings - name: Audit run: cargo audit diff --git a/Embedded-Base b/Embedded-Base index 970cceb..40f07ee 160000 --- a/Embedded-Base +++ b/Embedded-Base @@ -1 +1 @@ -Subproject commit 970cceb6d6589c23d21ff6e3d2c29696973902ff +Subproject commit 40f07ee0d15ad984d5a9e5c3d31cbae41fec5fc1 diff --git a/libs/calypso-cangen/src/can_gen_decode.rs b/libs/calypso-cangen/src/can_gen_decode.rs index e168618..12eb33a 100644 --- a/libs/calypso-cangen/src/can_gen_decode.rs +++ b/libs/calypso-cangen/src/can_gen_decode.rs @@ -5,7 +5,7 @@ use quote::{format_ident, quote}; /** * Trait to generate individual decode function for a CANMsg - * For NetField and CANPoint, generates parts of the function + * For NetField and CANPoint, generates parts of the function */ pub trait CANGenDecode { fn gen_decoder_fn(&mut self) -> ProcMacro2TokenStream; @@ -62,11 +62,11 @@ impl CANGenDecode for NetField { 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(); + let skip_line = quote! { + reader.skip(#size_literal).unwrap(); }; point_skips.extend(skip_line); - } + } quote! { #point_skips } @@ -148,20 +148,16 @@ impl CANGenDecode for CANPoint { _ => 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 } - } + 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 }, }, - _ => { - match self.size { - 0..=8 => quote! { u8 }, - 9..=16 => quote! { u16 }, - _ => quote! { u32 } - } - } }; // prefix to call potential format function diff --git a/libs/calypso-cangen/src/can_gen_encode.rs b/libs/calypso-cangen/src/can_gen_encode.rs index 51ec618..03ec9f3 100644 --- a/libs/calypso-cangen/src/can_gen_encode.rs +++ b/libs/calypso-cangen/src/can_gen_encode.rs @@ -47,9 +47,9 @@ impl CANGenEncode for CANMsg { } } } - }, + } None => { - quote! { } + quote! {} } } } @@ -83,27 +83,23 @@ 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 } - } + 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 }, }, - _ => { - 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! { }, + _ => quote! {}, }; let default_value: f32 = match self.default_value { Some(default_value) => default_value, @@ -119,7 +115,7 @@ impl CANGenEncode for CANPoint { quote! { writer.write_as_from::(#float_final as #write_type).unwrap(); } - }, + } // Big endian (default) _ => { match self.signed { @@ -128,7 +124,7 @@ impl CANGenEncode for CANPoint { quote! { writer.write_signed_out::<#size_literal, #write_type>(#float_final as #write_type).unwrap(); } - }, + } // Unsigned (default) _ => { quote! { diff --git a/libs/calypso-cangen/src/can_types.rs b/libs/calypso-cangen/src/can_types.rs index bf0c7ea..8b367f2 100644 --- a/libs/calypso-cangen/src/can_types.rs +++ b/libs/calypso-cangen/src/can_types.rs @@ -25,10 +25,7 @@ pub struct NetField { 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, + pub sim: Option, } /** @@ -42,3 +39,18 @@ pub struct CANPoint { pub format: Option, pub default_value: Option, } + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum Sim { + SimSweep { + min: f32, + max: f32, + inc_min: f32, + inc_max: f32, + round: Option, + }, + SimEnum { + options: Vec<[f32; 2]>, + }, +} diff --git a/libs/calypso-cangen/src/lib.rs b/libs/calypso-cangen/src/lib.rs index 44f4f19..e294d79 100644 --- a/libs/calypso-cangen/src/lib.rs +++ b/libs/calypso-cangen/src/lib.rs @@ -1,3 +1,3 @@ -pub mod can_types; pub mod can_gen_decode; pub mod can_gen_encode; +pub mod can_types; diff --git a/libs/daedalus/src/lib.rs b/libs/daedalus/src/lib.rs index 44398de..7077889 100644 --- a/libs/daedalus/src/lib.rs +++ b/libs/daedalus/src/lib.rs @@ -7,7 +7,7 @@ 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 quote::{format_ident, quote}; use std::fs; use std::io::Read; use std::path::PathBuf; @@ -15,7 +15,7 @@ use std::str::FromStr; /** * Path to CAN spec JSON files - * Used by all daedalus macros + * 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"; @@ -89,7 +89,8 @@ fn gen_decode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { 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 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(' ', "_") @@ -104,7 +105,7 @@ fn gen_decode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } @@ -135,13 +136,11 @@ fn gen_decode_fns(_path: PathBuf) -> ProcMacro2TokenStream { } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } - - /** * Macro to generate all the code for encode_data.rs * - Generates prelude, phf map, and all encode functions @@ -179,7 +178,10 @@ pub fn gen_encode_data(_item: TokenStream) -> TokenStream { { __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)); + __encode_key_list_entries.extend(gen_encode_keys( + __path.clone(), + &mut __encode_key_list_size, + )); } } Err(_) => { @@ -234,7 +236,7 @@ fn gen_encode_fns(_path: PathBuf) -> ProcMacro2TokenStream { } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } @@ -250,7 +252,7 @@ fn gen_encode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { let mut _msgs: Vec = serde_json::from_str(&_contents).unwrap(); let mut _entries = ProcMacro2TokenStream::new(); - // Only create encode mappings for CANMsgs with key field + // Only create encode mappings for CANMsgs with key field for mut _msg in _msgs { let _entry = match &_msg.key { Some(key) => { @@ -261,21 +263,21 @@ fn gen_encode_mappings(_path: PathBuf) -> ProcMacro2TokenStream { quote! { #key => #fn_name, } - }, + } None => { - quote! { } + quote! {} } }; _entries.extend(_entry); } - + quote! { #_entries } } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } @@ -297,9 +299,9 @@ fn gen_encode_keys(_path: PathBuf, _key_list_size: &mut usize) -> ProcMacro2Toke quote! { #key, } - }, + } None => { - quote! { } + quote! {} } }; _entries.extend(_entry); @@ -311,24 +313,22 @@ fn gen_encode_keys(_path: PathBuf, _key_list_size: &mut usize) -> ProcMacro2Toke } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } - - /** * Macro to generate all the code for simulate_data.rs - * - Generates prelude, all SimulatedComponentAttrs, and all - * SimulatedComponents + * - Generates prelude, all SimComponentAttrs, and all + * SimComponents */ #[proc_macro] pub fn gen_simulate_data(_item: TokenStream) -> TokenStream { let __simulate_prelude = quote! { - use crate::simulatable_message::{SimulatedComponent, SimulatedComponentAttr}; + use crate::simulatable_message::{SimComponent, SimComponentAttr, SimSweep, SimEnum, SimShared}; }; - let mut __simulate_function_body = quote! { }; + let mut __simulate_function_body = quote! {}; match fs::read_dir(DAEDALUS_CANGEN_SPEC_PATH) { Ok(__entries) => { @@ -338,7 +338,8 @@ pub fn gen_simulate_data(_item: TokenStream) -> TokenStream { 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())); + __simulate_function_body + .extend(gen_simulate_function_body(__path.clone())); } } Err(_) => { @@ -355,9 +356,9 @@ pub fn gen_simulate_data(_item: TokenStream) -> TokenStream { let __simulate_expanded = quote! { #__simulate_prelude - pub fn create_simulated_components() -> Vec { - let mut simulatable_messages: Vec = Vec::new(); - + pub fn create_simulated_components() -> Vec> { + let mut simulatable_messages: Vec> = Vec::new(); + #__simulate_function_body simulatable_messages @@ -376,53 +377,122 @@ fn gen_simulate_function_body(_path: PathBuf) -> ProcMacro2TokenStream { 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(); + 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); + match _field.sim { + Some(_sim) => match _sim { + Sim::SimSweep { + min: _sim_min, + max: _sim_max, + inc_min: _sim_inc_min, + inc_max: _sim_inc_max, + round: _round, + } => { + let _attr_name = format_ident!( + "{}_attr", + _field + .name + .clone() + .to_lowercase() + .replace(['/', ' ', '-'], "_") + ); + 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 _round = _round.unwrap_or(false); + let _component = quote! { + let #_attr_name: SimComponentAttr = SimComponentAttr { + sim_freq: #_freq, + n_canpoints: #_n_canpoints, + id: #_id.to_string(), + }; + + let #_component_name = Box::new(SimSweep::new( + #_name.to_string(), + #_unit.to_string(), + #_attr_name, + (#_sim_min, #_sim_max, #_sim_inc_min, #_sim_inc_max), + #_round + )); + + simulatable_messages.push(#_component_name); + }; + _extend.extend(_component); + } + Sim::SimEnum { options: _options } => { + let _attr_name = format_ident!( + "{}_attr", + _field + .name + .clone() + .to_lowercase() + .replace(['/', ' ', '-'], "_") + ); + 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(); + + // convert frequencies to running total + let mut options_corrected: Vec<[f32; 2]> = Vec::new(); + for i in 0.._options.len() { + let prev_opt_add = if i == 0 { + 0f32 + } else { + options_corrected[i - 1][1] + }; + let new_opt = + [_options[i][0], _options[i][1] + prev_opt_add]; + options_corrected.push(new_opt); + } + + // turn it into a vec of proc tokens as they do not implement ToToken + let _options_ts: Vec = options_corrected + .iter() + .map(|item| { + quote! { [#(#item),*]} + }) + .collect(); + let _component = quote! { + let #_attr_name: SimComponentAttr = SimComponentAttr { + sim_freq: #_freq, + n_canpoints: #_n_canpoints, + id: #_id.to_string(), + }; + + let #_component_name = Box::new(SimEnum::new( + #_name.to_string(), + #_unit.to_string(), + #_attr_name, + vec![#(#_options_ts),*] + )); + + simulatable_messages.push(#_component_name); + }; + _extend.extend(_component); + } + }, + None => continue, } } } @@ -436,7 +506,7 @@ fn gen_simulate_function_body(_path: PathBuf) -> ProcMacro2TokenStream { } Err(_) => { eprintln!("Error opening file"); - quote! { } + quote! {} } } } diff --git a/src/bin/simulate.rs b/src/bin/simulate.rs index 3f5f4f9..2a4c716 100644 --- a/src/bin/simulate.rs +++ b/src/bin/simulate.rs @@ -4,11 +4,11 @@ use std::{ }; use calypso::{ - mqtt::MqttClient, serverdata, simulatable_message::SimulatedComponent, simulate_data::create_simulated_components + mqtt::MqttClient, serverdata, simulatable_message::SimShared, + simulate_data::create_simulated_components, }; use clap::Parser; - /// Calypso command line arguments #[derive(Parser, Debug)] #[command(version)] @@ -23,14 +23,13 @@ struct CalypsoArgs { siren_host_url: String, } - fn simulate_out(pub_path: &str) { let mut client = MqttClient::new(pub_path, "calypso-simulator"); let _ = client.connect(); // todo: add error handling let sleep_time = Duration::from_millis(10); // todo: a way to turn individual components on and off - let mut simulated_components: Vec = create_simulated_components(); + let mut simulated_components: Vec> = create_simulated_components(); // loop through the simulated components, if they should update, update them and publish the data loop { @@ -38,7 +37,7 @@ fn simulate_out(pub_path: &str) { if component.should_update() { component.update(); let timestamp = UNIX_EPOCH.elapsed().unwrap().as_micros() as u64; - let data: calypso::data::DecodeData = component.get_data(); + let data: calypso::data::DecodeData = component.get_value(); let mut payload = serverdata::ServerData::new(); payload.unit = data.unit.to_string(); payload.values = data.value; @@ -59,8 +58,6 @@ fn simulate_out(pub_path: &str) { } } - - /** * Main Function * Calls the `simulate_out` function with the siren host URL from the command line arguments. diff --git a/src/data.rs b/src/data.rs index d9beb7d..a08de98 100644 --- a/src/data.rs +++ b/src/data.rs @@ -90,32 +90,32 @@ impl EncodeData { 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 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 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 - } + 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 - } + /* 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/lib.rs b/src/lib.rs index f6d30fc..76ef74e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,5 @@ pub mod decode_data; pub mod encode_data; pub mod mqtt; pub mod serverdata; -pub mod simulate_data; pub mod simulatable_message; +pub mod simulate_data; diff --git a/src/main.rs b/src/main.rs index ba7cc4f..21ef2ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,7 @@ use std::{ }; use calypso::{ - command_data, data::DecodeData, data::EncodeData, - decode_data::*, encode_data::*, + command_data, data::DecodeData, data::EncodeData, decode_data::*, encode_data::*, mqtt::MqttClient, serverdata, }; use clap::Parser; @@ -57,7 +56,9 @@ fn read_can(pub_path: &str, can_interface: &str) -> JoinHandle { } let socket = CanSocket::open(can_interface).expect("Failed to open CAN socket!"); - socket.set_error_filter_accept_all().expect("Failed to set error mask on CAN socket!"); + socket + .set_error_filter_accept_all() + .expect("Failed to set error mask on CAN socket!"); thread::spawn(move || loop { if !client.is_connected() { @@ -73,11 +74,11 @@ fn read_can(pub_path: &str, can_interface: &str) -> JoinHandle { let data = data_frame.data(); let id: u32 = match data_frame.id() { socketcan::Id::Standard(std) => std.as_raw().into(), - socketcan::Id::Extended(ext) => ext.as_raw() + socketcan::Id::Extended(ext) => ext.as_raw(), }; let decoder_func = match DECODE_FUNCTION_MAP.get(&id) { - Some(func) => *func, - None => decode_mock + Some(func) => *func, + None => decode_mock, }; decoder_func(data) } @@ -171,7 +172,7 @@ fn read_siren(pub_path: &str, send_map: Arc>>) - let key_owned = key.to_owned(); let encoder_func = match ENCODE_FUNCTION_MAP.get(&key_owned) { Some(func) => *func, - None => encode_mock + None => encode_mock, }; let ret = encoder_func(Vec::new()); writable_send_map.insert(ret.id, ret); @@ -198,7 +199,7 @@ fn read_siren(pub_path: &str, send_map: Arc>>) - let encoder_func = match ENCODE_FUNCTION_MAP.get(&key) { Some(func) => *func, - None => encode_mock + None => encode_mock, }; let ret = encoder_func(buf.data); diff --git a/src/simulatable_message.rs b/src/simulatable_message.rs index 2b2eff6..ba83a49 100644 --- a/src/simulatable_message.rs +++ b/src/simulatable_message.rs @@ -2,84 +2,109 @@ use super::data::DecodeData; use rand::prelude::*; use std::time::Instant; -/** - * Wrapper class for an individual message. - */ -pub struct SimulatedComponent { +/// Base properties of every simulated message +pub struct SimComponent { value: Vec, // DecodeData.value topic: String, // DecodeData.topic unit: String, // DecodeData.unit last_update: Instant, // when the data was last updated #[allow(dead_code)] - n_canpoints: u32, // number of can points - sim_min: f32, // min value - sim_max: f32, // max value - sim_inc_min: f32, // min increment step - sim_inc_max: f32, // max increment step + n_canpoints: u32, // number of can points sim_freq: f32, // Frequency in ms - // format: String, // e.g. "divide10" #[allow(dead_code)] - id: String, // e.g. "0x80" (or should this be a uint32?) - // signed: bool, // is the value signed? - // size: u8, // size of the value in bits -} + id: String, // e.g. "0x80" + // signed: bool, // is the value signed? + // size: u8, // size of the value in bits + // format: String, // e.g. "divide10" +} -pub struct SimulatedComponentAttr { - pub sim_min: f32, - pub sim_max: f32, - pub sim_inc_min: f32, - pub sim_inc_max: f32, +/// A wrapper struct for giving properties of a message from the macros to simulator +pub struct SimComponentAttr { pub sim_freq: f32, pub n_canpoints: u32, pub id: String, } -/** - * Implementation of SimulatedComponents. - */ -impl SimulatedComponent { +/// A simulation mode where a value is selected from a list at a given frequency +pub struct SimEnum { + component: SimComponent, + /// note the frequency here is not an individual pecentage but a running total of the probability + enum_ls: Vec<[f32; 2]>, +} + +/// A simulation mode where a value is choosen within a range with a range of possible increments +pub struct SimSweep { + component: SimComponent, + min: f32, + max: f32, + inc_min: f32, + inc_max: f32, + round: bool, +} + +/// Shared functions of a simulator +pub trait SimShared { + fn update(&mut self); + fn should_update(&self) -> bool { + self.get_component().should_update() + } + fn get_value(&self) -> DecodeData { + self.get_component().get_value() + } + fn get_component(&self) -> &SimComponent; +} + +/// Base implementations of a simulator +impl SimComponent { pub fn should_update(&self) -> bool { self.last_update.elapsed().as_millis() > self.sim_freq as u128 } + pub fn get_value(&self) -> DecodeData { + DecodeData::new(self.value.clone(), &self.topic, &self.unit) + } +} + +/// Sweep specific logic +impl SimSweep { pub fn new( topic: String, unit: String, - attr: SimulatedComponentAttr + attr: SimComponentAttr, + sweep_settings: (f32, f32, f32, f32), // min, max, inc_min, inc_max + round: bool, ) -> Self { - - let sim_min: f32 = attr.sim_min; - let sim_max: f32 = attr.sim_max; - let sim_inc_min: f32 = attr.sim_inc_min; - let sim_inc_max: f32 = attr.sim_inc_max; let sim_freq: f32 = attr.sim_freq; let n_canpoints: u32 = attr.n_canpoints; let id: String = attr.id; let mut value = vec![0.0; n_canpoints as usize]; - + // initialize value with random values between sim_min and sim_max let mut rng = rand::thread_rng(); for item in value.iter_mut().take(n_canpoints as usize) { - *item = rng.gen_range(sim_min..sim_max); - if sim_inc_min != 0.0 { - *item = (*item / sim_inc_min).round() * sim_inc_min; + *item = rng.gen_range(sweep_settings.0..sweep_settings.1); + if sweep_settings.2 != 0.0 { + *item = (*item / sweep_settings.2).round() * sweep_settings.2; } } Self { - value, - topic, - unit, - last_update: Instant::now(), - n_canpoints, - sim_min, - sim_max, - sim_inc_min, - sim_inc_max, - sim_freq, - id, + component: SimComponent { + value, + topic, + unit, + last_update: Instant::now(), + n_canpoints, + sim_freq, + id, + }, + min: sweep_settings.0, + max: sweep_settings.1, + inc_min: sweep_settings.2, + inc_max: sweep_settings.3, + round, } } @@ -91,52 +116,113 @@ impl SimulatedComponent { fn get_rand_offset(&self) -> f32 { let mut rng = rand::thread_rng(); let sign = if rng.gen_bool(0.5) { 1.0 } else { -1.0 }; - let offset: f32 = if self.sim_inc_min == self.sim_inc_max { - self.sim_inc_min + let offset: f32 = if self.inc_min == self.inc_max { + self.inc_min } else { - let rand_offset = rng.gen_range(self.sim_inc_min..self.sim_inc_max); - if self.sim_inc_min != 0.0 { - (rand_offset / self.sim_inc_min).round() * self.sim_inc_min - } - else { + let rand_offset = rng.gen_range(self.inc_min..self.inc_max); + if self.inc_min != 0.0 { + (rand_offset / self.inc_min).round() * self.inc_min + } else { rand_offset } }; offset * sign } +} +/// Base logic impl for sweeped sim +impl SimShared for SimSweep { /** * Update the value of the simulated component. * Ensures the value is within the range of sim_min and sim_max, and rounds the value to the nearest sim_inc_min if sim_inc_min is not 0. */ - pub fn update(&mut self) { - const MAX_ATTEMPTS: u8 = 10; - self.last_update = Instant::now(); - for i in 0..self.value.len() { - let mut new_value = self.value[i] + self.get_rand_offset(); - - // ensuring value is within range AND limit to 5 attempts - let mut attempts = 0; - while (new_value < self.sim_min || new_value > self.sim_max) && attempts < MAX_ATTEMPTS { - new_value = self.value[i] + self.get_rand_offset(); - attempts += 1; - } + fn update(&mut self) { + const MAX_ATTEMPTS: u8 = 10; + self.component.last_update = Instant::now(); + for i in 0..self.component.value.len() { + let mut new_value = self.component.value[i] + self.get_rand_offset(); - // give up if all attempts failed - if attempts >= MAX_ATTEMPTS { - return; - } + // ensuring value is within range AND limit to 10 attempts + let mut attempts = 0; + while (new_value < self.min || new_value > self.max) && attempts < MAX_ATTEMPTS { + new_value = self.component.value[i] + self.get_rand_offset(); + attempts += 1; + } - // rounding the new value - if self.sim_inc_min != 0.0 { - new_value = (new_value / self.sim_inc_min).round() * self.sim_inc_min; - } + // give up if all attempts failed + if attempts >= MAX_ATTEMPTS { + return; + } + + // rounding the new value + if self.inc_min != 0.0 { + new_value = (new_value / self.inc_min).round() * self.inc_min; + } + + // additional rounding override to whole number if enabled + if self.round { + new_value = new_value.round(); + } + + self.component.value[i] = new_value; + } + } + + fn get_component(&self) -> &SimComponent { + &self.component + } +} - self.value[i] = new_value; +/// Enum specific logic +impl SimEnum { + pub fn new( + topic: String, + unit: String, + attr: SimComponentAttr, + enum_ls: Vec<[f32; 2]>, // (summed to 1) + ) -> Self { + let sim_freq: f32 = attr.sim_freq; + let n_canpoints: u32 = attr.n_canpoints; + let id: String = attr.id; + + // placeholder value + let value = vec![0.0; n_canpoints as usize]; + + Self { + component: SimComponent { + value, + topic, + unit, + last_update: Instant::now(), + n_canpoints, + sim_freq, + id, + }, + enum_ls, + } + } +} + +/// Base logic for enum sim +impl SimShared for SimEnum { + fn update(&mut self) { + let mut rng = rand::thread_rng(); + for i in 0..self.component.value.len() { + let prob = rng.gen_range(0f32..1f32); + let mut new_value: Option = None; + for i in 0..self.enum_ls.len() { + let prob_floor = if i == 0 { 0f32 } else { self.enum_ls[i - 1][1] }; + let prob_ceiling = self.enum_ls[i][1]; + if prob >= prob_floor && prob <= prob_ceiling { + new_value = Some(self.enum_ls[i][0]); + break; + } } + self.component.value[i] = new_value.unwrap_or(-1f32); + } } - pub fn get_data(&self) -> DecodeData { - DecodeData::new(self.value.clone(), &self.topic, &self.unit) + fn get_component(&self) -> &SimComponent { + &self.component } }