Hardware API

Load hardware node configurations from horus.toml and use them in your Python nodes.

# simplified
import horus

entries = horus.hardware.load()
for name, obj in entries:
    print(f"Loaded: {name}")

Loading Hardware

hardware.load()

Reads the [hardware] section from horus.toml (searches current directory and up to 10 parents). Returns a list of (name, obj) tuples.

If a registered Python class matches the use field, obj is an instance of that class. Otherwise, obj is a NodeParams dict-like with the config values.

# simplified
entries = horus.hardware.load()

hardware.load_from(path)

Load from a specific config file. Useful for testing.

# simplified
entries = horus.hardware.load_from("tests/test_hardware.toml")

NodeParams

Typed access to config values from a [hardware.NAME] table.

MethodReturnsDescription
params.get(key)valueRequired param — raises KeyError if missing
params.get_or(key, default)valueOptional param with fallback
params.has(key)boolWhether key exists
params.keys()list[str]All param names
len(params)intNumber of params
params[key]valueDict-like access
key in paramsboolDict-like contains

TOML types are auto-converted to Python: str, int, float, bool, list.

# simplified
entries = horus.hardware.load()
for name, obj in entries:
    if isinstance(obj, horus.NodeParams):
        port = obj.get_or("port", "/dev/ttyUSB0")
        baud = obj.get_or("baudrate", 115200)
        print(f"{name}: {port} @ {baud}")

Registering Python Drivers

Register a Python class so hardware.load() instantiates it automatically when the use field matches:

# simplified
import horus

class ConveyorDriver(horus.Node):
    def __init__(self, params):
        super().__init__(
            name="conveyor",
            pubs=["conveyor.velocity"],
            rate=params.get_or("rate", 50),
        )
        self.port = params.get_or("port", "/dev/ttyACM0")
        self.speed = params.get_or("speed", 1.0)

    def tick(self):
        self.send("conveyor.velocity", horus.CmdVel(self.speed, 0.0))

# Register the class
horus.hardware.register_driver("ConveyorDriver", ConveyorDriver)

Then in horus.toml:

[hardware.conveyor]
use = "ConveyorDriver"
port = "/dev/ttyACM0"
speed = 0.5

And in your main script:

# simplified
entries = horus.hardware.load()
nodes = [obj for _, obj in entries if isinstance(obj, horus.Node)]
horus.run(*nodes)

Complete Example

# horus.toml
[hardware.imu]
use = "Bno055Driver"
bus = 1
address = 104

[hardware.motors]
use = "MotorDriver"
port = "/dev/ttyUSB0"
baudrate = 115200
# simplified
import horus
import smbus2
import serial

class Bno055Driver(horus.Node):
    def __init__(self, params):
        super().__init__(name="imu", pubs=["imu"], rate=100)
        bus_num = params.get_or("bus", 1)
        self.addr = params.get_or("address", 0x68)
        self.i2c = smbus2.SMBus(bus_num)

    def tick(self):
        data = self.i2c.read_i2c_block_data(self.addr, 0x08, 6)
        self.send("imu", horus.Imu(linear_acceleration=parse_accel(data)))

class MotorDriver(horus.Node):
    def __init__(self, params):
        super().__init__(name="motors", subs=["cmd_vel"], rate=50)
        port = params.get("port")
        baud = params.get_or("baudrate", 115200)
        self.ser = serial.Serial(port, baud)

    def tick(self):
        if self.has_msg("cmd_vel"):
            cmd = self.recv("cmd_vel")
            self.ser.write(encode_motor_cmd(cmd))

horus.hardware.register_driver("Bno055Driver", Bno055Driver)
horus.hardware.register_driver("MotorDriver", MotorDriver)

entries = horus.hardware.load()
nodes = [obj for _, obj in entries if isinstance(obj, horus.Node)]
horus.run(*nodes)

Simulation Override

Mark hardware entries with sim = true to swap them for stubs in simulation mode:

[hardware.imu]
use = "Bno055Driver"
bus = 1
sim = true
horus run          # real hardware
horus run --sim    # sim mode — stub nodes, simulator publishes to same topics

Error Handling

# simplified
try:
    entries = horus.hardware.load()
except Exception as e:
    print(f"No hardware config: {e}")
    entries = []

for name, obj in entries:
    if isinstance(obj, horus.NodeParams):
        print(f"  {name}: no registered class (params only)")

Legacy Support

The [drivers] section name and old source keys (terra, node, package) are still parsed. Migrate to [hardware] with the use field:

# Old
[drivers.imu]
terra = "mpu6050"
bus = 1

# New
[hardware.imu]
use = "mpu6050"
bus = 1
sim = true

Use horus.hardware to access hardware drivers.


See Also