mecha10_behavior_runtime/actions/
movement.rs1use super::elapsed_timer::{reset_timer_field, ElapsedTimer};
4use crate::{BehaviorNode, Context, NodeStatus};
5use async_trait::async_trait;
6use mecha10_core::actuator::Twist;
7use mecha10_core::topics::Topic;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::time::{Duration, Instant};
11
12#[derive(Debug)]
31pub struct WanderNode {
32 topic: String,
33 linear_speed: f64,
34 angular_speed: f64,
35 change_interval: Duration,
36 last_change: Option<Instant>,
37 current_angular: f64,
38}
39
40#[derive(Debug, Serialize, Deserialize)]
41struct WanderConfig {
42 topic: String,
43 #[serde(default = "default_linear_speed")]
44 linear_speed: f64,
45 #[serde(default = "default_angular_speed")]
46 angular_speed: f64,
47 #[serde(default = "default_change_interval")]
48 change_interval_secs: f64,
49}
50
51fn default_linear_speed() -> f64 {
52 0.3
53}
54
55fn default_angular_speed() -> f64 {
56 0.5
57}
58
59fn default_change_interval() -> f64 {
60 3.0
61}
62
63impl WanderNode {
64 pub fn from_json(config: Value) -> anyhow::Result<Self> {
66 let cfg: WanderConfig = serde_json::from_value(config)?;
67 Ok(Self {
68 topic: cfg.topic,
69 linear_speed: cfg.linear_speed,
70 angular_speed: cfg.angular_speed,
71 change_interval: Duration::from_secs_f64(cfg.change_interval_secs),
72 last_change: None,
73 current_angular: 0.0,
74 })
75 }
76}
77
78#[async_trait]
79impl BehaviorNode for WanderNode {
80 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
81 let now = Instant::now();
82
83 if self.last_change.is_none() || now.duration_since(self.last_change.unwrap()) >= self.change_interval {
84 self.current_angular = (rand::random::<f64>() * 2.0 - 1.0) * self.angular_speed;
85 self.last_change = Some(now);
86 }
87
88 let twist = Twist {
89 linear: self.linear_speed as f32,
90 angular: self.current_angular as f32,
91 };
92
93 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
94 let topic = Topic::<Twist>::new(topic_static);
95
96 ctx.publish_to(topic, &twist).await?;
97 Ok(NodeStatus::Running)
98 }
99
100 fn name(&self) -> &str {
101 "WanderNode"
102 }
103}
104
105#[derive(Debug, Default)]
123pub struct MoveNode {
124 topic: String,
125 linear: f64,
126 angular: f64,
127 duration: Option<Duration>,
128 timer: ElapsedTimer,
129}
130
131#[derive(Debug, Serialize, Deserialize)]
132struct MoveConfig {
133 topic: String,
134 linear: f64,
135 angular: f64,
136 duration_secs: Option<f64>,
137}
138
139impl MoveNode {
140 pub fn from_json(config: Value) -> anyhow::Result<Self> {
142 let cfg: MoveConfig = serde_json::from_value(config)?;
143 Ok(Self {
144 topic: cfg.topic,
145 linear: cfg.linear,
146 angular: cfg.angular,
147 duration: cfg.duration_secs.map(Duration::from_secs_f64),
148 timer: ElapsedTimer::default(),
149 })
150 }
151}
152
153#[async_trait]
154impl BehaviorNode for MoveNode {
155 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
156 if let Some(duration) = self.duration {
157 if self.timer.elapsed_since_start(duration) {
158 return Ok(NodeStatus::Success);
159 }
160 }
161
162 let twist = Twist {
163 linear: self.linear as f32,
164 angular: self.angular as f32,
165 };
166
167 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
168 let topic = Topic::<Twist>::new(topic_static);
169
170 ctx.publish_to(topic, &twist).await?;
171
172 Ok(NodeStatus::Running)
173 }
174
175 reset_timer_field!(timer);
176
177 fn name(&self) -> &str {
178 "MoveNode"
179 }
180}
181
182#[derive(Debug)]
198pub struct StopNode {
199 topic: String,
200}
201
202#[derive(Debug, Serialize, Deserialize)]
203struct StopConfig {
204 topic: String,
205}
206
207impl StopNode {
208 pub fn from_json(config: Value) -> anyhow::Result<Self> {
217 let cfg: StopConfig = serde_json::from_value(config)?;
218 Ok(Self { topic: cfg.topic })
219 }
220}
221
222#[async_trait]
223impl BehaviorNode for StopNode {
224 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
225 let twist = Twist {
226 linear: 0.0,
227 angular: 0.0,
228 };
229
230 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
231 let topic = Topic::<Twist>::new(topic_static);
232
233 ctx.publish_to(topic, &twist).await?;
234 tracing::debug!("StopNode: published stop command");
235 Ok(NodeStatus::Success)
236 }
237
238 fn name(&self) -> &str {
239 "StopNode"
240 }
241}