Python Deployment

How to get your Python HORUS nodes running on a real robot.


Dependencies & Environments

horus run resolves your Python dependencies automatically. It does not create a virtualenv — instead it generates .horus/pyproject.toml from your horus.toml and installs any declared PyPI dependencies with pip:

# Generates .horus/pyproject.toml and resolves declared PyPI deps via pip,
# then launches your node. No .horus/venv is created.
horus run src/main.py
# horus.toml — declare Python deps under [dependencies] with source = "pypi"
[dependencies]
numpy    = { version = ">=1.24", source = "pypi" }
pyserial = { version = ">=3.5",  source = "pypi" }

.horus/ holds generated build artifacts (including pyproject.toml) — never edit it by hand.

If you want an isolated interpreter, manage a virtualenv yourself and run the entry file directly:

python3 -m venv .venv
source .venv/bin/activate
pip install horus smbus2 pyserial numpy
python src/main.py

Docker

FROM python:3.12-slim

# Install system deps for hardware access
RUN apt-get update && apt-get install -y \
    i2c-tools \
    && rm -rf /var/lib/apt/lists/*

# Install HORUS
RUN curl -fsSL https://horusrobotics.dev/install | bash

# Install Python deps
COPY horus.toml .
RUN pip install smbus2 pyserial numpy

# Copy project
COPY src/ src/

# Run
CMD ["horus", "run", "src/main.py"]
# Build
docker build -t my-robot .

# Run with hardware access
docker run --rm \
    --device /dev/i2c-1 \
    --device /dev/ttyUSB0 \
    --cap-add SYS_NICE \
    my-robot

Key flags:

  • --device /dev/i2c-1 — I2C bus access
  • --device /dev/ttyUSB0 — Serial port access
  • --cap-add SYS_NICE — RT scheduling permissions
  • --privileged — Full hardware access (use only if needed)

systemd Service

Auto-start your robot on boot:

# /etc/systemd/system/my-robot.service
[Unit]
Description=My Robot HORUS Node
After=network.target

[Service]
Type=simple
User=robot
WorkingDirectory=/home/robot/my-robot
ExecStart=/home/robot/.cargo/bin/horus run src/main.py
Restart=on-failure
RestartSec=5
Environment=PYTHONPATH=/home/robot/my-robot

# RT scheduling permissions
AmbientCapabilities=CAP_SYS_NICE
LimitRTPRIO=99
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable my-robot
sudo systemctl start my-robot
sudo journalctl -u my-robot -f  # View logs

ARM Platforms

Raspberry Pi

# Install HORUS
curl -fsSL https://horusrobotics.dev/install | bash

# NumPy wheels are available for Pi 4/5 (aarch64)
pip install numpy

# I2C access
sudo apt install i2c-tools python3-smbus
sudo usermod -aG i2c $USER

# GPIO access (for direct GPIO, not via horus drivers)
pip install RPi.GPIO  # or gpiod for modern interface

NVIDIA Jetson

# NumPy + CUDA pre-installed on JetPack
# PyTorch available via NVIDIA's wheel index:
pip install torch --index-url https://pypi.ngc.nvidia.com

# Install HORUS
curl -fsSL https://horusrobotics.dev/install | bash

Common ARM Issues

IssueFix
numpy install fails (compilation)Use pip install numpy on aarch64 (wheels available for Pi 4+)
scipy takes 30+ min to installUse apt install python3-scipy instead of pip
opencv-python failsUse apt install python3-opencv or pip install opencv-python-headless
Permission denied on /dev/i2c-*sudo usermod -aG i2c $USER then re-login
Permission denied on /dev/ttyUSB*sudo usermod -aG dialout $USER then re-login

Mixed Rust + Python Deployment

For robots with both Rust and Python nodes, use a launch file:

# launch.yaml
nodes:
  - name: motor_driver
    command: "horus run src/motor.rs"
    rate_hz: 1000

  - name: ml_detector
    command: "horus run src/detector.py"
    rate_hz: 30
    depends_on: [motor_driver]
horus launch launch.yaml

Both processes share topics via shared memory — zero-copy between Rust and Python.


Freeze Dependencies

Pin exact versions for reproducible deploys:

# Generate lockfile from horus.toml
horus lock

# Or with pip
pip freeze > requirements.txt

See Also

Spotted an error on this page?