Node Trait
| Method | Signature | Default | Description |
|---|---|---|---|
name | fn name(&self) -> &str | Type name | Unique node identifier |
init | fn init(&mut self) -> Result<()> | Ok(()) | Called once at startup |
tick | fn tick(&mut self) | required | Main loop, called repeatedly |
shutdown | fn shutdown(&mut self) -> Result<()> | Ok(()) | Called once at cleanup |
publishers | fn publishers(&self) -> Vec<TopicMetadata> | vec![] | Topic metadata for pubs |
subscribers | fn subscribers(&self) -> Vec<TopicMetadata> | vec![] | Topic metadata for subs |
on_error | fn on_error(&mut self, error: &str) | Logs via hlog! | Custom error recovery |
is_safe_state | fn is_safe_state(&self) -> bool | true | Safety monitor query |
enter_safe_state | fn enter_safe_state(&mut self) | No-op | Emergency stop transition |
NodeBuilder
| Method | Parameter | Description |
|---|---|---|
order | u32 | Execution priority (lower = earlier). 0-9 critical, 10-49 high, 50-99 normal, 100+ low |
rate | Frequency | Tick rate. Auto-derives budget (80%) and deadline (95%). Auto-enables RT for BestEffort nodes |
budget | Duration | Max tick execution time. Overrides auto-derived 80% budget |
deadline | Duration | Absolute latest tick finish. Overrides auto-derived 95% deadline |
on_miss | Miss | Deadline miss policy: Warn, Skip, SafeMode, Stop |
compute | — | Parallel thread pool execution (CPU-bound work) |
on | &str | Event-triggered on topic update |
async_io | — | Tokio blocking pool execution (I/O-bound work) |
failure_policy | FailurePolicy | Per-node failure handling: fatal(), restart(n, backoff), skip(), ignore() |
priority | i32 | OS-level SCHED_FIFO priority (1-99). RT nodes only |
core | usize | Pin RT thread to CPU core via sched_setaffinity |
watchdog | Duration | Per-node watchdog timeout (overrides global) |
build | — | Finalize and register node. Returns Result<&mut Scheduler> |
Scheduler
| Method | Parameter | Description |
|---|---|---|
Scheduler::new() | — | Constructor with RT capability auto-detection |
Scheduler::simulation() | — | Constructor that skips RT detection (for tests) |
name | &str | Set scheduler name (default: "Scheduler") |
tick_rate | Frequency | Global tick rate (e.g. 1000_u64.hz()) |
deterministic | bool | Sequential execution, SimClock, fixed dt, seeded RNG |
prefer_rt | — | Try mlockall + SCHED_FIFO, degrade gracefully |
require_rt | — | Panic if RT unavailable |
cores | &[usize] | Pin scheduler threads to CPU cores |
watchdog | Duration | Frozen-node detection timeout |
blackbox | usize | Flight recorder size in MB |
max_deadline_misses | u64 | Emergency stop threshold (default: 100) |
verbose | bool | Enable/disable executor thread logging |
with_recording | — | Enable session recording |
telemetry | &str | Export endpoint ("udp://host:port") |
add | impl Node | Returns NodeBuilder for fluent configuration |
set_node_rate | (&str, Frequency) | Change node rate at runtime |
run | — | Start the main loop (blocks) |
run_for | Duration | Run for a fixed duration |
run_ticks | u64 | Run exactly N ticks |
run_until | FnMut() -> bool, u64 | Run until predicate or max ticks |
tick_once | — | Single-tick execution (sim/test). Lazy-inits on first call |
stop | — | Signal scheduler to stop |
status | — | Human-readable status report |
Topic<T>
| Method | Signature | Description |
|---|---|---|
new | fn new(name: impl Into<String>) -> Result<Self> | Create topic with auto-detected backend |
send | fn send(&self, msg: T) | Fire-and-forget with bounded retry |
try_send | fn try_send(&self, msg: T) -> Result<(), T> | Non-blocking send, returns msg on failure |
send_blocking | fn send_blocking(&self, msg: T, timeout: Duration) -> Result<(), SendBlockingError> | Blocking send with spin-yield-sleep strategy |
recv | fn recv(&self) -> Option<T> | Receive next message |
try_recv | fn try_recv(&self) -> Option<T> | Non-blocking receive (no logging) |
read_latest | fn read_latest(&self) -> Option<T> where T: Copy | Peek latest without advancing consumer |
name | fn name(&self) -> &str | Topic name |
metrics | fn metrics(&self) -> TopicMetrics | Send/recv counts and failure counts |
Services
| Type | Method | Description |
|---|---|---|
ServiceClient<S> | new() -> Result<Self> | Create blocking client for service S |
ServiceClient<S> | call(req, timeout) -> Result<S::Response> | Blocking RPC call |
ServiceClient<S> | call_with_retry(req, timeout, RetryConfig) -> Result<S::Response> | Call with retry policy |
AsyncServiceClient<S> | new() -> Result<Self> | Create non-blocking client |
AsyncServiceClient<S> | call_async(req) -> PendingResponse | Non-blocking call, returns handle |
ServiceServerBuilder<S> | new() -> Self | Start building a server |
ServiceServerBuilder<S> | on_request(Fn(Req) -> Result<Res, String>) -> Self | Set request handler |
ServiceServerBuilder<S> | build() -> Result<ServiceServer<S>> | Spawn background polling thread |
ServiceServer<S> | stop(&self) | Stop server (also happens on drop) |
Actions
| Type | Method | Description |
|---|---|---|
ActionClientBuilder<A> | new() -> Self | Start building an action client |
ActionClientBuilder<A> | build() -> Result<ActionClientNode<A>> | Create client node |
ActionClientNode<A> | send_goal(goal) -> ClientGoalHandle<A> | Send goal, get tracking handle |
ActionClientNode<A> | send_goal_with_priority(goal, GoalPriority) -> ClientGoalHandle<A> | Send prioritized goal |
ActionClientNode<A> | cancel_goal(GoalId) | Cancel a running goal |
ClientGoalHandle<A> | status() -> GoalStatus | Current goal status |
ClientGoalHandle<A> | is_active() -> bool | Still running? |
ClientGoalHandle<A> | is_done() -> bool | Terminal state? |
ClientGoalHandle<A> | result() -> Option<A::Result> | Get result if complete |
ClientGoalHandle<A> | last_feedback() -> Option<A::Feedback> | Most recent feedback |
ClientGoalHandle<A> | await_result(timeout) -> Option<A::Result> | Block until done or timeout |
ClientGoalHandle<A> | await_result_with_feedback(timeout, Fn(&Feedback)) | Block with feedback callback |
ActionServerBuilder<A> | new() -> Self | Start building an action server |
ActionServerBuilder<A> | on_goal(Fn(Goal) -> GoalResponse) -> Self | Accept/reject handler |
ActionServerBuilder<A> | on_cancel(Fn(GoalId) -> CancelResponse) -> Self | Cancel handler |
ActionServerBuilder<A> | on_execute(Fn(ServerGoalHandle) -> GoalOutcome) -> Self | Execution handler |
ActionServerBuilder<A> | build() -> Result<ActionServerNode<A>> | Create server node |
ServerGoalHandle<A> | goal() -> &A::Goal | Access goal data |
ServerGoalHandle<A> | is_cancel_requested() -> bool | Client requested cancel? |
ServerGoalHandle<A> | should_abort() -> bool | Cancel or preempt requested? |
ServerGoalHandle<A> | publish_feedback(feedback) | Send progress feedback |
ServerGoalHandle<A> | succeed(result) -> GoalOutcome | Complete successfully |
ServerGoalHandle<A> | abort(result) -> GoalOutcome | Server-side abort |
ServerGoalHandle<A> | canceled(result) -> GoalOutcome | Acknowledge cancellation |
ServerGoalHandle<A> | preempted(result) -> GoalOutcome | Preempted by higher-priority goal |
TransformFrame
| Method | Signature | Description |
|---|---|---|
new | fn new() -> Self | Create empty TF tree (default config) |
register_frame | fn register_frame(&self, name: &str, parent: Option<&str>) -> Result<FrameId> | Add a frame to the tree |
tf | fn tf(&self, src: &str, dst: &str) -> Result<Transform> | Lookup latest transform between frames |
tf_at | fn tf_at(&self, src: &str, dst: &str, timestamp_ns: u64) -> Result<Transform> | Lookup transform at a specific time |
tf_at_strict | fn tf_at_strict(&self, src: &str, dst: &str, timestamp_ns: u64) -> Result<Transform> | Strict time lookup (no interpolation beyond tolerance) |
tf_at_with_tolerance | fn tf_at_with_tolerance(&self, src: &str, dst: &str, ts: u64, tol: u64) -> Result<Transform> | Custom time tolerance |
update_transform | fn update_transform(&self, parent: &str, child: &str, ts: u64, tf: Transform) -> Result<()> | Update a frame's transform |
has_frame | fn has_frame(&self, name: &str) -> bool | Check if frame exists |
all_frames | fn all_frames(&self) -> Vec<String> | List all registered frame names |
frame_count | fn frame_count(&self) -> usize | Number of registered frames |
tf_by_id | fn tf_by_id(&self, src: FrameId, dst: FrameId) -> Option<Transform> | Lookup by numeric ID (fast path) |
tf_at_by_id | fn tf_at_by_id(&self, src: FrameId, dst: FrameId, ts: u64) -> Option<Transform> | Time-based lookup by ID |
DurationExt
| Method | Input | Output | Example |
|---|---|---|---|
ns | u64, f64, i32 | Duration | 500_u64.ns() |
us | u64, f64, i32 | Duration | 200_u64.us() |
ms | u64, f64, i32 | Duration | 1_u64.ms() |
secs | u64, f64, i32 | Duration | 5_u64.secs() |
hz | u64, f64, i32 | Frequency | 100_u64.hz() |
Frequency methods: value() -> f64, period() -> Duration, budget_default() -> Duration (80%), deadline_default() -> Duration (95%).
Enums
ExecutionClass
| Variant | Description |
|---|---|
Rt | Dedicated RT thread with spin-wait timing |
Compute | Parallel thread pool for CPU-bound work |
Event(String) | Triggered by topic updates |
AsyncIo | Tokio blocking pool for I/O-bound work |
BestEffort | Default -- main tick loop, sequential |
Miss
| Variant | Description |
|---|---|
Warn | Log warning and continue (default) |
Skip | Skip this tick, resume next cycle |
SafeMode | Call enter_safe_state(), continue ticking in safe mode |
Stop | Stop the entire scheduler |
NodeState
| Variant | Description |
|---|---|
Uninitialized | Created but not started |
Initializing | init() in progress |
Running | Normal operation |
Stopping | shutdown() in progress |
Stopped | Cleanly stopped |
Error(String) | Error occurred, still running |
Crashed(String) | Fatal error, unresponsive |
HealthStatus
| Variant | Description |
|---|---|
Healthy | Operating normally |
Warning | Degraded performance (slow ticks, missed deadlines) |
Error | Errors occurring but still running |
Critical | Fatal errors, about to crash |
Unknown | No heartbeat received (default) |
Standard Message Types
Geometry
| Type | Fields | Size |
|---|---|---|
Pose2D | x: f64, y: f64, theta: f64 | 24 B |
Pose3D | translation: [f64; 3], rotation: [f64; 4] | 56 B |
Point3 | x: f64, y: f64, z: f64 | 24 B |
Vector3 | x: f64, y: f64, z: f64 | 24 B |
Quaternion | x: f64, y: f64, z: f64, w: f64 | 32 B |
Twist | linear: [f64; 3], angular: [f64; 3] | 48 B |
Accel | linear: [f64; 3], angular: [f64; 3] | 48 B |
TransformStamped | parent: str, child: str, timestamp_ns: u64, transform: Transform | var |
PoseStamped | pose: Pose3D, timestamp_ns: u64, frame_id: str | var |
PoseWithCovariance | pose: Pose3D, covariance: [f64; 36] | 344 B |
TwistWithCovariance | twist: Twist, covariance: [f64; 36] | 336 B |
AccelStamped | accel: Accel, timestamp_ns: u64, frame_id: str | var |
Sensors
| Type | Fields | Size |
|---|---|---|
Imu | orientation: [f64;4], angular_velocity: [f64;3], linear_acceleration: [f64;3] | 80 B |
LaserScan | angle_min/max: f32, angle_increment: f32, ranges: Vec<f32>, intensities: Vec<f32> | var |
Odometry | pose: Pose2D, twist: Twist, timestamp_ns: u64 | var |
JointState | position: Vec<f64>, velocity: Vec<f64>, effort: Vec<f64>, name: Vec<String> | var |
BatteryState | voltage: f32, percentage: f32, current: f32, charging: bool, temperature: f32 | 17 B |
RangeSensor | sensor_type: u8, range: f32, min_range: f32, max_range: f32, fov: f32 | 17 B |
NavSatFix | latitude: f64, longitude: f64, altitude: f64, status: i8, covariance: [f64;9] | var |
MagneticField | field: [f64; 3], covariance: [f64; 9] | 96 B |
Temperature | temperature: f64, variance: f64 | 16 B |
FluidPressure | pressure: f64, variance: f64 | 16 B |
Illuminance | illuminance: f64, variance: f64 | 16 B |
Control
| Type | Fields | Size |
|---|---|---|
CmdVel | linear: f32, angular: f32 | 8 B |
MotorCommand | left: f64, right: f64 | 16 B |
ServoCommand | servo_id: u8, position: f32, speed: f32, torque_limit: f32 | 13 B |
JointCommand | name: Vec<String>, position: Vec<f64>, velocity: Vec<f64>, effort: Vec<f64> | var |
PidConfig | kp: f64, ki: f64, kd: f64, output_min: f64, output_max: f64 | 40 B |
TrajectoryPoint | position: [f64;3], linear_velocity: Vec3, angular_velocity: Vec3, time: f64 | var |
DifferentialDriveCommand | left: f64, right: f64, timestamp_ns: u64 | 24 B |
Navigation
| Type | Fields | Size |
|---|---|---|
NavGoal | target_pose: Pose2D, position_tolerance: f64, angle_tolerance: f64 | 40 B |
GoalResult | goal_id: u32, status: GoalStatus, message: String | var |
NavPath | waypoints: Vec<Waypoint> | var |
PathPlan | poses: Vec<Pose2D>, cost: f64 | var |
Waypoint | pose: Pose2D, speed: f64, tolerance: f64 | 40 B |
OccupancyGrid | width: u32, height: u32, resolution: f32, origin: Pose2D, data: Vec<i8> | var |
CostMap | width: u32, height: u32, resolution: f32, origin: Pose2D, data: Vec<u8> | var |
VelocityObstacle | position: Point3, velocity: Vector3, radius: f64 | 56 B |
Vision
| Type | Fields |
|---|---|
CompressedImage | format: String, data: Vec<u8>, timestamp_ns: u64 |
CameraInfo | width: u32, height: u32, fx: f64, fy: f64, cx: f64, cy: f64, distortion: Vec<f64> |
RegionOfInterest | x: u32, y: u32, width: u32, height: u32 |
StereoInfo | left: CameraInfo, right: CameraInfo, baseline: f64 |
Perception and Detection
| Type | Fields |
|---|---|
BoundingBox2D | x: f32, y: f32, width: f32, height: f32 |
BoundingBox3D | cx: f32, cy: f32, cz: f32, length: f32, width: f32, height: f32, yaw: f32 |
Detection | class_name: String, confidence: f32, bbox: BoundingBox2D |
Detection3D | class_name: String, confidence: f32, bbox: BoundingBox3D |
Landmark | x: f32, y: f32, visibility: f32, index: u32 |
Landmark3D | x: f32, y: f32, z: f32, visibility: f32, index: u32 |
LandmarkArray | landmarks: Vec<Landmark>, landmarks_3d: Vec<Landmark3D> |
SegmentationMask | width: u32, height: u32, class_ids: Vec<u8> |
TrackedObject | track_id: u64, bbox: BoundingBox2D, class_id: u32, confidence: f32 |
PlaneDetection | coefficients: [f64;4], center: Point3, normal: Vector3 |
Force and Haptics
| Type | Fields |
|---|---|
WrenchStamped | force: Vector3, torque: Vector3, timestamp_ns: u64, frame_id: String |
ForceCommand | force: Vector3, torque: Vector3, frame_id: String |
ImpedanceParameters | stiffness: [f64;6], damping: [f64;6], inertia: [f64;6] |
HapticFeedback | force: Vector3, vibration_frequency: f64, vibration_amplitude: f64 |
ContactInfo | state: ContactState, force_magnitude: f64, contact_point: Point3, normal: Vector3 |
Diagnostics
| Type | Fields |
|---|---|
Heartbeat | node_name: String, node_id: u32, timestamp_ns: u64, sequence: u64, health: HealthStatus |
DiagnosticStatus | level: StatusLevel, code: u32, message: String, values: Vec<DiagnosticValue> |
DiagnosticReport | statuses: Vec<DiagnosticStatus>, timestamp_ns: u64 |
EmergencyStop | triggered: bool, source: String, reason: String, timestamp_ns: u64 |
SafetyStatus | state: NodeStateMsg, health: HealthStatus, violations: Vec<String> |
ResourceUsage | cpu_percent: f32, memory_bytes: u64, thread_count: u32 |
NodeHeartbeat | node_name: String, tick_count: u64, health: HealthStatus |
Input
| Type | Fields |
|---|---|
JoystickInput | joystick_id: u32, input_type: InputType, button_id/axis_id: u32, value: f32 |
KeyboardInput | key: String, code: u32, modifiers: Vec<String>, pressed: bool |
Clock
| Type | Fields |
|---|---|
Clock | timestamp_ns: u64, clock_type: ClockType |
TimeReference | time_ref_ns: u64, source_name: String, offset_ns: i64 |
Python API
Node (Functional)
| Parameter | Type | Default | Description |
|---|---|---|---|
name | Optional[str] | Auto-generated UUID | Unique node name |
subs | str, list, or dict | None | Topics to subscribe to |
pubs | str, list, or dict | None | Topics to publish to |
tick | Callable[[Node], None] | None | Main loop callback (can be async def) |
init | Callable[[Node], None] | None | Startup callback |
shutdown | Callable[[Node], None] | None | Cleanup callback |
on_error | Callable[[Node, Exception], None] | None | Error handler |
rate | float | 30 | Tick rate in Hz |
Node Methods
| Method | Signature | Description |
|---|---|---|
has_msg | has_msg(topic: str) -> bool | Check if messages available (peeks) |
recv | recv(topic: str) -> Optional[Any] | Receive next message |
recv_all | recv_all(topic: str) -> List[Any] | Drain all available messages |
send | send(topic: str, data: Any) -> bool | Send data to topic |
log_info | log_info(message: str) -> None | Log info (during tick only) |
log_warning | log_warning(message: str) -> None | Log warning (during tick only) |
log_error | log_error(message: str) -> None | Log error (during tick only) |
log_debug | log_debug(message: str) -> None | Log debug (during tick only) |
request_stop | request_stop() -> None | Request scheduler shutdown |
publishers | publishers() -> List[str] | List pub topic names |
subscribers | subscribers() -> List[str] | List sub topic names |
Scheduler
| Method | Signature | Description |
|---|---|---|
__init__ | Scheduler(*, tick_rate=1000.0, rt=False, deterministic=False, blackbox_mb=0, watchdog_ms=0, recording=False) | Constructor |
add | add(node, order=100, rate=None, rt=False, failure_policy=None, on_miss=None, budget=None, deadline=None) -> Scheduler | Add node with options |
node | node(node) -> NodeBuilder | Fluent builder API |
run | run(duration=None) -> None | Run scheduler (blocks). Pass seconds or None for forever |
stop | stop() -> None | Signal stop |
set_node_rate | set_node_rate(name: str, rate: float) -> None | Change node rate at runtime |
get_node_stats | get_node_stats(name: str) -> Dict | Per-node metrics |
get_all_nodes | get_all_nodes() -> List[Dict] | All node metrics |
get_node_count | get_node_count() -> int | Number of registered nodes |
has_node | has_node(name: str) -> bool | Check node exists |
get_node_names | get_node_names() -> List[str] | All node names |
status | status() -> str | Scheduler status string |
capabilities | capabilities() -> Optional[Dict] | RT capabilities |
has_full_rt | has_full_rt() -> bool | Full RT available? |
safety_stats | safety_stats() -> Optional[Dict] | Budget overruns, deadline misses |
current_tick | current_tick() -> int | Tick counter |
horus.run()
| Parameter | Type | Description |
|---|---|---|
*nodes | Node | Node instances to run (positional) |
duration | Optional[float] | Seconds to run (None = forever) |
tick_rate | float | Global tick rate in Hz (default: 1000.0) |
deterministic | bool | SimClock, fixed dt, seeded RNG |
rt | bool | Enable memory locking + RT scheduling |
watchdog_ms | int | Watchdog timeout (0 = disabled) |
blackbox_mb | int | Flight recorder size (0 = disabled) |
recording | bool | Enable session recording |
CLI Commands
| Command | Usage | Description |
|---|---|---|
horus new | horus new mybot [--python|--rust|--cpp] | Create a new project |
horus init | horus init [-n name] | Initialize workspace in current directory |
horus run | horus run [files...] [-r] [--record name] | Build and run project |
horus build | horus build [files...] [-r] [-c] | Build without running |
horus test | horus test [FILTER] [-r] [--sim] [--integration] | Run tests |
horus check | horus check [PATH] [--full] [--health] | Validate manifest and sources |
horus clean | horus clean [--shm] [--all] [-n] | Clean build artifacts and SHM |
horus launch | horus launch file.yaml [--dry-run] | Launch multiple nodes from YAML |
horus topic | horus topic list|echo|pub|info | Topic introspection |
horus node | horus node list|info|kill | Node management |
horus param | horus param get|set|list|delete | Runtime parameters |
horus frame | horus frame list|echo|tree | TransformFrame operations (alias: horus tf) |
horus service | horus service list|call|info | Service interaction |
horus action | horus action list|info|send-goal|cancel-goal | Action interaction |
horus msg | horus msg list|show|fields | Message type introspection |
horus log | horus log [NODE] [-l level] [-f] [-n N] | View and filter logs |
horus blackbox | horus blackbox [-a] [-f] [--json] | Flight recorder inspection |
horus monitor | horus monitor [PORT] [--tui] | Live TUI/web dashboard |
horus install | horus install name[@ver] [--driver|--plugin] | Install package or driver |