diff --git a/rfe/README.md b/rfe/README.md new file mode 100644 index 0000000..3c3f9b4 --- /dev/null +++ b/rfe/README.md @@ -0,0 +1,56 @@ +# Real-time Framework for Embedded systems (RFE) + +RFE is a framework for building real-time embedded applications in Rust. It provides a message-passing architecture for inter-application communication, time management, and scheduling at different rates. + +## Features + +- Message-passing architecture for inter-application communication +- Time management for both system and monotonic time +- Scheduling of applications at different rates (1Hz to 100Hz) +- Support for different platforms through feature flags +- Connectors for communication between instances (TCP, UDP, Memory) + +## Usage + +To use RFE, you need to create an RfeInstance, add applications to it, and then run it at 100Hz. Applications must implement the App trait. + +```rust +use rfe::*; + +struct MyApp; + +impl App for MyApp { + fn init(&mut self, rfe: &mut Rfe) -> anyhow::Result<()> { + // Initialize the application + Ok(()) + } + + fn run(&mut self, rfe: &mut Rfe) { + // Run the application + } + + fn hk(&mut self, rfe: &mut Rfe) { + // Generate housekeeping data + } + + fn out_data(&mut self, rfe: &mut Rfe) { + // Generate output data + } + + fn get_app_rate(&self) -> Rate { + Rate::Hz10 // Run at 10Hz + } +} +``` + +## Platform Support + +RFE supports multiple platforms through feature flags: + +- `std`: Standard library support (Unix, Windows) +- `rp2040`: Raspberry Pi Pico support +- `reflect`: Runtime type information for debugging + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/rfe/src/connector.rs b/rfe/src/connector.rs index a2e8a9b..871dfdb 100644 --- a/rfe/src/connector.rs +++ b/rfe/src/connector.rs @@ -1,11 +1,35 @@ +/*! + * Connector module for RFE + * + * This module provides the Connector trait and implementations for various + * communication methods between RFE instances, including: + * - Memory connectors for inter-process communication + * - TCP connectors for network communication + * - UDP connectors for network communication + */ use core::fmt::Debug; use crate::msg::MsgPacket; extern crate alloc; use alloc::vec::Vec; +/// Connector trait for inter-instance communication +/// +/// This trait defines the interface for connectors that enable communication +/// between RFE instances. Connectors are responsible for sending and receiving +/// messages between instances. pub trait Connector: Debug { + /// Send messages to another instance + /// + /// # Arguments + /// * `msgs` - The messages to send fn send(&mut self, msgs: Vec); + + /// Receive messages from another instance + /// + /// # Returns + /// * `Some(Vec)` - If messages are available + /// * `None` - If no messages are available fn recv(&mut self) -> Option>; } diff --git a/rfe/src/lib.rs b/rfe/src/lib.rs index 9a23f69..9035981 100644 --- a/rfe/src/lib.rs +++ b/rfe/src/lib.rs @@ -1,25 +1,100 @@ #![no_std] +/*! + * Real-time Framework for Embedded systems (RFE) + * + * RFE is a framework for building real-time embedded applications in Rust. + * It provides a message-passing architecture for inter-application communication, + * time management, and scheduling at different rates. + * + * # Features + * + * - Message-passing architecture for inter-application communication + * - Time management for both system and monotonic time + * - Scheduling of applications at different rates (1Hz to 100Hz) + * - Support for different platforms through feature flags + * - Connectors for communication between instances (TCP, UDP, Memory) + * + * # Usage + * + * To use RFE, you need to create an RfeInstance, add applications to it, + * and then run it at 100Hz. Applications must implement the App trait. + * + * ```rust + * use rfe::*; + * + * struct MyApp; + * + * impl App for MyApp { + * fn init(&mut self, rfe: &mut Rfe) -> anyhow::Result<()> { + * // Initialize the application + * Ok(()) + * } + * + * fn run(&mut self, rfe: &mut Rfe) { + * // Run the application + * } + * + * fn hk(&mut self, rfe: &mut Rfe) { + * // Generate housekeeping data + * } + * + * fn out_data(&mut self, rfe: &mut Rfe) { + * // Generate output data + * } + * + * fn get_app_rate(&self) -> Rate { + * Rate::Hz10 // Run at 10Hz + * } + * } + * ``` + */ + #[cfg(feature = "std")] extern crate std; +/// Connector module for inter-instance communication pub mod connector; use bincode::config::Configuration; +/// Message module for defining message types pub mod msg; +/// Core RFE implementation mod rfe; pub use rfe::*; +/// Reflection module for runtime type information #[cfg(feature = "reflect")] pub mod reflect; +/// Macros for RFE pub use macros; +/// Serial communication module pub mod serial; +/// Time management module pub mod time; +/// Utility functions and types pub mod utils; +/// Bincode configuration for serialization pub const BINCODE_CONFIG: Configuration = bincode::config::standard(); +/// Macro for unwrapping a Result and printing an error message if it fails +/// +/// # Arguments +/// * `$x` - The Result to unwrap +/// * `$msg` - The error message to print if the Result is an Err +/// +/// # Example +/// ```rust +/// use rfe::unwrap_print_err; +/// use log::error; +/// +/// fn main() { +/// let result: Result<(), &str> = Err("error"); +/// unwrap_print_err!(result, "Failed to do something"); +/// } +/// ``` #[macro_export] macro_rules! unwrap_print_err { ($x:expr, $msg: tt) => { diff --git a/rfe/src/rfe.rs b/rfe/src/rfe.rs index ec4691c..4cdf567 100644 --- a/rfe/src/rfe.rs +++ b/rfe/src/rfe.rs @@ -1,3 +1,12 @@ +/*! + * Real-time Framework for Embedded systems (RFE) + * + * This module provides the core functionality for the RFE framework: + * - Application scheduling at different rates (1Hz to 100Hz) + * - Message passing between applications + * - Time management for both system and monotonic time + * - Connector management for inter-instance communication + */ extern crate alloc; use core::cell::RefCell; @@ -13,99 +22,207 @@ use crate::{ time::{TimeData, TimeDriver}, }; +/// Housekeeping data trait +/// +/// This trait is used to mark types that can be used as housekeeping data. +/// Housekeeping data is used to monitor the health and status of applications. pub trait Hk: Sized + Clone + Copy + 'static + Send + Sync {} + +/// Blanket implementation for all types that meet the requirements impl Hk for T where T: Sized + Clone + Copy + 'static + Send + Sync {} +/// Output data trait +/// +/// This trait is used to mark types that can be used as output data. +/// Output data is the primary data produced by applications. pub trait OutData: Sized + Clone + Copy + 'static + Send + Sync {} + +/// Blanket implementation for all types that meet the requirements impl OutData for T where T: Sized + Clone + Copy + 'static + Send + Sync {} +/// Application execution rates +/// +/// This enum defines the rates at which applications can be scheduled. +/// The RFE framework runs at 100Hz, and applications can be scheduled +/// at various rates derived from this base rate. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Rate { + /// 1Hz (once per second) Hz1, + /// 5Hz (5 times per second) Hz5, + /// 10Hz (10 times per second) Hz10, + /// 20Hz (20 times per second) Hz20, + /// 50Hz (50 times per second) Hz50, + /// 100Hz (100 times per second, every tick) Hz100, } +/// Reference to an RfeTime instance +/// +/// This type alias is used to share a single time reference between multiple RFE instances. type RfeTimeRef<'a> = Rc>>; +/// Time management for RFE +/// +/// This struct manages time for the RFE framework, providing both system time +/// and monotonic time through a TimeDriver implementation. pub struct RfeTime<'a> { + /// Time data including scheduler counter and time offset time_data: TimeData, + /// Driver for time operations time_driver: &'a dyn TimeDriver, } +/// Main RFE instance +/// +/// This struct represents a single RFE instance, which can contain multiple applications +/// and connectors. It manages the scheduling of applications and the routing of messages. pub struct RfeInstance<'a> { + /// List of applications registered with this instance app_list: HashMap<&'a str, AppRef<'a>>, + /// Reference to the time management time: RfeTimeRef<'a>, + /// The instance identifier #[allow(dead_code)] instance: Instance, + /// List of connectors for inter-instance communication connectors: Vec>, + /// Scheduler counter, incremented on each tick sch_counter: u64, } +/// Reference to an application +/// +/// This struct holds a reference to an application and its associated RFE instance. +/// It also stores the rates at which the application should be run. pub struct AppRef<'a> { + /// Reference to the application app: &'a mut dyn App, + /// Rate at which the application's run method should be called app_rate: Rate, + /// Rate at which the application's out_data method should be called out_data_rate: Rate, + /// Rate at which the application's hk method should be called hk_rate: Rate, + /// RFE instance for this application rfe: Rfe<'a>, } +/// Core RFE interface for applications +/// +/// This struct provides the interface for applications to interact with the RFE framework. +/// It handles message subscription, sending, and receiving, as well as time management. pub struct Rfe<'a> { + /// Messages this application is subscribed to subscriptions: HashSet, + /// Messages to be sent to other applications or connectors msgs_to_send: Vec, - msgs_recevied: VecDeque, + /// Messages received from other applications or connectors + msgs_received: VecDeque, + /// The instance this RFE belongs to instance: Instance, + /// Reference to the time management time: RfeTimeRef<'a>, + /// Flag indicating if subscriptions have been updated subs_updated: bool, } +/// State of a connector +/// +/// This struct holds a reference to a connector and its associated state. +/// It tracks subscriptions and subscription request timing. #[derive(Debug)] pub struct ConnectorState<'a> { + /// Reference to the connector connector: &'a mut dyn Connector, + /// Messages this connector is subscribed to subscriptions: HashSet, + /// Flag indicating if subscriptions have been received subs_received: bool, + /// Scheduler counter value when subscriptions were last requested subs_last_requested: u64, } impl<'a> Rfe<'a> { + /// Creates a new RFE instance + /// + /// # Arguments + /// * `instance` - The instance this RFE belongs to + /// * `time` - Reference to the time management pub fn new(instance: Instance, time: RfeTimeRef<'a>) -> Self { Self { subscriptions: HashSet::new(), msgs_to_send: Vec::new(), - msgs_recevied: VecDeque::new(), + msgs_received: VecDeque::new(), instance, subs_updated: false, time, } } + /// Returns the instance this RFE belongs to pub fn get_instance(&self) -> Instance { - return self.instance; + self.instance } + /// Subscribe to a message + /// + /// This method subscribes the application to a specific message type. + /// The application will receive messages of this type from other applications + /// and connectors. + /// + /// # Arguments + /// * `msg` - The message type to subscribe to pub fn subscribe(&mut self, msg: TargetMsg) { self.subscriptions.insert(msg); self.subs_updated = true; } + /// Subscribe to multiple messages + /// + /// This method subscribes the application to multiple message types. + /// The application will receive messages of these types from other applications + /// and connectors. + /// + /// # Arguments + /// * `msgs` - The message types to subscribe to pub fn subscribe_all>(&mut self, msgs: T) { self.subscriptions.extend(msgs.into_iter()); self.subs_updated = true; } + /// Unsubscribe from a message + /// + /// This method unsubscribes the application from a specific message type. + /// The application will no longer receive messages of this type. + /// + /// # Arguments + /// * `msg` - The message type to unsubscribe from pub fn unsubscribe(&mut self, msg: &TargetMsg) { self.subscriptions.remove(msg); self.subs_updated = true; } + /// Unsubscribe from all messages + /// + /// This method unsubscribes the application from all message types. + /// The application will no longer receive any messages. pub fn unsubscribe_all(&mut self) { self.subscriptions.clear(); self.subs_updated = true; } + /// Send a message + /// + /// This method sends a message from this application to other applications + /// and connectors that are subscribed to this message type. + /// + /// # Arguments + /// * `msg` - The message to send pub fn send(&mut self, msg: Msg) { self.msgs_to_send.push(MsgPacket::new( self.get_instance(), @@ -114,41 +231,110 @@ impl<'a> Rfe<'a> { )); } + /// Send a command to a specific instance + /// + /// This method sends a command to a specific instance. + /// The command will be received by applications in that instance + /// that are subscribed to this message type. + /// + /// # Arguments + /// * `msg` - The command to send + /// * `target` - The target instance to send the command to pub fn send_cmd(&mut self, msg: Msg, target: Instance) { self.msgs_to_send .push(MsgPacket::new(target, msg, self.get_system_time())); } + /// Posts a message to this RFE's receive queue + /// + /// # Arguments + /// * `msg` - The message to post pub fn post_message(&mut self, msg: MsgPacket) { - self.msgs_recevied.push_back(msg); + self.msgs_received.push_back(msg); } + /// Receives a message from this RFE's receive queue + /// + /// # Returns + /// * `Some(MsgPacket)` - If a message is available + /// * `None` - If no message is available pub fn recv(&mut self) -> Option { - self.msgs_recevied.pop_front() + self.msgs_received.pop_front() } - /// time starting from power on or program start + /// Get mission elapsed time + /// + /// This method returns the time in microseconds since power on or program start. + /// It is useful for measuring durations and scheduling events. + /// + /// # Returns + /// * Time in microseconds since power on or program start pub fn get_met_time(&self) -> u64 { let time = self.time.borrow(); time.time_driver.get_monotonic_time(time.time_data) } - /// Time in microseconds relative to system epoch + /// Get system time + /// + /// This method returns the time in microseconds relative to the system epoch. + /// It is useful for timestamping events and correlating with external systems. + /// + /// # Returns + /// * Time in microseconds relative to the system epoch pub fn get_system_time(&self) -> u64 { let time = self.time.borrow(); time.time_driver.get_system_time(time.time_data) } } +/// Application trait for RFE applications +/// +/// This trait defines the interface for applications to interact with the RFE framework. +/// Applications must implement this trait to be scheduled by the RFE framework. pub trait App { + /// Initialize the application + /// + /// This method is called once when the application is added to the RFE instance. + /// Use this method to set up subscriptions and initialize the application state. fn init(&mut self, rfe: &mut Rfe) -> Result<()>; + + /// Run the application + /// + /// This method is called at the rate specified by `get_app_rate()`. + /// Use this method to perform the main application logic. fn run(&mut self, rfe: &mut Rfe); + + /// Generate housekeeping data + /// + /// This method is called at the rate specified by the RFE instance. + /// Use this method to generate housekeeping data for telemetry. fn hk(&mut self, rfe: &mut Rfe); + + /// Generate output data + /// + /// This method is called at the rate specified by the RFE instance. + /// Use this method to generate output data for telemetry. fn out_data(&mut self, rfe: &mut Rfe); + + /// Get the application rate + /// + /// This method returns the rate at which the application should be run. fn get_app_rate(&self) -> Rate; } impl<'a> RfeInstance<'a> { + /// Create a new RFE instance + /// + /// This method creates a new RFE instance with the specified instance identifier + /// and time driver. The instance identifier is used to identify this instance + /// when communicating with other instances. + /// + /// # Arguments + /// * `instance` - The instance identifier + /// * `time_driver` - The time driver to use for time management + /// + /// # Returns + /// * A new RFE instance pub fn new(instance: Instance, time_driver: &'a dyn TimeDriver) -> Self { let time = Rc::new(RefCell::new(RfeTime { time_data: TimeData { @@ -166,32 +352,50 @@ impl<'a> RfeInstance<'a> { } } + /// Add an application to the RFE instance + /// + /// # Arguments + /// * `name` - The name of the application + /// * `app` - The application to add + /// + /// # Returns + /// * `Ok(())` - If the application was added successfully + /// * `Err(...)` - If the application could not be added pub fn add_app(&mut self, name: &'a str, app: &'a mut dyn App) -> Result<()> { if self.app_list.contains_key(name) { return Err(anyhow!( - "failed to add app {name}, already added an app with that name" + "Failed to add app '{name}': an app with that name already exists" )); } + let app_rate = app.get_app_rate(); self.app_list.insert( name, AppRef { - app: app, - app_rate: app_rate, + app, + app_rate, hk_rate: Rate::Hz1, out_data_rate: app_rate, rfe: Rfe::new(self.instance, self.time.clone()), }, ); + // Initialize the application let appref = self.app_list.get_mut(name).unwrap(); if let Err(e) = appref.app.init(&mut appref.rfe) { - error!("app {name} failed to initialize {e}"); + error!("App '{name}' failed to initialize: {e}"); } - return Ok(()); + Ok(()) } + /// Add a connector to the RFE instance + /// + /// Connectors are used to communicate with other RFE instances. + /// They can be used to send and receive messages between instances. + /// + /// # Arguments + /// * `connector` - The connector to add pub fn add_connector(&mut self, connector: &'a mut dyn Connector) { self.connectors.push(ConnectorState { connector, @@ -201,40 +405,31 @@ impl<'a> RfeInstance<'a> { }); } - /// Expected to be called at 100Hz + /// Run the RFE instance + /// + /// This method should be called at 100Hz to schedule applications and process messages. + /// It runs applications, collects messages, and routes them to the appropriate destinations. pub fn run(&mut self) { let mut msgs = Vec::new(); + + // Run applications and collect messages for app in self.app_list.values_mut() { - if app.app_rate == Rate::Hz100 - || (self.sch_counter % 2 == 0 && app.app_rate == Rate::Hz50) - || (self.sch_counter % 5 == 0 && app.app_rate == Rate::Hz20) - || (self.sch_counter % 10 == 0 && app.app_rate == Rate::Hz10) - || (self.sch_counter % 20 == 0 && app.app_rate == Rate::Hz5) - || (self.sch_counter % 100 == 0 && app.app_rate == Rate::Hz1) - { + // Check if the application should run at this tick + if self.should_run_at_rate(app.app_rate) { app.app.run(&mut app.rfe); } - if app.hk_rate == Rate::Hz100 - || (self.sch_counter % 2 == 0 && app.hk_rate == Rate::Hz50) - || (self.sch_counter % 5 == 0 && app.hk_rate == Rate::Hz20) - || (self.sch_counter % 10 == 0 && app.hk_rate == Rate::Hz10) - || (self.sch_counter % 20 == 0 && app.hk_rate == Rate::Hz5) - || (self.sch_counter % 100 == 0 && app.hk_rate == Rate::Hz1) - { + // Check if housekeeping should run at this tick + if self.should_run_at_rate(app.hk_rate) { app.app.hk(&mut app.rfe); } - if app.out_data_rate == Rate::Hz100 - || (self.sch_counter % 2 == 0 && app.out_data_rate == Rate::Hz50) - || (self.sch_counter % 5 == 0 && app.out_data_rate == Rate::Hz20) - || (self.sch_counter % 10 == 0 && app.out_data_rate == Rate::Hz10) - || (self.sch_counter % 20 == 0 && app.out_data_rate == Rate::Hz5) - || (self.sch_counter % 100 == 0 && app.out_data_rate == Rate::Hz1) - { + // Check if output data should run at this tick + if self.should_run_at_rate(app.out_data_rate) { app.app.out_data(&mut app.rfe); } + // Collect messages from the application let new_msgs = core::mem::take(&mut app.rfe.msgs_to_send); msgs.extend(new_msgs); } @@ -365,7 +560,30 @@ impl<'a> RfeInstance<'a> { self.sch_counter += 1; } + /// Helper method to determine if a task should run at the given rate + /// + /// # Arguments + /// * `rate` - The rate to check + /// + /// # Returns + /// * `true` - If the task should run at this tick + /// * `false` - If the task should not run at this tick + fn should_run_at_rate(&self, rate: Rate) -> bool { + match rate { + Rate::Hz100 => true, + Rate::Hz50 => self.sch_counter % 2 == 0, + Rate::Hz20 => self.sch_counter % 5 == 0, + Rate::Hz10 => self.sch_counter % 10 == 0, + Rate::Hz5 => self.sch_counter % 20 == 0, + Rate::Hz1 => self.sch_counter % 100 == 0, + } + } + #[cfg(feature = "std")] + /// Start the RFE instance + /// + /// This method starts the RFE instance and runs it at 100Hz. + /// It blocks the current thread and never returns. pub fn start(&mut self) { use core::time::Duration; use std::{thread::sleep, time::Instant}; diff --git a/rfe/src/time.rs b/rfe/src/time.rs index 3ae4063..01e7d9a 100644 --- a/rfe/src/time.rs +++ b/rfe/src/time.rs @@ -1,17 +1,59 @@ +/*! + * Time management module for RFE + * + * This module provides time management functionality for the RFE framework, + * including: + * - Timestamp type for representing time + * - TimeData struct for storing time-related data + * - TimeDriver trait for platform-specific time implementations + * - Various TimeDriver implementations for different platforms + */ + /// Microseconds timestamp +/// +/// This type represents time in microseconds, either as a duration or +/// as an absolute time relative to some epoch. pub type Timestamp = u64; +/// Time data for RFE +/// +/// This struct stores time-related data for the RFE framework, including +/// the scheduler counter and time offset. #[derive(Debug, Clone, Copy)] pub struct TimeData { + /// Scheduler counter, incremented on each tick pub sch_counter: u64, + /// Time offset in microseconds pub time_offset: Timestamp, } +/// Time driver trait +/// +/// This trait defines the interface for platform-specific time implementations. +/// It provides methods for getting both system time and monotonic time. pub trait TimeDriver { - /// Time in microseconds relative to system epoch + /// Get system time + /// + /// This method returns the time in microseconds relative to the system epoch. + /// It is useful for timestamping events and correlating with external systems. + /// + /// # Arguments + /// * `time_data` - The time data to use + /// + /// # Returns + /// * Time in microseconds relative to the system epoch fn get_system_time(&self, time_data: TimeData) -> Timestamp; - /// Time in microseconds since program start or power on + /// Get monotonic time + /// + /// This method returns the time in microseconds since program start or power on. + /// It is useful for measuring durations and scheduling events. + /// + /// # Arguments + /// * `time_data` - The time data to use + /// + /// # Returns + /// * Time in microseconds since program start or power on fn get_monotonic_time(&self, time_data: TimeData) -> Timestamp; }