diff --git a/src/config.rs b/src/config.rs index 2516304..566316d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,7 +35,7 @@ impl Default for AppConfig { mqtt_port: 1883, // Provide a default MQTT port value mqtt_base_topic: String::new(), refresh_rate_ms: Some(1000), // Set the default refresh rate to 1000ms - config_path: None + config_path: None, } } } @@ -75,9 +75,15 @@ pub fn load_configuration(config_path: Option<&str>) -> Result u16 { ((high as u16) << 8) | (low as u16) } -/// Parse the Realtime Data List and create a SpeeduinoData instance +/// Parses the Realtime Data List and creates a `SpeeduinoData` instance. +/// +/// This function reads a byte slice and extracts various fields to populate +/// a `SpeeduinoData` structure. It uses an internal helper function to read +/// individual bytes from the data slice. +/// +/// # Arguments +/// +/// * `data` - A byte slice containing the realtime data to be parsed. +/// +/// # Returns +/// +/// A `SpeeduinoData` instance populated with the parsed data. +/// +/// # Example +/// +/// ``` +/// let data: &[u8] = &[0x01, 0x02, 0x03, ...]; +/// let speeduino_data = parse_realtime_data(data); +/// ``` #[allow(unused_assignments)] fn parse_realtime_data(data: &[u8]) -> SpeeduinoData { let mut offset = 0; - macro_rules! read_byte { - () => {{ - if offset < data.len() { - let value = data[offset]; - offset += 1; - value - } else { - eprintln!("Not enough bytes remaining to read"); - 0 - } - }}; + fn read_byte(data: &[u8], offset: &mut usize) -> u8 { + if *offset < data.len() { + let value = data[*offset]; + *offset += 1; + value + } else { + eprintln!("Not enough bytes remaining to read"); + 0 + } } // Create a SpeeduinoData instance by reading each field SpeeduinoData { - secl: read_byte!(), - status1: read_byte!(), - engine: read_byte!(), - dwell: read_byte!(), - map_low: read_byte!(), - map_high: read_byte!(), - mat: read_byte!(), - coolant_adc: read_byte!(), - bat_correction: read_byte!(), - battery_10: read_byte!(), - o2_primary: read_byte!(), - ego_correction: read_byte!(), - iat_correction: read_byte!(), - wue_correction: read_byte!(), - rpm_low: read_byte!(), - rpm_high: read_byte!(), - tae_amount: read_byte!(), - corrections: read_byte!(), - ve: read_byte!(), - afr_target: read_byte!(), - pw1_low: read_byte!(), - pw1_high: read_byte!(), - tps_dot: read_byte!(), - advance: read_byte!(), - tps: read_byte!(), - loops_per_second_low: read_byte!(), - loops_per_second_high: read_byte!(), - free_ram_low: read_byte!(), - free_ram_high: read_byte!(), - boost_target: read_byte!(), - boost_duty: read_byte!(), - spark: read_byte!(), - rpm_dot_low: read_byte!(), - rpm_dot_high: read_byte!(), - ethanol_pct: read_byte!(), - flex_correction: read_byte!(), - flex_ign_correction: read_byte!(), - idle_load: read_byte!(), - test_outputs: read_byte!(), - o2_secondary: read_byte!(), - baro: read_byte!(), + secl: read_byte(data, &mut offset), + status1: read_byte(data, &mut offset), + engine: read_byte(data, &mut offset), + dwell: read_byte(data, &mut offset), + map_low: read_byte(data, &mut offset), + map_high: read_byte(data, &mut offset), + mat: read_byte(data, &mut offset), + coolant_adc: read_byte(data, &mut offset), + bat_correction: read_byte(data, &mut offset), + battery_10: read_byte(data, &mut offset), + o2_primary: read_byte(data, &mut offset), + ego_correction: read_byte(data, &mut offset), + iat_correction: read_byte(data, &mut offset), + wue_correction: read_byte(data, &mut offset), + rpm_low: read_byte(data, &mut offset), + rpm_high: read_byte(data, &mut offset), + tae_amount: read_byte(data, &mut offset), + corrections: read_byte(data, &mut offset), + ve: read_byte(data, &mut offset), + afr_target: read_byte(data, &mut offset), + pw1_low: read_byte(data, &mut offset), + pw1_high: read_byte(data, &mut offset), + tps_dot: read_byte(data, &mut offset), + advance: read_byte(data, &mut offset), + tps: read_byte(data, &mut offset), + loops_per_second_low: read_byte(data, &mut offset), + loops_per_second_high: read_byte(data, &mut offset), + free_ram_low: read_byte(data, &mut offset), + free_ram_high: read_byte(data, &mut offset), + boost_target: read_byte(data, &mut offset), + boost_duty: read_byte(data, &mut offset), + spark: read_byte(data, &mut offset), + rpm_dot_low: read_byte(data, &mut offset), + rpm_dot_high: read_byte(data, &mut offset), + ethanol_pct: read_byte(data, &mut offset), + flex_correction: read_byte(data, &mut offset), + flex_ign_correction: read_byte(data, &mut offset), + idle_load: read_byte(data, &mut offset), + test_outputs: read_byte(data, &mut offset), + o2_secondary: read_byte(data, &mut offset), + baro: read_byte(data, &mut offset), canin: [ - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), - read_byte!(), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), + read_byte(data, &mut offset), ], - tps_adc: read_byte!(), - next_error: read_byte!(), + tps_adc: read_byte(data, &mut offset), + next_error: read_byte(data, &mut offset), } } -/// Helper function to publish Speeduino parameters to MQTT -fn publish_speeduino_params_to_mqtt( - client: &mqtt::Client, - config: &Arc, - speeduino_data: &SpeeduinoData, -) { - // List of parameters to publish - let params_to_publish: Vec<(&str, String)> = vec![ - // RPM: Engine revolutions per minute +/// Retrieves the parameters from the provided `SpeeduinoData` structure. +/// +/// This function extracts various parameters from the `SpeeduinoData` structure +/// and returns them as a vector of tuples, where each tuple contains a parameter code +/// and its corresponding value as a string. +/// +/// # Arguments +/// +/// * `speeduino_data` - A reference to the `SpeeduinoData` structure containing the parameters. +/// +/// # Returns +/// +/// A vector of tuples, where each tuple contains a parameter code as a string slice +/// and its corresponding value as a string. +/// +/// # Example +/// +/// ```rust +/// let speeduino_data = SpeeduinoData { /* initialize fields */ }; +/// let params = get_params_to_publish(&speeduino_data); +/// for (code, value) in params { +/// println!("{}: {}", code, value); +/// } +/// ``` +fn get_params_to_publish(speeduino_data: &SpeeduinoData) -> Vec<(&str, String)> { + vec![ ( "RPM", combine_bytes(speeduino_data.rpm_high, speeduino_data.rpm_low).to_string(), ), - // TPS: Throttle Position Sensor reading (0% to 100%) ("TPS", speeduino_data.tps.to_string()), - // VE: Volumetric Efficiency (%) ("VE1", speeduino_data.ve.to_string()), - // O2P: Primary O2 sensor reading ("O2P", (speeduino_data.o2_primary as f32 / 10.0).to_string()), - // MAT: Manifold Air Temperature sensor reading ("MAT", speeduino_data.mat.to_string()), - // CAD: Coolant Analog-to-Digital Conversion value ("CAD", speeduino_data.coolant_adc.to_string()), - // DWL: Dwell time ("DWL", speeduino_data.dwell.to_string()), - // MAP: Manifold Absolute Pressure sensor reading ( "MAP", combine_bytes(speeduino_data.map_high, speeduino_data.map_low).to_string(), ), - // O2S: Secondary O2 sensor reading ( "O2S", (speeduino_data.o2_secondary as f32 / 10.0).to_string(), ), - // ITC: Manifold Air Temperature Correction (%) ("ITC", speeduino_data.iat_correction.to_string()), - // TAE: Warm-Up Enrichment Correction (%) ("TAE", speeduino_data.tae_amount.to_string()), - // COR: Total GammaE (%) ("COR", speeduino_data.corrections.to_string()), - // AFT: Air-Fuel Ratio Target ("AFT", (speeduino_data.afr_target as f32 / 10.0).to_string()), - // PW1: Pulse Width 1 ( "PW1", combine_bytes(speeduino_data.pw1_high, speeduino_data.pw1_low).to_string(), ), - // TPD: Throttle Position Sensor Change per Second ("TPD", speeduino_data.tps_dot.to_string()), - // ADV: Ignition Advance ("ADV", speeduino_data.advance.to_string()), - // LPS: Loops per Second ( "LPS", combine_bytes( @@ -227,35 +266,23 @@ fn publish_speeduino_params_to_mqtt( ) .to_string(), ), - // FRM: Free RAM ( "FRM", combine_bytes(speeduino_data.free_ram_high, speeduino_data.free_ram_low).to_string(), ), - // BST: Boost Target ("BST", speeduino_data.boost_target.to_string()), - // BSD: Boost Duty ("BSD", speeduino_data.boost_duty.to_string()), - // SPK: Spark ("SPK", speeduino_data.spark.to_string()), - // RPD: RPM DOT (assuming signed integer) ( "RPD", combine_bytes(speeduino_data.rpm_dot_high, speeduino_data.rpm_dot_low).to_string(), ), - // ETH: Ethanol Percentage ("ETH", speeduino_data.ethanol_pct.to_string()), - // FLC: Flex Fuel Correction ("FLC", speeduino_data.flex_correction.to_string()), - // FIC: Flex Fuel Ignition Correction ("FIC", speeduino_data.flex_ign_correction.to_string()), - // ILL: Idle Load ("ILL", speeduino_data.idle_load.to_string()), - // TOF: Test Outputs ("TOF", speeduino_data.test_outputs.to_string()), - // BAR: Barometric Pressure ("BAR", speeduino_data.baro.to_string()), - // CN1 to CN8: CAN Input values (Combine bytes) ( "CN1", combine_bytes(speeduino_data.canin[1], speeduino_data.canin[0]).to_string(), @@ -288,50 +315,85 @@ fn publish_speeduino_params_to_mqtt( "CN8", combine_bytes(speeduino_data.canin[15], speeduino_data.canin[14]).to_string(), ), - // TAD: Throttle Position Sensor ADC value ("TAD", speeduino_data.tps_adc.to_string()), - // NER: Next Error code ("NER", speeduino_data.next_error.to_string()), - // STA: Status 1 ("STA", speeduino_data.status1.to_string()), - // ENG: Engine status ("ENG", speeduino_data.engine.to_string()), - // BTC: Battery Temperature Correction ("BTC", speeduino_data.bat_correction.to_string()), - // BAT: Battery voltage (scaled by 10) ("BAT", (speeduino_data.battery_10 as f32 / 10.0).to_string()), - // EGC: EGO Correction ("EGC", speeduino_data.ego_correction.to_string()), - // WEC: Warm-Up Enrichment Correction ("WEC", speeduino_data.wue_correction.to_string()), - // SCL: Secondary Load ("SCL", speeduino_data.secl.to_string()), - ]; + ] +} + +/// Publishes Speeduino parameters to an MQTT broker. +/// +/// This function retrieves the parameters from the provided `SpeeduinoData` +/// and publishes each parameter to the MQTT broker using the provided MQTT client. +/// +/// # Arguments +/// +/// * `client` - A reference to the MQTT client used to publish the messages. +/// * `config` - A reference to the application configuration, which contains the base MQTT topic. +/// * `speeduino_data` - A reference to the `SpeeduinoData` structure containing the parameters to be published. +/// +/// # Example +/// +/// ```rust +/// let client = mqtt::Client::new("mqtt://broker.hivemq.com:1883").unwrap(); +/// let config = Arc::new(AppConfig { mqtt_base_topic: "speeduino/".to_string() }); +/// let speeduino_data = SpeeduinoData { /* initialize fields */ }; +/// +/// publish_speeduino_params_to_mqtt(&client, &config, &speeduino_data); +/// ``` +fn publish_speeduino_params_to_mqtt( + client: &mqtt::Client, + config: &Arc, + speeduino_data: &SpeeduinoData, +) { + let params_to_publish = get_params_to_publish(speeduino_data); - // Iterate over parameters and publish to MQTT for (param_code, param_value) in params_to_publish { publish_param_to_mqtt(client, config, param_code, param_value); } } -/// Helper function to publish a parameter to MQTT with a three-letter code +/// Helper function to publish a parameter to MQTT with a three-letter code. +/// +/// This function constructs the MQTT topic using the base topic from the configuration +/// and the provided parameter code. It then publishes the parameter value to the MQTT broker. +/// +/// # Arguments +/// +/// * `client` - A reference to the MQTT client used to publish the message. +/// * `config` - A reference to the application configuration, which contains the base MQTT topic. +/// * `param_code` - A string slice representing the three-letter code of the parameter. +/// * `param_value` - A string containing the value of the parameter to be published. +/// +/// # Example +/// +/// ```rust +/// let client = mqtt::Client::new("mqtt://broker.hivemq.com:1883").unwrap(); +/// let config = Arc::new(AppConfig { mqtt_base_topic: "speeduino/".to_string() }); +/// let param_code = "RPM"; +/// let param_value = "3000".to_string(); +/// +/// publish_param_to_mqtt(&client, &config, param_code, param_value); +/// ``` fn publish_param_to_mqtt( client: &mqtt::Client, config: &Arc, param_code: &str, param_value: String, ) { - // Concatenate the three-letter code to the base MQTT topic let topic = format!("{}{}", config.mqtt_base_topic, param_code); - - // Specify the desired QoS level - let qos = 1; // Specify the desired QoS level, adjust as needed - - // Create a message and publish it to the MQTT topic + let qos = 1; let message = mqtt::Message::new(&topic, param_value, qos); - client - .publish(message) - .expect("Failed to publish message to MQTT"); + + if let Err(e) = client.publish(message) { + eprintln!("Failed to publish message to MQTT: {}", e); + } } #[cfg(test)] diff --git a/src/ecu_serial_comms_handler.rs b/src/ecu_serial_comms_handler.rs index ce6e0c0..58bf7d5 100644 --- a/src/ecu_serial_comms_handler.rs +++ b/src/ecu_serial_comms_handler.rs @@ -1,13 +1,14 @@ use crate::config::{load_configuration, AppConfig}; use crate::ecu_data_parser::process_speeduino_realtime_data; use crate::mqtt_handler::setup_mqtt; +use atty::Stream; use lazy_static::lazy_static; +use paho_mqtt as mqtt; use serialport::SerialPort; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use atty::Stream; lazy_static! { /// Interval between commands sent to the ECU. @@ -33,20 +34,50 @@ pub fn setup_serial_port(config: &AppConfig) -> Result, seri "Connecting to port: {}, baud rate: {}", config.port_name, config.baud_rate ); - serialport::new(&config.port_name, config.baud_rate as u32) + + let port = serialport::new(&config.port_name, config.baud_rate as u32) .timeout(Duration::from_millis(1000)) - .open() + .open(); + + match port { + Ok(p) => Ok(p), + Err(e) => { + eprintln!("Failed to open serial port: {}", e); + Err(e) + } + } } -/// Read data from the provided serial port and process it. +/// Starts the ECU communication process. /// -/// This function continuously reads data from the serial port, processes the engine data, -/// and communicates with the MQTT broker based on the provided configuration. +/// This function initializes the necessary components for communication with the Speeduino ECU, +/// including setting up the MQTT client and serial port. It then spawns a separate thread to handle +/// the communication with the ECU and processes user input to control the communication thread. +/// +/// # Arguments +/// +/// * `config` - The application configuration containing settings for the serial port and MQTT client. +/// +/// # Behavior +/// +/// The function performs the following steps: +/// 1. Creates an `Arc` for the application configuration. +/// 2. Sets up the MQTT client and handles any errors that occur during setup. +/// 3. Sets up the serial port and handles any errors that occur during setup. +/// 4. Creates a channel for communication between the main thread and the communication thread. +/// 5. Spawns a separate thread to handle the communication with the ECU. +/// 6. Handles user input from the command line to control the communication thread. +/// +/// If the program is running interactively (i.e., attached to a terminal), it will continuously +/// read lines from the standard input. If the input is "q", it will send a quit command to the +/// communication thread and set the `should_exit` flag to true, then terminate the program. +/// If the input is not recognized, it will prompt the user to type "q" to exit. +/// +/// If the program is not running interactively (i.e., running as a service), it will run an +/// empty loop to keep the program active. pub fn start_ecu_communication(config: AppConfig) { - let arc_config = Arc::new(config); - // Setup MQTT outside the loop let mqtt_client = match setup_mqtt(&arc_config) { Ok(client) => client, Err(err) => { @@ -63,84 +94,135 @@ pub fn start_ecu_communication(config: AppConfig) { } }; - let (sender, receiver) = mpsc::channel(); // Create a channel for communication between threads + let (sender, receiver) = mpsc::channel(); let arc_sender = Arc::new(Mutex::new(sender)); - - // Flag to indicate whether the program should exit let should_exit = Arc::new(Mutex::new(false)); let arc_config_thread = arc_config.clone(); + let mqtt_client_thread = mqtt_client.clone(); + let port_thread = port.clone(); + let should_exit_thread = should_exit.clone(); - thread::spawn({ - let mqtt_client = mqtt_client.clone(); - let port = port.clone(); - let should_exit = should_exit.clone(); - - move || { - let mut last_send_time = Instant::now(); - let mut connected = false; // Flag to track connection status - println!("Connecting to Speeduino ECU.."); - - loop { - let elapsed_time = last_send_time.elapsed(); - if elapsed_time >= *COMMAND_INTERVAL { - // Read the entire engine data message length in the buffer - let engine_data = read_engine_data(&mut port.lock().unwrap()); - - // Process the engine data only if it's not empty - if !engine_data.is_empty() { - process_speeduino_realtime_data(&engine_data, &arc_config_thread, &mqtt_client); - - // Print the connection message only if not connected - if !connected { - println!("Successfully connected to Speeduino ECU"); - connected = true; - } - } - - last_send_time = Instant::now(); - } else { - // Sleep for a short duration to avoid busy waiting - thread::sleep(Duration::from_millis(15)); - } - - // Check for a quit command from the main thread - if let Ok(message) = receiver.try_recv() { - if message == "q" { - println!("Received quit command. Exiting the communication thread."); - break; - } - } - - // Check if the main thread has signaled to exit - if *should_exit.lock().unwrap() { - println!("Exiting the communication thread."); - break; + thread::spawn(move || { + communication_thread( + mqtt_client_thread, + port_thread, + arc_config_thread, + receiver, + should_exit_thread, + ); + }); + + handle_user_input(arc_sender, should_exit); +} + +/// Handles the communication with the Speeduino ECU. +/// +/// This function runs in a separate thread and continuously communicates with the Speeduino ECU. +/// It reads engine data at regular intervals, processes the data, and sends it to the MQTT client. +/// It also listens for quit commands from the main thread and exits the loop when a quit command is received. +/// +/// # Arguments +/// +/// * `mqtt_client` - The MQTT client used to publish engine data. +/// * `port` - A thread-safe reference to the serial port used for communication with the ECU. +/// * `arc_config` - A thread-safe reference to the application configuration. +/// * `receiver` - A channel receiver used to receive messages from the main thread. +/// * `should_exit` - A thread-safe flag that indicates whether the communication thread should exit. +/// +/// # Behavior +/// +/// The function enters a loop where it performs the following actions: +/// 1. Checks if the elapsed time since the last send is greater than or equal to the command interval. +/// 2. Reads engine data from the serial port. +/// 3. Processes the engine data and sends it to the MQTT client if the data is not empty. +/// 4. Prints a connection message if the connection to the ECU is successful. +/// 5. Sleeps for a short duration to avoid busy waiting. +/// 6. Checks for a quit command from the main thread and exits the loop if a quit command is received. +/// 7. Checks if the main thread has signaled to exit and exits the loop if the flag is set. +fn communication_thread( + mqtt_client: mqtt::Client, + port: Arc>>, + arc_config: Arc, + receiver: mpsc::Receiver, + should_exit: Arc>, +) { + let mut last_send_time = Instant::now(); + let mut connected = false; + println!("Connecting to Speeduino ECU.."); + + loop { + let elapsed_time = last_send_time.elapsed(); + if elapsed_time >= *COMMAND_INTERVAL { + let engine_data = read_engine_data(&mut port.lock().unwrap()); + + if !engine_data.is_empty() { + process_speeduino_realtime_data(&engine_data, &arc_config, &mqtt_client); + + if !connected { + println!("Successfully connected to Speeduino ECU"); + connected = true; } } + + last_send_time = Instant::now(); + } else { + thread::sleep(Duration::from_millis(15)); + } + + if let Ok(message) = receiver.try_recv() { + if message == "q" { + println!("Received quit command. Exiting the communication thread."); + break; + } } - }); - + if *should_exit.lock().unwrap() { + println!("Exiting the communication thread."); + break; + } + } +} + +/// Handles user input from the command line. +/// +/// This function runs in the main thread and listens for user input from the command line. +/// If the input is "q", it signals the communication thread to exit and terminates the program. +/// If the input is not recognized, it prompts the user to type "q" to exit. +/// +/// # Arguments +/// +/// * `arc_sender` - An `Arc>>` used to send messages to the communication thread. +/// * `should_exit` - An `Arc>` flag that indicates whether the program should exit. +/// +/// # Behavior +/// +/// If the program is running interactively (i.e., attached to a terminal), it will continuously +/// read lines from the standard input. If the input is "q", it will send a quit command to the +/// communication thread and set the `should_exit` flag to true, then terminate the program. +/// If the input is not recognized, it will prompt the user to type "q" to exit. +/// +/// If the program is not running interactively (i.e., running as a service), it will run an +/// empty loop to keep the program active. +fn handle_user_input(arc_sender: Arc>>, should_exit: Arc>) { let is_interactive = atty::is(Stream::Stdin); if is_interactive { - // Add a loop in the main thread to handle user input loop { let mut input = String::new(); match std::io::stdin().read_line(&mut input) { - Ok(0) => break, // EOF + Ok(0) => break, Ok(_) => { let trimmed_input = input.trim(); - - // Send quit command to the communication thread - arc_sender.lock().unwrap().send(trimmed_input.to_string()).unwrap(); - + arc_sender + .lock() + .unwrap() + .send(trimmed_input.to_string()) + .unwrap(); + if trimmed_input.eq_ignore_ascii_case("q") { - // Signal the communication thread to exit *should_exit.lock().unwrap() = true; println!("Shutting down. Goodbye!"); - // Terminate the entire program std::process::exit(0); } else { println!("Unknown command. Type 'q' to exit."); @@ -153,12 +235,10 @@ pub fn start_ecu_communication(config: AppConfig) { } } } else { - // Running as a service, run empty loop to keep program active. loop { thread::sleep(Duration::from_millis(15)); } } - } /// Read the entire engine data message length in the buffer. @@ -179,7 +259,7 @@ fn read_engine_data(port: &mut Box) -> Vec { // Send "A" command if let Err(e) = port.write_all("A".as_bytes()) { - println!("Error sending command to the ECU: {:?}", e); + eprintln!("Error sending command to the ECU: {:?}", e); return engine_data; } @@ -187,7 +267,7 @@ fn read_engine_data(port: &mut Box) -> Vec { loop { match port.read(serial_buf.as_mut_slice()) { Ok(t) if t > 0 => { - engine_data.extend_from_slice(&serial_buf[0..t]); + engine_data.extend_from_slice(&serial_buf[..t]); // Check if the engine data message is complete if engine_data.len() >= *ENGINE_DATA_MESSAGE_LENGTH { @@ -195,36 +275,19 @@ fn read_engine_data(port: &mut Box) -> Vec { } } Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => { - println!("Read timed out"); + eprintln!("Read timed out"); break; } Err(e) => { - println!("{:?}", e); + eprintln!("Error reading from serial port: {:?}", e); break; } - Ok(_) => todo!(), + Ok(_) => { + // No data read, continue the loop + continue; + } } } engine_data } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_setup_serial_port() { - let config = AppConfig { - port_name: String::from("/dev/ttyUSB0"), - baud_rate: 9600, - mqtt_host: String::from("test.example.com"), - mqtt_port: 1883, - mqtt_base_topic: String::from("speeduino"), - ..Default::default() - }; - - let result = setup_serial_port(&config); - assert!(result.is_ok()); - } -} diff --git a/src/main.rs b/src/main.rs index ab23a1a..451336d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,14 +19,14 @@ /// /// - `main()`: The main function that starts the ECU communication and displays the welcome message. /// - `displayWelcome()`: Function to display a graphical welcome message. - mod config; mod ecu_data_parser; mod ecu_serial_comms_handler; mod mqtt_handler; -use gumdrop::Options; -use ecu_serial_comms_handler::start_ecu_communication; use crate::config::load_configuration; +use crate::config::AppConfig; +use ecu_serial_comms_handler::start_ecu_communication; +use gumdrop::Options; /// Define options for the program. #[derive(Debug, Options)] @@ -45,18 +45,55 @@ fn print_help() { println!(" -c, --config FILE Sets a custom config file path"); } -/// Displays a graphical welcome message. +/// Displays the welcome message and instructions for the Speeduino To MQTT Processor application. +/// +/// This function performs the following tasks: +/// - Prints a welcome message in green. +/// - Displays a car logo in red. +/// - Provides instructions on how to use the application. +/// - Lists the available commands for the user. fn display_welcome() { + println!("\x1b[1;32m"); // Set text color to green println!("\nWelcome to Speeduino To MQTT Processor!\n"); println!("==================================="); + + // Display car logo in red + println!("\x1b[1;31m"); // Set text color to red + println!(" ______"); + println!(" // ||\\ \\"); + println!(" ____//___||_\\ \\___"); + println!(" ) _ _ \\"); + println!(" |_/ \\________/ \\___|"); + println!("___\\_/________\\_/______"); + println!("\x1b[1;32m"); // Set text color back to green + + println!("===================================\n"); + + println!( + "This application processes data from Speeduino ECU and publishes it to an MQTT broker." + ); + println!("Ensure your Speeduino ECU is connected and configured properly."); println!("Press 'q' to quit the application."); println!("===================================\n"); + + // Display a list of available commands + println!("Available Commands:"); + println!("q - Quit the application"); + println!("===================================\n"); + + println!("\x1b[0m"); // Reset text color } #[tokio::main] -/// The main function that starts the ECU communication and displays the welcome message. +/// The main entry point of the application. +/// +/// This function performs the following tasks: +/// - Parses command-line arguments. +/// - Displays a help message if requested. +/// - Displays a welcome message. +/// - Loads the configuration file. +/// - Starts ECU communication. async fn main() { - // Parse CLI arguments using gumdrop let opts = MyOptions::parse_args_default_or_exit(); @@ -69,19 +106,31 @@ async fn main() { // Display welcome message display_welcome(); - // Load configuration, set up serial port, and start processing - let config_path = opts.config.as_deref(); - let config = match load_configuration(config_path) { - Ok(config) => config, - Err(err) => { - // Handle the error gracefully, print a message, and exit - eprintln!("Error loading configuration: {}", err); - std::process::exit(1); - } - }; + // Load configuration + let config = load_config_or_exit(opts.config.as_deref()); // Start ECU communication - start_ecu_communication(config.clone()); + start_ecu_communication(config); +} + +/// Loads the configuration file or exits the application if an error occurs. +/// +/// # Arguments +/// +/// * `config_path` - An optional path to the configuration file. +/// +/// # Returns +/// +/// * `Config` - The loaded configuration. +fn load_config_or_exit(config_path: Option<&str>) -> AppConfig { + match load_configuration(config_path) { + Ok(config) => config, + Err(err) => { + // Handle the error gracefully, print a message, and exit + eprintln!("Error loading configuration: {}", err); + std::process::exit(1); + } + } } #[cfg(test)] diff --git a/src/mqtt_handler.rs b/src/mqtt_handler.rs index 16747d0..1c4f77b 100644 --- a/src/mqtt_handler.rs +++ b/src/mqtt_handler.rs @@ -36,11 +36,10 @@ pub fn setup_mqtt(config: &Arc) -> Result { Ok(cli) // Return the MQTT client after successful connection. } - #[cfg(test)] mod tests { use super::*; - use std::sync::Mutex; + use std::sync::{Arc, Mutex}; #[test] fn test_setup_mqtt() { @@ -55,7 +54,8 @@ mod tests { }); // Use a Mutex to ensure the test runs sequentially - let _guard = Mutex::new().lock().unwrap(); + let mutex = Mutex::new(()); + let _guard = mutex.lock().unwrap(); // Test the setup_mqtt function let result = setup_mqtt(&config);