Skip to main content

OpenCastor v2026.3.12.0: Smarter Hardware Detection

6 min read By Craig Merry
opencastor hardware robotics raspberry-pi

OpenCastor v2026.3.12.0: Smarter Hardware Detection

v2026.3.12.0 closes five issues from the backlog (#537–#541): four new detectors and one expanded lookup table. The theme is differentiation: cases where OpenCastor could see a device but couldn’t tell you what it actually was. U2D2 vs. generic FTDI. RPLidar vs. YDLIDAR. A camera with an NPU that may or may not have its firmware directory present. Feetech boards that could be a single SO-ARM101 or a bimanual leader-follower pair. This release makes all five of those cases explicit.

Dynamixel U2D2 Detection (#537)

The U2D2 uses FTDI’s FT232RL chip (VID 0x0403, manufacturer string "FTDI"), which meant it was previously caught by the generic FTDI fallback path — same bucket as any FTDI serial adapter. That’s fine for “something is there,” but not for suggest_preset() — you don’t want a random FTDI dongle suggesting "dynamixel_arm".

detect_dynamixel_usb() now matches against a KNOWN_DYNAMIXEL_DEVICES table of specific U2D2 PIDs — 0403:6001 (FT232RL, standard U2D2) and 0403:6014 (FT232H, U2D2-H) — rather than matching any FTDI VID. If you have an unusual setup, run lsusb -v to see what your device enumerates as.

Only verified U2D2 matches trigger suggest_preset() with the dynamixel_arm profile. Everything else that matches FTDI’s VID stays in the generic bucket. The castor scan output gains a preset field when a U2D2 is present:

USB Devices:
  [✓] Dynamixel U2D2      /dev/ttyUSB0   preset: dynamixel_arm

I2C Device Lookup (#538)

v2026.3.11.0 introduced I2C scanning with a lookup table covering a few common sensors (BNO055 at 0x28/0x29, MPU6050 at 0x68/0x69, BME280 at 0x76/0x77). detect_i2c_devices() in this release expands that table with five more sensor types:

AddressDevice
0x29VL53L1X (ToF)
0x3C / 0x3DSSD1306/SH1106 OLED
0x48–0x4BADS1115 ADC
0x6A / 0x6BLSM6DSO / LSM6DSOX IMU
0x1EHMC5883L / QMC5883L magnetometer

Note: 0x29 is shared by VL53L1X and BNO055 (from the 3.11 table) — the scanner labels it "VL53L1X or BNO055 (alt)" to make the ambiguity explicit rather than silently picking one.

Note on magnetometers: Honeywell discontinued the HMC5883L. QST Corporation developed the QMC5883L — a pin-compatible but internally distinct replacement — using Honeywell’s AMR sensor technology under license. The two chips share the same breakout form factor but use different I2C addresses — HMC5883L at 0x1E, QMC5883L at 0x0D. The lookup table covers 0x1E; if your compass isn’t showing up, run i2cdetect -y 1 to check whether it’s actually at 0x0D (a QMC5883L on a mislabeled board).

If smbus2 isn’t installed, it falls back to reading /sys/bus/i2c/devices/ sysfs entries. This only surfaces devices that have a kernel driver bound — it won’t find a bare IMU with no kernel module, but it will find anything that the OS already knows about. If neither smbus2 nor sysfs entries are available, the function returns an empty list rather than crashing. Zero new required packages.

The output shows up in castor scan under i2c_devices:

"i2c_devices": [
  { "bus": 1, "address": "0x29", "name": "VL53L1X" },
  { "bus": 1, "address": "0x76", "name": "BME280" }
]

RPLidar vs. YDLIDAR (#539)

Both RPLidar (older hardware) and YDLIDAR share the CP2102 USB chip: VID 0x10C4 / PID 0xEA60. Previously we couldn’t tell them apart and just reported “CP2102 serial” with no lidar suggestion.

The fix reads the USB product, description, and manufacturer strings from the device descriptor and uppercases the combined result. "YDLIDAR" in that string → ydlidar; "RPLIDAR" or "SLAMTEC" → rplidar; anything else → unknown_lidar. If the pyserial port list isn’t available, it falls back to lsusb output and classifies as unknown_lidar (model unknown). If your lidar model isn’t identified correctly, run lsusb -v and open an issue with the product string output.

STM32 VCP (VID 0x0483 / PID 0x5740) is used by both newer RPLidar and YDLIDAR T15 — this VID/PID is in the lidar match set and goes through the same product-string disambiguation. If you see your lidar enumerate on a different VID/PID entirely, open an issue with lsusb -v output and we’ll add it.

suggest_extras() maps to the right package for each:

  • RPLidar → rplidar (PyPI)
  • YDLIDAR → ydlidar (PyPI)

Raspberry Pi AI Camera (#540)

The Pi AI Camera (IMX500) is interesting because the sensor has an on-chip NPU. Whether that NPU is usable depends on whether the firmware directory is present — specifically /lib/firmware/imx500/, which is created by the imx500-tools apt package. detect_rpi_ai_camera() reports both the camera presence and the firmware state separately, so you know before trying to run inference.

sudo apt install imx500-tools   # installs NPU firmware

Detection uses three steps:

  1. libcamera-hello --list-cameras output for the string "imx500" — fastest on a live system
  2. /proc/device-tree/model — checks device-tree for IMX500 mention
  3. V4L2 sysfs (/sys/class/video4linux/*/name) — works even if libcamera tools aren’t installed

The resulting entry in castor scan:

"rpi_ai_camera": {
  "detected": true,
  "npu": true
}

npu: false means the camera is present but the /lib/firmware/imx500/ directory is absent — you can run the camera but not on-chip inference. Useful to surface before someone tries to load a model and gets a confusing error.

LeRobot SO-ARM101 Detection (#541)

This one required combining two detection signals. The SO-ARM100/SO-ARM101 arms use six Feetech STS3215 serial bus servos controlled via a CH340-based USB adapter. That adapter has a distinct product string (see the v2026.3.11.0 notes for the disambiguation). Serial port count tells you which profile:

  • 1 serial port → so_arm101 (single arm)
  • 2+ serial ports → aloha (dual-arm leader + follower pair)

detect_lerobot_hardware() runs both checks and returns a compatible and profile field:

"lerobot": {
  "compatible": true,
  "profile": "aloha"
}

The [lerobot] extras group has been around since v2026.3.11.0 (with the broken scservo-sdk package name fixed in v2026.3.11.1). What’s new in this release is that gym-pusht and gym-aloha are now included alongside feetech-servo-sdk and dynamixel-sdk:

pip install opencastor[lerobot]

When LeRobot hardware is detected, suggest_extras() tells you exactly what to install. No more hunting through the README.

Full castor scan Output

A Pi 5 running a single SO-ARM101 arm, with a BME280 and LSM6DSO on the I2C bus, and the Pi AI Camera with firmware loaded:

{
  "usb_devices": [
    { "vid": "0x1A86", "pid": "0x7523", "name": "Feetech USB (SO-ARM101)", "port": "/dev/ttyACM0" }
  ],
  "i2c_devices": [
    { "bus": 1, "address": "0x76", "name": "BME280" },
    { "bus": 1, "address": "0x6A", "name": "LSM6DSO" }
  ],
  "rpi_ai_camera": { "detected": true, "npu": true },
  "lerobot": { "compatible": true, "profile": "so_arm101" },
  "suggested_extras": ["opencastor[lerobot]"]
}

Upgrade

pip install --upgrade opencastor
# or on alex/bob
castor upgrade

What’s Next

More driver work, and the next RCAN spec work is capability advertisement — robots publishing their detected hardware into the Robot Registry Foundation registry. Getting detection right here is the prerequisite for that. Hardware detection PRs are very welcome — see CONTRIBUTING.md for the detector interface and how to add new VID/PID entries.