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