OpenCastor v2026.3.11.1: Making the Install Experience Not Terrible
OpenCastor v2026.3.11.1: Making the Install Experience Not Terrible
I reinstalled alex.local last week. Fresh Pi OS. aarch64. Python 3.13. And I hit 16 separate rough edges before I had OpenCastor running cleanly again.
That’s too many. This release fixes them.
What Actually Broke
The scservo-sdk PyPI Ghost
pip install opencastor[lerobot] failed immediately. The package name in pyproject.toml was scservo-sdk>=1.0. That package doesn’t exist on PyPI. The correct name is feetech-servo-sdk. Anyone trying to install LeRobot servo support has been hitting this wall since we added the extra. Fixed — it’s feetech-servo-sdk now.
OAK-D SR Showing as “bootloader/lite”
The hardware scanner was doing a case-sensitive match on lsusb output. The OAK-D SR has VID/PID 03e7:f63b. lsusb prints it in lowercase. Our lookup table had it in mixed case. Result: the OAK-D SR was coming back as “Luxonis device (bootloader/lite)” instead of “Luxonis OAK-D SR”. One line fix — normalize to lowercase before the lookup.
The systemd Binary Path Problem
The service templates we generate hardcode ExecStart=/path/to/castor gateway. That path is the castor script inside your venv’s bin/. When you upgrade Python or recreate your venv, that binary path changes. The service breaks silently and you get “address already in use” on next boot because the old service left a zombie.
The fix: use python -m castor.cli gateway instead of the binary. Same for the dashboard — python -m streamlit run instead of streamlit. These survive venv migrations because python always points to the right interpreter.
libcamera Spam
Every castor scan invocation on a Pi was dumping 30+ lines of libcamera log output to stderr before printing any results. Nobody needs [INFO] [0.123456] RPiCameraFrontend - Using sensor configuration.... Fixed by setting LIBCAMERA_LOG_LEVELS=*:FATAL at hardware_detect.py import time.
What’s New
castor scan
The CLI subcommand was already there as an internal function. Now it’s a proper top-level command with flags:
castor scan # human-readable output
castor scan --json # machine-readable
castor scan --refresh # bypass the 30-second cache
castor scan --preset-only # just print the suggested RCAN preset name
The detect_hardware() function now has a 30-second TTL cache. Scanning USB buses is slow. Running it on every API call was adding 2–3 seconds to /api/hardware/scan. Cache it once, invalidate on demand.
scan_cameras() also got smarter — it reads the v4l2 device name from sysfs instead of just printing /dev/video0. You now get “Integrated Camera” or “USB 2.0 Camera” instead of a device node.
castor doctor Hardware Checks
castor doctor already checked disk space, memory, swap, CPU temperature, and GPU VRAM. Now it also checks whether you have the right packages for whatever hardware it detects.
If it finds an OAK-D: warns you if depthai isn’t installed. Finds a Reachy: warns about reachy2-sdk. The suggest_extras(hw) function maps detected hardware keys to missing pip packages so you know exactly what to install.
castor upgrade
castor upgrade # git pull + pip install -e . + restart service
castor upgrade --check # just show pending commits, don't apply
castor upgrade --venv PATH # use a specific venv python
Before this, upgrading meant: SSH in, git pull, pip install -e . --break-system-packages, systemctl --user restart castor-gateway.service, check status. Every. Time. Now it’s one command.
castor stop
The gateway writes its PID to ~/.opencastor/gateway.pid on startup. castor stop reads that file and sends SIGTERM. Clean shutdown. No more kill $(lsof -ti:8000) hunting.
On startup, the gateway also checks if port 8000 is already in use and fails fast with a clear error message instead of crashing with a stack trace.
Gateway Systemd Hardening
The generated service files now include:
KillMode=control-group
TimeoutStopSec=15
SendSIGKILL=yes
ExecStartPre=/bin/sh -c 'fuser -k 8000/tcp 2>/dev/null || true'
KillMode=control-group means systemd kills the entire process group, not just the main PID. Important because the gateway spawns child processes for some channel integrations. ExecStartPre clears port 8000 before starting so a previous zombie doesn’t block the new instance.
The Pi OS Venv Situation
This is the big one for anyone running on Raspberry Pi OS (Bookworm or later).
PEP 668 makes pip refuse to install packages system-wide. You need a venv. But on Pi, you often need picamera2 and libcamera Python bindings — and those only exist as system packages (apt install python3-picamera2). They can’t be pip-installed inside a regular venv.
The solution is --system-site-packages:
python3 -m venv ~/opencastor-env --system-site-packages
source ~/opencastor-env/bin/activate
pip install opencastor
This venv inherits system packages (picamera2, libcamera, numpy from apt) while keeping OpenCastor and its deps isolated. This is documented properly in the new upgrade guide.
16 Issues → 342 Tests
We went from 16 open install-related issues to 0. Added 12 new tests covering OAK-D SR detection, the TTL cache, v4l2 device names, and suggest_extras(). 342 tests total, 0 ruff issues.
If you hit anything during a fresh install, file an issue. The goal is zero friction from pip install opencastor to castor scan showing your hardware correctly.