Multi-Language Support

HORUS supports multiple programming languages, allowing you to choose the best tool for each component of your robotics system.

Supported Languages

Rust (Native)

Best for: High-performance nodes, control loops, real-time systems

HORUS is written in Rust and provides the most complete API. All examples in the documentation use Rust by default.

Getting Started:

horus new my-project
# Select: Rust (option 2)

Learn more: Quick Start

Python

Production-Ready: Full-featured Python API with advanced capabilities

Best for: Rapid prototyping, AI/ML integration, data processing, visualization

Python bindings (via PyO3) provide a simple, Pythonic API for HORUS with production-grade features:

  • Per-node rate control - Different rates for different nodes (100Hz sensor, 10Hz logger)
  • Automatic timestamps - Built-in message timing and staleness detection
  • Typed messages - Type-safe messages shared with Rust (CmdVel, Pose2D, Imu, Odometry, LaserScan)
  • Generic messages - Send any Python type (dicts, lists, etc.) between Python nodes
  • Multiprocess support - Process isolation via shared memory

Perfect for integrating with NumPy, PyTorch, TensorFlow, and other Python libraries.

Getting Started:

horus new my-project
# Select: Python (option 1)

Learn more: Python Bindings

Cross-Language Communication

Python and Rust nodes communicate through HORUS's shared memory system. For cross-language communication, both sides must use the same typed message types.

Typed Topics for Cross-Language Communication

Pass a message type class to the Python Topic() constructor to create a typed topic that Rust can read:

# Python publisher (pose_publisher.py)
from horus import Topic, Pose2D

# Create typed topic - the type determines the topic name and serialization
topic = Topic(Pose2D)

# Send a Pose2D message (shared memory, readable by Rust)
pose = Pose2D(x=1.0, y=2.0, theta=0.5)
topic.send(pose)
// Rust subscriber (in another process)
use horus::prelude::*;

let topic: Topic<Pose2D> = Topic::new("pose")?;
if let Some(pose) = topic.recv() {
    println!("Received: x={}, y={}, theta={}", pose.x, pose.y, pose.theta);
}

Supported Typed Message Types

These types work for Python-Rust cross-language communication:

Message TypePython ConstructorDefault Topic NameUse Case
CmdVelCmdVel(linear, angular)cmd_velVelocity commands
Pose2DPose2D(x, y, theta)pose2D position
ImuImu(accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z)imuIMU sensor data
OdometryOdometry(x, y, theta, linear_velocity, angular_velocity)odomOdometry data
LaserScanLaserScan(angle_min, angle_max, ..., ranges=[...])scanLiDAR scans

All message types include an optional timestamp_ns field for nanosecond timestamps.

Usage examples:

from horus import Topic, CmdVel, Imu, LaserScan

# Velocity commands
cmd_topic = Topic(CmdVel)
cmd_topic.send(CmdVel(linear=1.5, angular=0.3))

# IMU data
imu_topic = Topic(Imu)
imu_topic.send(Imu(
    accel_x=0.0, accel_y=0.0, accel_z=9.81,
    gyro_x=0.0, gyro_y=0.0, gyro_z=0.1
))

# Receive (returns typed Python object or None)
if cmd := cmd_topic.recv():
    print(f"linear={cmd.linear}, angular={cmd.angular}")

Generic Topics (Python-to-Python Only)

For Python-to-Python communication, pass a string name to create a generic topic that can send any Python type:

from horus import Topic

# Generic topic - pass topic name as string
topic = Topic("my_data")
topic.send({"sensor": "lidar", "ranges": [1.0, 1.1, 1.2]})
topic.send([1, 2, 3])
topic.send("hello")

# Receive
if msg := topic.recv():
    print(msg)  # Python dict, list, string, etc.

Generic topics use JSON/MessagePack serialization internally. Rust nodes cannot read generic topics — use typed messages for cross-language communication.

When to use which:

  • Typed Topics (Topic(CmdVel), Topic(Pose2D)) — Cross-language Rust+Python or Python-only
  • Generic Topics (Topic("topic_name")) — Python-only systems with custom data

Python Node API

The Python Node class provides a simple callback-based API:

from horus import Node, Topic, Scheduler, CmdVel, Pose2D

class Controller(Node):
    def __init__(self):
        super().__init__("Controller")

    def init(self):
        self.pose_sub = Topic(Pose2D)
        self.cmd_pub = Topic(CmdVel)

    def tick(self):
        pose = self.pose_sub.recv()
        if pose is not None:
            # Compute velocity command from pose
            cmd = CmdVel(linear=1.0, angular=0.5)
            self.cmd_pub.send(cmd)

scheduler = Scheduler()
scheduler.node(Controller()).order(0).rate(30).build()
scheduler.run()

Key Topic methods:

  • topic.send(msg) — Send data to a topic
  • topic.recv() — Get next message (returns None if no messages)

Choosing a Language

Use CaseRecommended Language
Control loopsRust (lowest latency)
AI/ML modelsPython (ecosystem)
Hardware driversRust
Data processingPython or Rust
Real-time systemsRust
PrototypingPython (fastest development)

Mixed-Language Systems

You can build systems with nodes in different languages:

Example: Robot with mixed languages

  • Motor controller (Rust) — 1kHz control loop
  • Vision processing (Python) — PyTorch object detection
  • Hardware driver (Rust) — Sensor integration
  • Monitor (Rust) — Real-time visualization

All communicate through HORUS shared memory with sub-microsecond latency.

Running Mixed-Language Systems

The horus run command automatically handles compilation and execution of mixed-language systems:

# Mix Python and Rust nodes
horus run sensor.py controller.rs visualizer.py

# Mix Rust and Python
horus run lidar_driver.rs planner.py motor_control.rs

What happens:

  1. Rust files (.rs) are automatically compiled with cargo build using HORUS dependencies
  2. Python files (.py) are executed directly with Python 3
  3. All processes communicate via shared memory at /dev/shm/horus/
  4. horus run manages the lifecycle (start, monitor, stop all together)

Note: For Rust files, horus run creates a temporary Cargo project in .horus/ with proper dependencies, builds it with cargo build, and executes the resulting binary.

Example: Complete Mixed System

Python sensor node (sensor.py):

from horus import Node, Topic, Scheduler, LaserScan

class LidarSensor(Node):
    def __init__(self):
        super().__init__("LidarSensor")

    def init(self):
        self.scan_pub = Topic(LaserScan)

    def tick(self):
        scan = LaserScan(
            angle_min=-1.57,
            angle_max=1.57,
            angle_increment=0.01,
            range_min=0.1,
            range_max=10.0,
            ranges=[1.0, 1.1, 1.2, 0.9]
        )
        self.scan_pub.send(scan)

scheduler = Scheduler()
scheduler.node(LidarSensor()).order(0).rate(10).build()
scheduler.run()

Rust planner node (planner.rs):

use horus::prelude::*;

fn main() -> Result<()> {
    let scan_topic: Topic<LaserScan> = Topic::new("scan")?;
    let cmd_topic: Topic<CmdVel> = Topic::new("cmd_vel")?;

    loop {
        if let Some(scan) = scan_topic.recv() {
            let cmd = plan_path(&scan);  // Your planning logic
            cmd_topic.send(cmd);
        }
    }
}
# Run both together
horus run sensor.py planner.rs

# Both processes communicate via shared memory

Benefits:

  • No manual compilationhorus run handles it
  • Automatic dependency management — HORUS libraries linked correctly
  • Process isolation — One crash doesn't kill the whole system
  • True parallelism — Each process can use separate CPU cores

API Parity

FeatureRustPython
Topic send/recvtopic.send(msg) / topic.recv()topic.send(msg) / topic.recv()
Typed messagesTopic<CmdVel>Topic(CmdVel)
Generic messagesTopic<GenericMessage>Topic("name")
Node lifecycleinit(), tick(), shutdown()init(), tick(), shutdown() callbacks
SchedulerScheduler::new()Scheduler()
Node priority.order(n)order=n
Rate controlScheduler raterate=Hz per node
Backend hintsAutomatic (topology-based)backend="spsc" kwarg (hint)
Message typesFull horus_libraryCmdVel, Pose2D, Imu, Odometry, LaserScan + horus.library
TransformFrame transformsTransformFrame::new()TransformFrame()
Tensor systemNativeImage, PointCloud, DepthImage (pool-backed)
Logginghlog!(info, ...)node.log_info(...)

Next Steps

Choose your language:

Build something: