mecha10-controllers

Crates.iomecha10-controllers
lib.rsmecha10-controllers
version0.1.48
created_at2025-11-24 21:10:47.244221+00
updated_at2026-01-25 23:05:51.744379+00
descriptionControl algorithms and controllers for the Mecha10 robotics framework
homepage
repositoryhttps://github.com/mecha10/mecha10
max_upload_size
id1948676
size200,532
Peter C (PeterChauYEG)

documentation

README

Mecha10 Controllers

Hardware controller abstractions for the Mecha10 framework.

Overview

The controllers package provides a composable layer between hardware drivers and nodes. Controllers wrap device SDKs/libraries and provide standardized interfaces for nodes to manage hardware.

Architecture

┌─────────────────┐
│     Node        │  (Business logic)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   Controller    │  (Device management + SDK abstraction)
└────────┬────────┘
         │
         ▼
  ┌──────────────┐
  │ Hardware SDK │  (realsense-rust, bno055, etc.)
  └──────────────┘

Benefits

  1. Separation of Concerns: Nodes focus on business logic, controllers handle hardware
  2. Testability: Easy to mock and test hardware interactions
  3. Composability: Mix and match controllers, wrap them with middleware
  4. Hot-swappable: Switch between real hardware and simulation
  5. Consistent Interfaces: Standard error handling and capabilities discovery

Core Traits

Controller - Base trait

All controllers implement this trait providing:

  • Initialization and configuration
  • Start/stop lifecycle
  • Health checks
  • Capability discovery
use mecha10_controllers::{Controller, ControllerHealth};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = MyControllerConfig::default();
    let mut controller = MyController::init(config).await?;

    controller.start().await?;

    let health = controller.health_check().await;
    assert_eq!(health, ControllerHealth::Healthy);

    controller.stop().await?;
    Ok(())
}

Domain-Specific Traits

  • CameraController: For RGB, depth, and RGBD cameras
  • ImuController: For IMUs and orientation sensors
  • LidarController: For 2D and 3D LiDARs
  • MotorController: For motors and actuators

Examples

Using a Camera Controller

use mecha10_controllers::{Controller, CameraController};
use mecha10_controllers::mock::MockCameraController;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize camera
    let config = MockCameraConfig::default();
    let mut camera = MockCameraController::init(config).await?;

    // Start streaming
    camera.start().await?;

    // Capture frames
    loop {
        let frame = camera.capture_frame().await?;

        match frame {
            CameraFrame::Rgb(rgb) => {
                println!("RGB frame: {}x{}", rgb.width, rgb.height);
            }
            CameraFrame::Rgbd { color, depth, .. } => {
                println!("RGBD frame: {}x{}", color.width, color.height);
            }
            _ => {}
        }

        tokio::time::sleep(tokio::time::Duration::from_millis(33)).await;
    }

    Ok(())
}

Using an IMU Controller

use mecha10_controllers::{Controller, ImuController};
use mecha10_controllers::mock::MockImuController;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = MockImuConfig::default();
    let mut imu = MockImuController::init(config).await?;

    imu.start().await?;

    // Check calibration
    let status = imu.calibration_status();
    if !status.is_fully_calibrated() {
        println!("Calibrating IMU...");
        imu.calibrate().await?;
    }

    // Read IMU data
    loop {
        let data = imu.read_imu().await?;
        println!("Orientation: roll={}, pitch={}, yaw={}",
                 data.roll, data.pitch, data.yaw);

        tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
    }

    Ok(())
}

Using a Motor Controller

use mecha10_controllers::{Controller, MotorController};
use mecha10_controllers::mock::MockMotorController;
use mecha10_core::actuator::Twist;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = MockMotorConfig::default();
    let mut motors = MockMotorController::init(config).await?;

    motors.start().await?;

    // Send velocity commands
    let twist = Twist {
        linear: 0.5,  // 0.5 m/s forward
        angular: 0.1, // 0.1 rad/s rotation
    };

    motors.set_twist(twist).await?;

    // Get encoder readings
    let (left, right) = motors.get_encoders().await?;
    println!("Encoders: left={}, right={}", left, right);

    // Emergency stop
    motors.emergency_stop().await?;

    Ok(())
}

Mock Implementations

All controller traits have mock implementations for testing:

  • MockCameraController: Generates synthetic RGB/depth images with gradients
  • MockImuController: Generates synthetic IMU data with simulated motion
  • MockLidarController: Generates synthetic laser scans
  • MockMotorController: Simulates differential drive motors with encoders

Use these in your tests:

#[cfg(test)]
mod tests {
    use mecha10_controllers::mock::*;
    use mecha10_controllers::{Controller, CameraController};

    #[tokio::test]
    async fn test_camera_capture() {
        let config = MockCameraConfig::default();
        let mut camera = MockCameraController::init(config).await.unwrap();

        camera.start().await.unwrap();

        let frame = camera.capture_frame().await.unwrap();
        // Test your code with the mock frame

        camera.stop().await.unwrap();
    }
}

Creating Custom Controllers

To create a controller for new hardware:

  1. Implement the base Controller trait
  2. Implement the appropriate domain trait (CameraController, ImuController, etc.)
  3. Define your configuration struct
  4. Handle initialization, start/stop, and error recovery

Example skeleton:

use mecha10_controllers::{Controller, CameraController, ControllerCapabilities, ControllerHealth};
use async_trait::async_trait;

pub struct MyCustomCameraConfig {
    pub device_path: String,
    pub resolution: (u32, u32),
}

pub struct MyCustomCameraController {
    config: MyCustomCameraConfig,
    // Your SDK handle here
}

#[async_trait]
impl Controller for MyCustomCameraController {
    type Config = MyCustomCameraConfig;
    type Error = anyhow::Error;

    async fn init(config: Self::Config) -> Result<Self, Self::Error> {
        // Initialize your hardware SDK
        Ok(Self { config })
    }

    async fn start(&mut self) -> Result<(), Self::Error> {
        // Start data acquisition
        Ok(())
    }

    async fn stop(&mut self) -> Result<(), Self::Error> {
        // Stop gracefully
        Ok(())
    }

    async fn health_check(&self) -> ControllerHealth {
        // Check device health
        ControllerHealth::Healthy
    }

    fn capabilities(&self) -> ControllerCapabilities {
        ControllerCapabilities::new("camera", "my_custom_camera")
            .with_vendor("MyCompany")
            .with_feature("depth", false)
    }
}

#[async_trait]
impl CameraController for MyCustomCameraController {
    type Frame = CameraFrame;

    async fn capture_frame(&mut self) -> Result<Self::Frame, Self::Error> {
        // Capture frame from SDK
        todo!()
    }

    // Implement other required methods...
}

Usage in Nodes

Nodes should accept controllers via dependency injection:

use mecha10_controllers::{Controller, CameraController};
use mecha10_core::prelude::*;

pub struct VisionNode {
    camera: Box<dyn CameraController<Frame = CameraFrame>>,
}

impl VisionNode {
    pub async fn new(camera: Box<dyn CameraController<Frame = CameraFrame>>) -> Self {
        Self { camera }
    }

    pub async fn run(&mut self, ctx: &Context) -> Result<()> {
        self.camera.start().await?;

        loop {
            let frame = self.camera.capture_frame().await?;

            // Process frame...

            // Publish to topics...
        }
    }
}

Design Principles

  1. Trait-based: Use traits for abstraction and polymorphism
  2. Type-safe: Strong typing with generics where appropriate
  3. Async-first: All I/O operations are async
  4. Error-transparent: Controllers expose hardware errors clearly
  5. Zero-copy where possible: Avoid unnecessary data copying
  6. Composable: Controllers can wrap other controllers

Related Documentation

Commit count: 0

cargo fmt