C++ Hardware Integration

Pattern: Hardware Driver Node

Every hardware device gets its own horus::Node subclass:

class LidarDriver : public horus::Node {
public:
    LidarDriver(const char* port) : Node("lidar_driver"), port_(port) {
        scan_pub_ = advertise<horus::msg::LaserScan>("lidar.scan");
    }

    void init() override {
        // Open serial port, configure device
        fd_ = open(port_, O_RDWR | O_NOCTTY);
        if (fd_ < 0) {
            horus::log::error("lidar", "Failed to open port");
        }
        horus::log::info("lidar", "Connected");
    }

    void tick() override {
        if (fd_ < 0) return;

        // Read raw data from device
        uint8_t buf[2048];
        int n = read(fd_, buf, sizeof(buf));
        if (n <= 0) return;

        // Parse into LaserScan message
        auto scan = scan_pub_->loan();
        parse_rplidar_packet(buf, n, scan.get());
        scan_pub_->publish(std::move(scan));
    }

    void enter_safe_state() override {
        // Stop motor on safety event
        if (fd_ >= 0) {
            uint8_t stop_cmd[] = {0xA5, 0x25};
            write(fd_, stop_cmd, 2);
        }
    }

private:
    const char* port_;
    int fd_ = -1;
    horus::Publisher<horus::msg::LaserScan>* scan_pub_;

    void parse_rplidar_packet(const uint8_t* buf, int len, horus::msg::LaserScan* scan) {
        // Device-specific parsing...
        for (int i = 0; i < 360; i++) {
            scan->ranges[i] = 5.0f;  // placeholder
        }
    }
};

Multi-Device Robot

int main() {
    horus::Scheduler sched;
    sched.tick_rate(100_hz).prefer_rt();

    // Hardware drivers — highest priority, deterministic
    LidarDriver lidar("/dev/ttyUSB0");
    sched.add(lidar).order(0).rate(100_hz).budget(2_ms).build();

    ImuDriver imu("/dev/ttyUSB1");
    sched.add(imu).order(1).rate(200_hz).budget(1_ms).build();

    MotorDriver motors("/dev/ttyUSB2");
    sched.add(motors).order(2).rate(100_hz).budget(1_ms)
        .on_miss(horus::Miss::SafeMode).build();

    // Control — reads from hardware topics
    Controller ctrl;
    sched.add(ctrl).order(10).budget(5_ms).build();

    // Monitoring — lowest priority
    TelemetryLogger logger;
    sched.add(logger).order(100).build();

    sched.spin();
}

Coordinate Frames

Mount sensors with known transforms:

horus::TransformFrame tf;
tf.register_frame("world");
tf.register_frame("base_link", "world");
tf.register_frame("lidar", "base_link");
tf.register_frame("camera", "base_link");

// Static sensor mounts
tf.update("lidar",  {0.2, 0.0, 0.3}, {0, 0, 0, 1}, 0);
tf.update("camera", {0.1, 0.0, 0.25}, {0, 0, 0.04, 1.0}, 0);

// In processing node:
auto lidar_in_world = tf.lookup("lidar", "world");

Runtime Parameters

Tune hardware settings without recompiling:

horus::Params params;
params.set("lidar_rpm", int64_t(600));
params.set("motor_max_torque", 2.5);
params.set("imu_calibration_offset", 0.02);

// In driver tick:
double max_torque = params.get<double>("motor_max_torque", 1.0);

CLI Integration

While your C++ robot runs:

horus topic list          # see all active topics
horus topic echo lidar.scan  # watch raw sensor data
horus node list           # see node status, tick rates, CPU usage
horus log                 # see log messages from all nodes