5 releases
| 0.1.48 | Jan 25, 2026 |
|---|---|
| 0.1.47 | Jan 25, 2026 |
| 0.1.42 | Jan 15, 2026 |
| 0.1.23 | Dec 30, 2025 |
| 0.1.0 | Nov 24, 2025 |
#249 in Hardware support
Used in 3 crates
(2 directly)
1MB
17K
SLoC
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
- Separation of Concerns: Nodes focus on business logic, controllers handle hardware
- Testability: Easy to mock and test hardware interactions
- Composability: Mix and match controllers, wrap them with middleware
- Hot-swappable: Switch between real hardware and simulation
- 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 camerasImuController: For IMUs and orientation sensorsLidarController: For 2D and 3D LiDARsMotorController: 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 gradientsMockImuController: Generates synthetic IMU data with simulated motionMockLidarController: Generates synthetic laser scansMockMotorController: 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:
- Implement the base
Controllertrait - Implement the appropriate domain trait (
CameraController,ImuController, etc.) - Define your configuration struct
- 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
- Trait-based: Use traits for abstraction and polymorphism
- Type-safe: Strong typing with generics where appropriate
- Async-first: All I/O operations are async
- Error-transparent: Controllers expose hardware errors clearly
- Zero-copy where possible: Avoid unnecessary data copying
- Composable: Controllers can wrap other controllers
Related Documentation
- Controller Layer Design - Complete design document
- Driver Documentation - Hardware driver implementations
- Core Framework - Core types and traits
Dependencies
~29–49MB
~673K SLoC