Files
rfe/README.md
T

7.3 KiB

Rust Flight Executive (RFE)

RFE is a framework for building real-time embedded applications in Rust, inspired by concepts from NASA's Core Flight Executive (cFE). Rather than being a direct port of cFE to Rust, RFE is designed as an alternative that embraces Rust's idioms and strengths. Unlike cFE which primarily targets computer systems, RFE extends support to microcontrollers and other resource-constrained embedded devices. It provides a message-passing architecture for inter-application communication, time management, and scheduling at different rates. I/O operations are handled either by the standard library (when the std feature is enabled) or by board support packages like rp2040-hal for embedded platforms. These board support packages can be used generically through the traits defined in the embedded-hal crate, allowing for portable code across different embedded platforms.

Overview

RFE is a framework that enables the development of modular, reusable flight software applications in Rust. While it shares some architectural concepts with NASA's cFE, RFE makes different design choices that are more ergonomic and better aligned with Rust's safety features, performance characteristics, and modern development ecosystem. By supporting both standard computers and microcontrollers, RFE offers greater flexibility for embedded systems development across a wide range of hardware platforms.

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)
  • Platform abstraction through feature flags:
    • std: Standard library support (Unix, Windows)
    • rp2040: Raspberry Pi Pico support
    • More platform abstractions planned for future releases
    • Support for custom platforms by implementing a few simple traits
  • Runtime reflection:
    • reflect: Runtime type information that can be used for dynamic command generation and telemetry processing
  • Connectors: A trait-based system for communication between instances, with built-in implementations (TCP, UDP, Memory) and support for custom user-defined connectors
  • Core applications:
    • Data Storage (DS): For telemetry storage
    • Health & Safety (HS): For system monitoring
    • Telemetry Output (TO): For telemetry management
    • Example app: Demonstrating application development

Architecture

RFE follows a modular architecture where applications communicate through a message bus. The core components include:

  • RfeInstance: The main framework instance that manages applications and scheduling. An instance is intended as a runner for apps - multiple apps can be added to a single instance, but thread/task priority is given to each instance rather than to individual apps. For each app to have its own task priority, you will need a separate instance for each app.
  • App trait: Interface that all applications must implement
  • Rfe: Interface for applications to interact with the framework
  • Connectors: A trait-based interface for communication between different RFE instances, allowing users to implement custom message transport mechanisms. Instances can communicate with each other through these connectors.

Getting Started

Prerequisites

  • Rust toolchain (latest stable recommended)
  • For RP2040 development: thumbv6m-none-eabi target

Building

# For standard desktop build
cargo build --package example_build

# For Raspberry Pi Pico
cargo build --package rp_pico_example --target thumbv6m-none-eabi

Creating an Application

Applications in RFE must implement the App trait:

use rfe::*;
use anyhow::Result;

struct MyApp;

impl App for MyApp {
    fn init(&mut self, rfe: &mut Rfe) -> Result<()> {
        // Initialize the application
        // Set up message subscriptions
        rfe.subscribe(TargetMsg::new(rfe.get_instance(), MsgKind::YourMsgKind));
        Ok(())
    }

    fn run(&mut self, rfe: &mut Rfe) {
        // Run the application logic
        // Process received messages
        while let Some(msg) = rfe.recv() {
            // Handle message
        }
    }

    fn hk(&mut self, rfe: &mut Rfe) {
        // Generate housekeeping data
        rfe.send(Msg::YourHkMsg(self.your_hk_data.clone()));
    }

    fn out_data(&mut self, rfe: &mut Rfe) {
        // Generate output data
        rfe.send(Msg::YourOutDataMsg(self.your_out_data.clone()));
    }

    fn get_app_rate(&self) -> Rate {
        // Set the intended rate at which your app runs
        Rate::Hz10 // Run at 10Hz
    }
}

Running an RFE Instance

use rfe::*;
use rfe::connector::MemConnector;
use std::thread;
use std::sync::Arc;

// Create a shared memory connector for inter-instance communication
let (connector1, connector2) = Arc::new(MemConnector::new());

// First instance (you can set the priority of the thread you run the instance in)
thread::spawn(move || {
    // Create a time driver for the first instance
    let time_driver = StdTimeDriver::new();

    // Create the first RFE instance
    let mut instance1 = RfeInstance::new(Instance::Main, &time_driver);

    // Add the memory connector to the instance
    instance1.add_connector(connector1);

    // Add applications to the first instance
    let mut my_app1 = MyApp::new();
    instance1.add_app("my_app1", &mut my_app1).unwrap();

    // Run the first instance
    instance1.start();
});

// Second instance
let time_driver2 = StdTimeDriver::new();
let mut instance2 = RfeInstance::new(Instance::Secondary, &time_driver2);

// Add the memory connector to the second instance
instance2.add_connector(connector_clone);

// Add applications to the second instance
let mut my_app2 = AnotherApp::new();
instance2.add_app("my_app2", &mut my_app2).unwrap();

// Run the second instance
instance2.start();

In this example:

  • Two RFE instances are created, each with its own application
  • The first instance runs in a separate thread. You can set the priority of this thread
  • The second instance runs in the main thread
  • A shared MemConnector allows the instances to communicate with each other
  • Each app can send messages that will be routed to the appropriate instance

Project Structure

  • rfe/: Core framework implementation
  • apps/: Application implementations
    • ds/: Data Storage application
    • hs/: Health & Safety application
    • to/: Telemetry Output application
    • example/: Example application
  • builds/: Example builds for different platforms
    • example_build/: Standard desktop build
    • rp_pico_example/: Raspberry Pi Pico build
    • ground/: Ground system with GUI
  • tools/: Development tools
    • decom/: Telemetry decommutation tool

Platform Support

RFE supports multiple platforms through feature flags:

  • Standard Desktop (std feature): Unix, Windows
  • Raspberry Pi Pico (rp2040 feature): Embedded ARM Cortex-M0+
  • Users can add support for any platform by implementing just a few key traits (primarily the TimeDriver trait)
  • Board support packages utilize the embedded-hal traits for generic, portable hardware abstraction
  • Platform-specific functionality can be easily integrated through Rust's trait system
  • Additional platform support is planned for future releases

The reflect feature can be enabled on any platform to provide runtime type information.