use crate::{BehaviorNode, BoxedBehavior, Context, NodeRegistry, NodeStatus};
use async_trait::async_trait;
use mecha10_core::actuator::Twist;
use mecha10_core::topics::Topic;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::time::{Duration, Instant};
pub fn register_builtin_actions(registry: &mut NodeRegistry) {
registry.register("wander", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(WanderNode::from_json(config)?))
});
registry.register("move", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(MoveNode::from_json(config)?))
});
registry.register("sensor_check", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(SensorCheckNode::from_json(config)?))
});
registry.register("timer", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(TimerNode::from_json(config)?))
});
registry.register("idle", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(IdleNode::from_json(config)?))
});
registry.register("pause", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(PauseNode::from_json(config)?))
});
registry.register("stop", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(StopNode::from_json(config)?))
});
registry.register("plan_path", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(PlanPathNode::from_json(config)?))
});
registry.register("follow_path", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(FollowPathNode::from_json(config)?))
});
registry.register("check_arrival", |config: Value| -> anyhow::Result<BoxedBehavior> {
Ok(Box::new(CheckArrivalNode::from_json(config)?))
});
}
#[derive(Debug)]
pub struct WanderNode {
topic: String,
linear_speed: f64,
angular_speed: f64,
change_interval: Duration,
last_change: Option<Instant>,
current_angular: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct WanderConfig {
topic: String,
#[serde(default = "default_linear_speed")]
linear_speed: f64,
#[serde(default = "default_angular_speed")]
angular_speed: f64,
#[serde(default = "default_change_interval")]
change_interval_secs: f64,
}
fn default_linear_speed() -> f64 {
0.3
}
fn default_angular_speed() -> f64 {
0.5
}
fn default_change_interval() -> f64 {
3.0
}
impl WanderNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: WanderConfig = serde_json::from_value(config)?;
Ok(Self {
topic: cfg.topic,
linear_speed: cfg.linear_speed,
angular_speed: cfg.angular_speed,
change_interval: Duration::from_secs_f64(cfg.change_interval_secs),
last_change: None,
current_angular: 0.0,
})
}
}
#[async_trait]
impl BehaviorNode for WanderNode {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
let now = Instant::now();
if self.last_change.is_none() || now.duration_since(self.last_change.unwrap()) >= self.change_interval {
self.current_angular = (rand::random::<f64>() * 2.0 - 1.0) * self.angular_speed;
self.last_change = Some(now);
}
let twist = Twist {
linear: self.linear_speed as f32,
angular: self.current_angular as f32,
};
let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
let topic = Topic::<Twist>::new(topic_static);
ctx.publish_to(topic, &twist).await?;
Ok(NodeStatus::Running)
}
fn name(&self) -> &str {
"WanderNode"
}
}
#[derive(Debug)]
pub struct MoveNode {
topic: String,
linear: f64,
angular: f64,
duration: Option<Duration>,
start_time: Option<Instant>,
}
#[derive(Debug, Serialize, Deserialize)]
struct MoveConfig {
topic: String,
linear: f64,
angular: f64,
duration_secs: Option<f64>,
}
impl MoveNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: MoveConfig = serde_json::from_value(config)?;
Ok(Self {
topic: cfg.topic,
linear: cfg.linear,
angular: cfg.angular,
duration: cfg.duration_secs.map(Duration::from_secs_f64),
start_time: None,
})
}
}
#[async_trait]
impl BehaviorNode for MoveNode {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
let now = Instant::now();
if self.start_time.is_none() {
self.start_time = Some(now);
}
if let Some(duration) = self.duration {
if now.duration_since(self.start_time.unwrap()) >= duration {
return Ok(NodeStatus::Success);
}
}
let twist = Twist {
linear: self.linear as f32,
angular: self.angular as f32,
};
let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
let topic = Topic::<Twist>::new(topic_static);
ctx.publish_to(topic, &twist).await?;
Ok(NodeStatus::Running)
}
async fn reset(&mut self) -> anyhow::Result<()> {
self.start_time = None;
Ok(())
}
fn name(&self) -> &str {
"MoveNode"
}
}
#[derive(Debug)]
pub struct SensorCheckNode {
#[allow(dead_code)] topic: String,
field: String,
operator: ComparisonOperator,
threshold: f64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ComparisonOperator {
LessThan,
GreaterThan,
Equal,
}
#[derive(Debug, Serialize, Deserialize)]
struct SensorCheckConfig {
topic: String,
field: String,
operator: ComparisonOperator,
threshold: f64,
}
impl SensorCheckNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: SensorCheckConfig = serde_json::from_value(config)?;
Ok(Self {
topic: cfg.topic,
field: cfg.field,
operator: cfg.operator,
threshold: cfg.threshold,
})
}
}
#[async_trait]
impl BehaviorNode for SensorCheckNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
tracing::debug!(
"SensorCheckNode: checking {} {} {}",
self.field,
match self.operator {
ComparisonOperator::LessThan => "<",
ComparisonOperator::GreaterThan => ">",
ComparisonOperator::Equal => "==",
},
self.threshold
);
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"SensorCheckNode"
}
}
#[derive(Debug)]
pub struct TimerNode {
duration: Duration,
start_time: Option<Instant>,
}
#[derive(Debug, Serialize, Deserialize)]
struct TimerConfig {
duration_secs: f64,
}
impl TimerNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: TimerConfig = serde_json::from_value(config)?;
Ok(Self {
duration: Duration::from_secs_f64(cfg.duration_secs),
start_time: None,
})
}
}
#[async_trait]
impl BehaviorNode for TimerNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
let now = Instant::now();
if self.start_time.is_none() {
self.start_time = Some(now);
}
if now.duration_since(self.start_time.unwrap()) >= self.duration {
Ok(NodeStatus::Success)
} else {
Ok(NodeStatus::Running)
}
}
async fn reset(&mut self) -> anyhow::Result<()> {
self.start_time = None;
Ok(())
}
fn name(&self) -> &str {
"TimerNode"
}
}
#[derive(Debug)]
pub struct IdleNode;
impl IdleNode {
pub fn from_json(_config: Value) -> anyhow::Result<Self> {
Ok(Self)
}
}
#[async_trait]
impl BehaviorNode for IdleNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"IdleNode"
}
}
#[derive(Debug)]
pub struct PauseNode {
duration: Duration,
start_time: Option<Instant>,
}
#[derive(Debug, Serialize, Deserialize)]
struct PauseConfig {
duration_secs: f64,
}
impl PauseNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: PauseConfig = serde_json::from_value(config)?;
Ok(Self {
duration: Duration::from_secs_f64(cfg.duration_secs),
start_time: None,
})
}
}
#[async_trait]
impl BehaviorNode for PauseNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
let now = Instant::now();
if self.start_time.is_none() {
self.start_time = Some(now);
tracing::debug!("PauseNode: starting pause for {:?}", self.duration);
}
if now.duration_since(self.start_time.unwrap()) >= self.duration {
tracing::debug!("PauseNode: pause complete");
Ok(NodeStatus::Success)
} else {
Ok(NodeStatus::Running)
}
}
async fn reset(&mut self) -> anyhow::Result<()> {
self.start_time = None;
Ok(())
}
fn name(&self) -> &str {
"PauseNode"
}
}
#[derive(Debug)]
pub struct StopNode {
topic: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct StopConfig {
topic: String,
}
impl StopNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: StopConfig = serde_json::from_value(config)?;
Ok(Self { topic: cfg.topic })
}
}
#[async_trait]
impl BehaviorNode for StopNode {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
let twist = Twist {
linear: 0.0,
angular: 0.0,
};
let topic_static: &'static str = Box::leak(self.topic.clone().into_boxed_str());
let topic = Topic::<Twist>::new(topic_static);
ctx.publish_to(topic, &twist).await?;
tracing::debug!("StopNode: published stop command");
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"StopNode"
}
}
#[derive(Debug)]
pub struct PlanPathNode {
#[allow(dead_code)]
target_position: Vec<f64>,
#[allow(dead_code)]
algorithm: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct PlanPathConfig {
target_position: Vec<f64>,
#[serde(default = "default_algorithm")]
path_planning_algorithm: String,
}
fn default_algorithm() -> String {
"a_star".to_string()
}
impl PlanPathNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: PlanPathConfig = serde_json::from_value(config)?;
Ok(Self {
target_position: cfg.target_position,
algorithm: cfg.path_planning_algorithm,
})
}
}
#[async_trait]
impl BehaviorNode for PlanPathNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
tracing::debug!("PlanPathNode: planning path to {:?} (stub)", self.target_position);
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"PlanPathNode"
}
}
#[derive(Debug)]
pub struct FollowPathNode {
#[allow(dead_code)] topic: String,
#[allow(dead_code)]
max_speed: f64,
#[allow(dead_code)]
turn_rate: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct FollowPathConfig {
#[serde(default = "default_motor_topic")]
topic: String,
#[serde(default = "default_max_speed")]
max_speed: f64,
#[serde(default = "default_turn_rate")]
turn_rate: f64,
}
fn default_motor_topic() -> String {
"/motor/cmd_vel".to_string()
}
fn default_max_speed() -> f64 {
1.0
}
fn default_turn_rate() -> f64 {
0.5
}
impl FollowPathNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: FollowPathConfig = serde_json::from_value(config)?;
Ok(Self {
topic: cfg.topic,
max_speed: cfg.max_speed,
turn_rate: cfg.turn_rate,
})
}
}
#[async_trait]
impl BehaviorNode for FollowPathNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
tracing::debug!("FollowPathNode: following path (stub)");
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"FollowPathNode"
}
}
#[derive(Debug)]
pub struct CheckArrivalNode {
#[allow(dead_code)]
position_tolerance: f64,
#[allow(dead_code)]
angular_tolerance: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct CheckArrivalConfig {
position_tolerance: f64,
angular_tolerance: f64,
}
impl CheckArrivalNode {
pub fn from_json(config: Value) -> anyhow::Result<Self> {
let cfg: CheckArrivalConfig = serde_json::from_value(config)?;
Ok(Self {
position_tolerance: cfg.position_tolerance,
angular_tolerance: cfg.angular_tolerance,
})
}
}
#[async_trait]
impl BehaviorNode for CheckArrivalNode {
async fn tick(&mut self, _ctx: &Context) -> anyhow::Result<NodeStatus> {
tracing::debug!("CheckArrivalNode: checking arrival (stub)");
Ok(NodeStatus::Success)
}
fn name(&self) -> &str {
"CheckArrivalNode"
}
}