Skip to main content

mecha10_behavior_runtime/actions/
navigation.rs

1//! Stub navigation action nodes for path planning, following, and arrival checks.
2
3use crate::{BehaviorNode, Context, NodeStatus};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// Body of the `tick` override for a stub node that has nothing to do yet: log a fixed debug
9/// message and immediately report `Success`. `FollowPathNode`/`CheckArrivalNode` had
10/// byte-for-byte identical `tick` bodies here (only the log message differed) - cargo-dupes
11/// flagged them as an exact-duplicate trait-impl pair. This stays an expression-position macro
12/// (rather than generating the whole `async fn tick`) so `#[async_trait]` still sees a literal
13/// `async fn` signature to rewrite on each impl.
14macro_rules! stub_tick_success {
15    ($msg:literal) => {{
16        tracing::debug!($msg);
17        Ok(NodeStatus::Success)
18    }};
19}
20
21/// PlanPathNode - Plans a path to a target position (STUB)
22///
23/// This is a stub implementation for path planning.
24/// In a full implementation, this would calculate a collision-free path.
25///
26/// # Configuration
27///
28/// ```json
29/// {
30///   "type": "plan_path",
31///   "config": {
32///     "target_position": [5.0, 3.0, 0.0],
33///     "path_planning_algorithm": "a_star",
34///     "max_speed": 1.0
35///   }
36/// }
37/// ```
38#[derive(Debug)]
39pub struct PlanPathNode {
40    #[allow(dead_code)]
41    target_position: Vec<f64>,
42    #[allow(dead_code)]
43    algorithm: String,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47struct PlanPathConfig {
48    target_position: Vec<f64>,
49    #[serde(default = "default_algorithm")]
50    path_planning_algorithm: String,
51}
52
53fn default_algorithm() -> String {
54    "a_star".to_string()
55}
56
57impl PlanPathNode {
58    // duplicate: false positive — matches `StopNode::from_json`
59    // (packages/behavior-runtime/src/actions/movement.rs) and `CheckArrivalNode::from_json`
60    // below on AST shape only: parse a `serde_json::Value` into a node-specific `*Config`
61    // struct, then map its fields into `Self`. See the note on `StopNode::from_json` for why
62    // that's a coincidental structural match rather than real duplication (each maps a
63    // different field set/count) and isn't unified the way `TimerNode`/`PauseNode` were.
64    /// Create a new PlanPathNode from JSON configuration
65    pub fn from_json(config: Value) -> anyhow::Result<Self> {
66        let cfg: PlanPathConfig = serde_json::from_value(config)?;
67        Ok(Self {
68            target_position: cfg.target_position,
69            algorithm: cfg.path_planning_algorithm,
70        })
71    }
72}
73
74#[async_trait]
75impl BehaviorNode for PlanPathNode {
76    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
77        // TODO: Implement actual path planning
78        tracing::debug!("PlanPathNode: planning path to {:?} (stub)", self.target_position);
79        Ok(NodeStatus::Success)
80    }
81
82    fn name(&self) -> &str {
83        "PlanPathNode"
84    }
85}
86
87/// FollowPathNode - Follows a planned path (STUB)
88///
89/// This is a stub implementation for path following.
90/// In a full implementation, this would execute the planned path.
91///
92/// # Configuration
93///
94/// ```json
95/// {
96///   "type": "follow_path",
97///   "config": {
98///     "max_speed": 1.0,
99///     "turn_rate": 0.5,
100///     "topic": "/motor/cmd_vel"
101///   }
102/// }
103/// ```
104#[derive(Debug)]
105pub struct FollowPathNode {
106    #[allow(dead_code)] // TODO: Use this when implementing path following
107    topic: String,
108    #[allow(dead_code)]
109    max_speed: f64,
110    #[allow(dead_code)]
111    turn_rate: f64,
112}
113
114#[derive(Debug, Serialize, Deserialize)]
115struct FollowPathConfig {
116    #[serde(default = "default_motor_topic")]
117    topic: String,
118    #[serde(default = "default_max_speed")]
119    max_speed: f64,
120    #[serde(default = "default_turn_rate")]
121    turn_rate: f64,
122}
123
124fn default_motor_topic() -> String {
125    "/motor/cmd_vel".to_string()
126}
127
128fn default_max_speed() -> f64 {
129    1.0
130}
131
132fn default_turn_rate() -> f64 {
133    0.5
134}
135
136impl FollowPathNode {
137    /// Create a new FollowPathNode from JSON configuration
138    pub fn from_json(config: Value) -> anyhow::Result<Self> {
139        let cfg: FollowPathConfig = serde_json::from_value(config)?;
140        Ok(Self {
141            topic: cfg.topic,
142            max_speed: cfg.max_speed,
143            turn_rate: cfg.turn_rate,
144        })
145    }
146}
147
148#[async_trait]
149impl BehaviorNode for FollowPathNode {
150    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
151        // TODO: Implement actual path following
152        stub_tick_success!("FollowPathNode: following path (stub)")
153    }
154
155    fn name(&self) -> &str {
156        "FollowPathNode"
157    }
158}
159
160/// CheckArrivalNode - Checks if the robot has arrived at the target (STUB)
161///
162/// This is a stub implementation for arrival checking.
163/// In a full implementation, this would check the robot's position against the target.
164///
165/// # Configuration
166///
167/// ```json
168/// {
169///   "type": "check_arrival",
170///   "config": {
171///     "position_tolerance": 0.1,
172///     "angular_tolerance": 0.05
173///   }
174/// }
175/// ```
176#[derive(Debug)]
177pub struct CheckArrivalNode {
178    #[allow(dead_code)]
179    position_tolerance: f64,
180    #[allow(dead_code)]
181    angular_tolerance: f64,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
185struct CheckArrivalConfig {
186    position_tolerance: f64,
187    angular_tolerance: f64,
188}
189
190impl CheckArrivalNode {
191    // duplicate: false positive — matches `StopNode::from_json`
192    // (packages/behavior-runtime/src/actions/movement.rs) and `PlanPathNode::from_json` above
193    // on AST shape only; see the note on `StopNode::from_json` for why.
194    /// Create a new CheckArrivalNode from JSON configuration
195    pub fn from_json(config: Value) -> anyhow::Result<Self> {
196        let cfg: CheckArrivalConfig = serde_json::from_value(config)?;
197        Ok(Self {
198            position_tolerance: cfg.position_tolerance,
199            angular_tolerance: cfg.angular_tolerance,
200        })
201    }
202}
203
204#[async_trait]
205impl BehaviorNode for CheckArrivalNode {
206    async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
207        // TODO: Implement actual arrival checking
208        stub_tick_success!("CheckArrivalNode: checking arrival (stub)")
209    }
210
211    fn name(&self) -> &str {
212        "CheckArrivalNode"
213    }
214}