robotrt-cli 0.1.0-beta.1

RobotRT modular robotics runtime and middleware components.
use std::io;
use std::path::PathBuf;

use core_types::{Timestamp, TransportDomain};
use data_model::{ControlEnvelope, Packet, PacketHeader, SchemaId, SchemaVersion};
use introspection_core::{
    ActionStatus, GraphEdge, HealthStatusItem, MissionStatus, NodeStatus, PluginStatus,
    ResourceCatalogReport, ServiceStatus, StatusSnapshot, TopicStatus, collect_middleware_load,
    write_middleware_load_report, write_resource_catalog_report, write_status_snapshot,
};
use middleware_core::MiddlewareStack;

pub fn capture_demo_report(path: &PathBuf) -> io::Result<()> {
    let mut stack = MiddlewareStack::default();
    stack.register_topic("demo/camera/raw");
    stack.register_topic("demo/camera/processed");
    stack.register_service("demo/echo");

    let raw_slot = stack.topic_slot("demo/camera/raw", 8);
    let processed_slot = stack.topic_slot("demo/camera/processed", 16);
    for idx in 0..5u32 {
        raw_slot.push(Box::new(idx));
    }
    for idx in 0..3u32 {
        processed_slot.push(Box::new(idx));
    }

    let echo = stack.service_channel("demo/echo");
    echo.push_request(1, Box::new(String::from("hello")));
    echo.push_request(2, Box::new(String::from("world")));
    echo.push_response(1, Box::new(String::from("ok")));

    let closed_id = stack.open_session(TransportDomain::Local, "demo/local");
    stack.close_session(closed_id);

    let reconnecting_id = stack.open_session(TransportDomain::Network, "demo/network");
    stack.mission_disconnect(reconnecting_id, "demo reconnecting");

    let report = collect_middleware_load(&stack);
    write_middleware_load_report(path, &report)
}

pub fn capture_demo_resource_report(path: &PathBuf) -> io::Result<()> {
    let report = ResourceCatalogReport::from_lists(
        vec!["demo_node".to_string()],
        vec![
            "/demo/camera/raw".to_string(),
            "/demo/camera/processed".to_string(),
        ],
        vec!["/demo/echo".to_string()],
        vec!["/demo/calibrate".to_string()],
        vec!["demo_mission".to_string()],
    );
    write_resource_catalog_report(path, &report)
}

pub fn capture_demo_status_report(path: &PathBuf) -> io::Result<()> {
    let snapshot = demo_status_snapshot();
    write_status_snapshot(path, &snapshot)
}

pub fn demo_status_snapshot() -> StatusSnapshot {
    StatusSnapshot::new(
        vec![NodeStatus {
            name: "demo_node".to_string(),
            namespace: "/demo".to_string(),
            capabilities: vec![
                "pubsub".to_string(),
                "service".to_string(),
                "mission".to_string(),
            ],
        }],
        vec![TopicStatus {
            name: "/demo/camera/raw".to_string(),
            schema: "sample.image".to_string(),
            reliable: true,
            depth: 16,
            pending: 3,
            max_depth: 16,
            publishers: 1,
            subscribers: 1,
        }],
        vec![ServiceStatus {
            name: "/demo/echo".to_string(),
            pending_requests: 1,
            pending_responses: 0,
            clients: 1,
            servers: 1,
        }],
        vec![ActionStatus {
            name: "/demo/calibrate".to_string(),
            clients: 1,
            servers: 1,
            current_state: Some("executing".to_string()),
            active_goals: Some(1),
            health_state: Some("active".to_string()),
            heartbeat_timeout_ms: Some(5000),
            last_heartbeat_at_unix_nanos: Some(Timestamp::now().0),
            last_feedback_at_unix_nanos: Some(Timestamp::now().0),
            last_result_at_unix_nanos: None,
        }],
        vec![MissionStatus {
            name: "demo_mission".to_string(),
            state: "running".to_string(),
            last_checkpoint: Some("cp-demo-1".to_string()),
        }],
        vec![
            HealthStatusItem {
                component: "runtime".to_string(),
                status: "healthy".to_string(),
                reason: None,
            },
            HealthStatusItem {
                component: "device".to_string(),
                status: "healthy".to_string(),
                reason: None,
            },
            HealthStatusItem {
                component: "pipeline".to_string(),
                status: "healthy".to_string(),
                reason: None,
            },
            HealthStatusItem {
                component: "plugin".to_string(),
                status: "healthy".to_string(),
                reason: None,
            },
        ],
        vec![
            PluginStatus {
                name: "sample-device-plugin".to_string(),
                kind: "device".to_string(),
                loaded: true,
            },
            PluginStatus {
                name: "sample-obs-plugin".to_string(),
                kind: "observability".to_string(),
                loaded: false,
            },
        ],
        vec![
            GraphEdge {
                from: "demo_node".to_string(),
                to: "/demo/camera/raw".to_string(),
                relation: "publishes".to_string(),
            },
            GraphEdge {
                from: "demo_node".to_string(),
                to: "/demo/echo".to_string(),
                relation: "provides".to_string(),
            },
        ],
    )
}

pub fn make_demo_packet(sequence: u64, domain: TransportDomain) -> Packet {
    Packet::Control(ControlEnvelope {
        header: PacketHeader {
            version: 1,
            domain,
            session_id: None,
            stream_id: None,
            sequence,
            ack: None,
            timestamp: Timestamp::now(),
            schema_id: SchemaId::new("robotrt.bag.demo"),
            schema_version: SchemaVersion(1),
        },
        label: format!("bag.demo.{sequence}"),
        payload: format!("payload-{sequence}").into_bytes(),
    })
}