mecha10_core/teleop.rs
1//! Teleoperation message types
2//!
3//! This module contains message types for teleoperation control from multiple sources
4//! (CLI, dashboard, game controllers, etc.)
5
6use crate::messages::Message;
7use serde::{Deserialize, Serialize};
8
9// ============================================================================
10// Teleop Input
11// ============================================================================
12
13/// Input command from any teleoperation source
14///
15/// Multiple sources can send commands, and the teleop node will arbitrate
16/// between them based on priority and timestamps.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TeleopInput {
19 /// Source identifier (e.g., "cli", "dashboard", "gamepad")
20 pub source: String,
21
22 /// Priority level (0-255, higher = more important)
23 /// Typical values:
24 /// - 255: Emergency stop
25 /// - 200: Safety operator override
26 /// - 100: Primary operator (gamepad)
27 /// - 50: Secondary operator (dashboard)
28 /// - 10: Autonomous fallback
29 pub priority: u8,
30
31 /// Linear velocity command in m/s
32 pub linear: f32,
33
34 /// Angular velocity command in rad/s
35 pub angular: f32,
36
37 /// Timestamp in microseconds (from now_micros)
38 pub timestamp: u64,
39
40 /// Emergency stop flag - immediately halt all motion
41 pub emergency_stop: bool,
42}
43
44impl Message for TeleopInput {}
45
46impl TeleopInput {
47 /// Create a new teleop input with standard priority
48 pub fn new(source: impl Into<String>, linear: f32, angular: f32, timestamp: u64) -> Self {
49 Self {
50 source: source.into(),
51 priority: 50, // Default priority
52 linear,
53 angular,
54 timestamp,
55 emergency_stop: false,
56 }
57 }
58
59 /// Create an emergency stop command
60 pub fn emergency_stop(source: impl Into<String>, timestamp: u64) -> Self {
61 Self {
62 source: source.into(),
63 priority: 255,
64 linear: 0.0,
65 angular: 0.0,
66 timestamp,
67 emergency_stop: true,
68 }
69 }
70
71 /// Set the priority level
72 pub fn with_priority(mut self, priority: u8) -> Self {
73 self.priority = priority;
74 self
75 }
76
77 /// Check if command is expired (older than timeout_ms milliseconds)
78 pub fn is_expired(&self, current_time: u64, timeout_ms: u64) -> bool {
79 let age_us = current_time.saturating_sub(self.timestamp);
80 let age_ms = age_us / 1000;
81 age_ms > timeout_ms
82 }
83
84 /// Get age in milliseconds
85 pub fn age_ms(&self, current_time: u64) -> u64 {
86 let age_us = current_time.saturating_sub(self.timestamp);
87 age_us / 1000
88 }
89}
90
91// ============================================================================
92// Teleop Status
93// ============================================================================
94
95/// Status information from the teleop node
96///
97/// Published periodically so UIs can display current state
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TeleopStatus {
100 /// Currently active control source
101 pub active_source: String,
102
103 /// Age of last command in milliseconds
104 pub last_command_age_ms: u64,
105
106 /// Safety system engaged (command timeout or e-stop)
107 pub safety_engaged: bool,
108
109 /// Current linear velocity being sent to motors (m/s)
110 pub current_linear: f32,
111
112 /// Current angular velocity being sent to motors (rad/s)
113 pub current_angular: f32,
114
115 /// Number of commands received since startup
116 pub total_commands: u64,
117
118 /// Priority of active source
119 pub active_priority: u8,
120}
121
122impl Message for TeleopStatus {}
123
124impl TeleopStatus {
125 /// Create a new status with default values
126 pub fn new() -> Self {
127 Self {
128 active_source: "none".to_string(),
129 last_command_age_ms: 0,
130 safety_engaged: false,
131 current_linear: 0.0,
132 current_angular: 0.0,
133 total_commands: 0,
134 active_priority: 0,
135 }
136 }
137
138 /// Check if teleop is actively controlling
139 pub fn is_active(&self) -> bool {
140 !self.safety_engaged && self.active_source != "none"
141 }
142}
143
144impl Default for TeleopStatus {
145 fn default() -> Self {
146 Self::new()
147 }
148}