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)]
13pub struct WanderNode {
14 topic: String,
15 linear_speed: f64,
16 angular_speed: f64,
17 change_interval: Duration,
18 last_change: Option<Instant>,
19 current_angular: f64,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23struct WanderConfig {
24 topic: String,
25 #[serde(default = "default_linear_speed")]
26 linear_speed: f64,
27 #[serde(default = "default_angular_speed")]
28 angular_speed: f64,
29 #[serde(default = "default_change_interval")]
30 change_interval_secs: f64,
31}
32
33fn default_linear_speed() -> f64 {
34 0.3
35}
36
37fn default_angular_speed() -> f64 {
38 0.5
39}
40
41fn default_change_interval() -> f64 {
42 3.0
43}
44
45impl WanderNode {
46 pub fn from_json(config: Value) -> anyhow::Result<Self> {
48 let cfg: WanderConfig = serde_json::from_value(config)?;
49 Ok(Self {
50 topic: cfg.topic,
51 linear_speed: cfg.linear_speed,
52 angular_speed: cfg.angular_speed,
53 change_interval: Duration::from_secs_f64(cfg.change_interval_secs),
54 last_change: None,
55 current_angular: 0.0,
56 })
57 }
58}
59
60#[async_trait]
61impl BehaviorNode for WanderNode {
62 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
63 let now = Instant::now();
64
65 if self.last_change.is_none() || now.duration_since(self.last_change.unwrap()) >= self.change_interval {
66 self.current_angular = (rand::random::<f64>() * 2.0 - 1.0) * self.angular_speed;
67 self.last_change = Some(now);
68 }
69
70 let twist = Twist {
71 linear: self.linear_speed as f32,
72 angular: self.current_angular as f32,
73 };
74
75 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
76 let topic = Topic::<Twist>::new(topic_static);
77
78 ctx.publish_to(topic, &twist).await?;
79 Ok(NodeStatus::Running)
80 }
81
82 fn name(&self) -> &str {
83 "WanderNode"
84 }
85}
86
87#[derive(Debug, Default)]
88pub struct MoveNode {
89 topic: String,
90 linear: f64,
91 angular: f64,
92 duration: Option<Duration>,
93 timer: ElapsedTimer,
94}
95
96#[derive(Debug, Serialize, Deserialize)]
97struct MoveConfig {
98 topic: String,
99 linear: f64,
100 angular: f64,
101 duration_secs: Option<f64>,
102}
103
104impl MoveNode {
105 pub fn from_json(config: Value) -> anyhow::Result<Self> {
107 let cfg: MoveConfig = serde_json::from_value(config)?;
108 Ok(Self {
109 topic: cfg.topic,
110 linear: cfg.linear,
111 angular: cfg.angular,
112 duration: cfg.duration_secs.map(Duration::from_secs_f64),
113 timer: ElapsedTimer::default(),
114 })
115 }
116}
117
118#[async_trait]
119impl BehaviorNode for MoveNode {
120 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
121 if let Some(duration) = self.duration {
122 if self.timer.elapsed_since_start(duration) {
123 return Ok(NodeStatus::Success);
124 }
125 }
126
127 let twist = Twist {
128 linear: self.linear as f32,
129 angular: self.angular as f32,
130 };
131
132 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
133 let topic = Topic::<Twist>::new(topic_static);
134
135 ctx.publish_to(topic, &twist).await?;
136
137 Ok(NodeStatus::Running)
138 }
139
140 reset_timer_field!(timer);
141
142 fn name(&self) -> &str {
143 "MoveNode"
144 }
145}
146
147#[derive(Debug)]
148pub struct StopNode {
149 topic: String,
150}
151
152#[derive(Debug, Serialize, Deserialize)]
153struct StopConfig {
154 topic: String,
155}
156
157impl StopNode {
158 pub fn from_json(config: Value) -> anyhow::Result<Self> {
160 let cfg: StopConfig = serde_json::from_value(config)?;
161 Ok(Self { topic: cfg.topic })
162 }
163}
164
165#[async_trait]
166impl BehaviorNode for StopNode {
167 async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
168 let twist = Twist {
169 linear: 0.0,
170 angular: 0.0,
171 };
172
173 let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
174 let topic = Topic::<Twist>::new(topic_static);
175
176 ctx.publish_to(topic, &twist).await?;
177 tracing::debug!("StopNode: published stop command");
178 Ok(NodeStatus::Success)
179 }
180
181 fn name(&self) -> &str {
182 "StopNode"
183 }
184}