mecha10_behavior_runtime/actions/
movement.rs1use crate::{BehaviorNode, Context, NodeStatus};
4use async_trait::async_trait;
5use mecha10_core::actuator::Twist;
6use mecha10_core::topics::Topic;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::time::{Duration, Instant};
10
11#[derive(Debug)]
30pub struct WanderNode {
31 topic: String,
32 linear_speed: f64,
33 angular_speed: f64,
34 change_interval: Duration,
35 last_change: Option<Instant>,
36 current_angular: f64,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40struct WanderConfig {
41 topic: String,
42 #[serde(default = "default_linear_speed")]
43 linear_speed: f64,
44 #[serde(default = "default_angular_speed")]
45 angular_speed: f64,
46 #[serde(default = "default_change_interval")]
47 change_interval_secs: f64,
48}
49
50fn default_linear_speed() -> f64 {
51 0.3
52}
53
54fn default_angular_speed() -> f64 {
55 0.5
56}
57
58fn default_change_interval() -> f64 {
59 3.0
60}
61
62impl WanderNode {
63 pub fn from_json(config: Value) -> anyhow::Result<Self> {
65 let cfg: WanderConfig = serde_json::from_value(config)?;
66 Ok(Self {
67 topic: cfg.topic,
68 linear_speed: cfg.linear_speed,
69 angular_speed: cfg.angular_speed,
70 change_interval: Duration::from_secs_f64(cfg.change_interval_secs),
71 last_change: None,
72 current_angular: 0.0,
73 })
74 }
75}
76
77#[async_trait]
78impl BehaviorNode for WanderNode {
79 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
80 let now = Instant::now();
81
82 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;
86 self.last_change = Some(now);
87 }
88
89 let twist = Twist {
91 linear: self.linear_speed as f32,
92 angular: self.current_angular as f32,
93 };
94
95 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
97 let topic = Topic::<Twist>::new(topic_static);
98
99 ctx.publish_to(topic, &twist).await?;
100 Ok(NodeStatus::Running)
101 }
102
103 fn name(&self) -> &str {
104 "WanderNode"
105 }
106}
107
108#[derive(Debug)]
126pub struct MoveNode {
127 topic: String,
128 linear: f64,
129 angular: f64,
130 duration: Option<Duration>,
131 start_time: Option<Instant>,
132}
133
134#[derive(Debug, Serialize, Deserialize)]
135struct MoveConfig {
136 topic: String,
137 linear: f64,
138 angular: f64,
139 duration_secs: Option<f64>,
140}
141
142impl MoveNode {
143 pub fn from_json(config: Value) -> anyhow::Result<Self> {
145 let cfg: MoveConfig = serde_json::from_value(config)?;
146 Ok(Self {
147 topic: cfg.topic,
148 linear: cfg.linear,
149 angular: cfg.angular,
150 duration: cfg.duration_secs.map(Duration::from_secs_f64),
151 start_time: None,
152 })
153 }
154}
155
156#[async_trait]
157impl BehaviorNode for MoveNode {
158 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
159 let now = Instant::now();
160
161 if self.start_time.is_none() {
163 self.start_time = Some(now);
164 }
165
166 if let Some(duration) = self.duration {
168 if now.duration_since(self.start_time.unwrap()) >= duration {
169 return Ok(NodeStatus::Success);
170 }
171 }
172
173 let twist = Twist {
175 linear: self.linear as f32,
176 angular: self.angular as f32,
177 };
178
179 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
181 let topic = Topic::<Twist>::new(topic_static);
182
183 ctx.publish_to(topic, &twist).await?;
184
185 Ok(NodeStatus::Running)
186 }
187
188 async fn reset(&mut self) -> anyhow::Result<()> {
189 self.start_time = None;
190 Ok(())
191 }
192
193 fn name(&self) -> &str {
194 "MoveNode"
195 }
196}
197
198#[derive(Debug)]
214pub struct StopNode {
215 topic: String,
216}
217
218#[derive(Debug, Serialize, Deserialize)]
219struct StopConfig {
220 topic: String,
221}
222
223impl StopNode {
224 pub fn from_json(config: Value) -> anyhow::Result<Self> {
226 let cfg: StopConfig = serde_json::from_value(config)?;
227 Ok(Self { topic: cfg.topic })
228 }
229}
230
231#[async_trait]
232impl BehaviorNode for StopNode {
233 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
234 let twist = Twist {
236 linear: 0.0,
237 angular: 0.0,
238 };
239
240 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
242 let topic = Topic::<Twist>::new(topic_static);
243
244 ctx.publish_to(topic, &twist).await?;
245 tracing::debug!("StopNode: published stop command");
246 Ok(NodeStatus::Success)
247 }
248
249 fn name(&self) -> &str {
250 "StopNode"
251 }
252}