use crate::commands::NodeArgs;
use anyhow::Result;
#[cfg(feature = "bundled-nodes")]
use crate::services::ModelService;
#[cfg(feature = "bundled-nodes")]
use anyhow::Context;
#[cfg(feature = "bundled-nodes")]
use std::path::PathBuf;
#[cfg(feature = "bundled-nodes")]
use tracing::{info, warn};
#[cfg(not(feature = "bundled-nodes"))]
pub async fn handle_node(_args: &NodeArgs) -> Result<()> {
anyhow::bail!(
"Bundled nodes are not available in this build.\n\
Install with bundled nodes: cargo install mecha10-cli --features bundled-nodes\n\
Or use: mecha10 dev (which builds your project's nodes)"
)
}
#[cfg(all(feature = "bundled-nodes", not(feature = "vision")))]
const BUNDLED_NODES: &[&str] = &[
"behavior-executor",
"diagnostics-node",
"imu",
"listener",
"llm-command",
"motor",
"simulation-bridge",
"speaker",
"teleop",
"websocket-bridge",
];
#[cfg(all(feature = "bundled-nodes", feature = "vision"))]
const BUNDLED_NODES: &[&str] = &[
"behavior-executor",
"diagnostics-node",
"image-classifier",
"imu",
"listener",
"llm-command",
"motor",
"object-detector",
"simulation-bridge",
"speaker",
"teleop",
"websocket-bridge",
];
#[cfg(feature = "bundled-nodes")]
pub async fn handle_node(args: &NodeArgs) -> Result<()> {
let name = &args.name;
if !BUNDLED_NODES.contains(&name.as_str()) {
anyhow::bail!(
"Unknown node: '{}'. Available nodes: {}",
name,
BUNDLED_NODES.join(", ")
);
}
#[cfg(feature = "vision")]
{
if let Some(model_name) = get_required_model(name) {
ensure_model_available(model_name).await?;
}
}
info!("Starting bundled node: {}", name);
run_bundled_node(name).await
}
#[cfg(all(feature = "bundled-nodes", feature = "vision"))]
fn get_required_model(node_name: &str) -> Option<&'static str> {
match node_name {
"object-detector" => Some("yolov8n"),
"image-classifier" => Some("mobilenet-v2"),
_ => None,
}
}
#[cfg(all(feature = "bundled-nodes", feature = "vision"))]
async fn ensure_model_available(model_name: &str) -> Result<()> {
let model_path = PathBuf::from(format!("models/{}/model.onnx", model_name));
if model_path.exists() {
info!("Model '{}' found at {}", model_name, model_path.display());
return Ok(());
}
info!("Model '{}' not found, downloading...", model_name);
let model_service = ModelService::with_models_dir("models".into()).context("Failed to initialize model service")?;
let pb = indicatif::ProgressBar::new_spinner();
pb.set_message(format!("Downloading model '{}'...", model_name));
pb.enable_steady_tick(std::time::Duration::from_millis(100));
match model_service.pull(model_name, Some(&pb)).await {
Ok(_) => {
pb.finish_with_message(format!("Model '{}' downloaded successfully", model_name));
info!("Model '{}' ready", model_name);
Ok(())
}
Err(e) => {
pb.finish_with_message(format!("Failed to download model '{}'", model_name));
warn!(
"Failed to download model '{}': {}. Node may fail to start.",
model_name, e
);
Ok(())
}
}
}
#[cfg(feature = "bundled-nodes")]
async fn run_bundled_node(name: &str) -> Result<()> {
match name {
"behavior-executor" => mecha10_nodes_behavior_executor::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"diagnostics-node" => mecha10_nodes_diagnostics::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
#[cfg(feature = "vision")]
"image-classifier" => mecha10_nodes_image_classifier::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"imu" => mecha10_nodes_imu::run().await.map_err(|e| anyhow::anyhow!("{}", e)),
"listener" => mecha10_nodes_listener::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"llm-command" => mecha10_nodes_llm_command::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"motor" => mecha10_nodes_motor::run().await.map_err(|e| anyhow::anyhow!("{}", e)),
#[cfg(feature = "vision")]
"object-detector" => mecha10_nodes_object_detector::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"simulation-bridge" => mecha10_nodes_simulation_bridge::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
"speaker" => mecha10_nodes_speaker::run().await.map_err(|e| anyhow::anyhow!("{}", e)),
"teleop" => mecha10_nodes_teleop::run().await.map_err(|e| anyhow::anyhow!("{}", e)),
"websocket-bridge" => mecha10_nodes_websocket_bridge::run()
.await
.map_err(|e| anyhow::anyhow!("{}", e)),
_ => Err(anyhow::anyhow!("Unknown node: {}", name)),
}
}