mecha10-core 0.6.5

Mecha10 core runtime and foundational types
Documentation
//! Teleoperation message types
//!
//! This module contains message types for teleoperation control from multiple sources
//! (CLI, dashboard, game controllers, etc.)

use crate::messages::Message;
use serde::{Deserialize, Serialize};

/// Input command from any teleoperation source
///
/// Multiple sources can send commands, and the teleop node will arbitrate
/// between them based on priority and timestamps.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeleopInput {
    /// Source identifier (e.g., "cli", "dashboard", "gamepad")
    pub source: String,

    /// Priority level (0-255, higher = more important)
    /// Typical values:
    /// - 255: Emergency stop
    /// - 200: Safety operator override
    /// - 100: Primary operator (gamepad)
    /// - 50: Secondary operator (dashboard)
    /// - 10: Autonomous fallback
    pub priority: u8,

    /// Linear velocity command in m/s
    pub linear: f32,

    /// Angular velocity command in rad/s
    pub angular: f32,

    /// Timestamp in microseconds (from now_micros)
    pub timestamp: u64,

    /// Emergency stop flag - immediately halt all motion
    pub emergency_stop: bool,
}

impl Message for TeleopInput {}

impl TeleopInput {
    /// Create a new teleop input with standard priority
    pub fn new(source: impl Into<String>, linear: f32, angular: f32, timestamp: u64) -> Self {
        Self {
            source: source.into(),
            priority: 50,
            linear,
            angular,
            timestamp,
            emergency_stop: false,
        }
    }

    /// Create an emergency stop command
    pub fn emergency_stop(source: impl Into<String>, timestamp: u64) -> Self {
        Self {
            source: source.into(),
            priority: 255,
            linear: 0.0,
            angular: 0.0,
            timestamp,
            emergency_stop: true,
        }
    }

    /// Set the priority level
    pub fn with_priority(mut self, priority: u8) -> Self {
        self.priority = priority;
        self
    }

    /// Get age in milliseconds
    pub fn age_ms(&self, current_time: u64) -> u64 {
        let age_us = current_time.saturating_sub(self.timestamp);
        age_us / 1000
    }
}

/// Status information from the teleop node
///
/// Published periodically so UIs can display current state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeleopStatus {
    /// Currently active control source
    pub active_source: String,

    /// Age of last command in milliseconds
    pub last_command_age_ms: u64,

    /// Safety system engaged (command timeout or e-stop)
    pub safety_engaged: bool,

    /// Current linear velocity being sent to motors (m/s)
    pub current_linear: f32,

    /// Current angular velocity being sent to motors (rad/s)
    pub current_angular: f32,

    /// Number of commands received since startup
    pub total_commands: u64,

    /// Priority of active source
    pub active_priority: u8,
}

impl Message for TeleopStatus {}

impl TeleopStatus {
    /// Create a new status with default values
    pub fn new() -> Self {
        Self {
            active_source: "none".to_string(),
            last_command_age_ms: 0,
            safety_engaged: false,
            current_linear: 0.0,
            current_angular: 0.0,
            total_commands: 0,
            active_priority: 0,
        }
    }

    /// Check if teleop is actively controlling
    pub fn is_active(&self) -> bool {
        !self.safety_engaged && self.active_source != "none"
    }
}

impl Default for TeleopStatus {
    fn default() -> Self {
        Self::new()
    }
}