craigm26 / craigm26/OpenCastor

Flashable ESP32 image: the six-dollar robot, browser-flashed, no toolchain

Open
#941 7 comments 0 reactions 0 assignees View on GitHub
marathon-2026-08-15 ten-minute
Dominant language
Python
Stars
28
Forks
5
PR merge metrics
No merged PRs in 30d

Description

## Why
The Pi image (#931) makes the ten-minute clock beatable on a Pi 5 — but the cheapest COMMON hardware is a ~$6 ESP32 devkit, and for a fixed-function vehicle a Pi is overkill. The ESP32 natively has 16 LEDC PWM channels (steering + ESC with no PCA9685 at all), Wi-Fi, and enough headroom to verify Ed25519 envelopes on-device (libsodium/monocypher both run on it). Browser flashing over WebSerial (ESPHome-web-installer style) means the path needs no SD card, no toolchain, no terminal — arguably a FASTER ten minutes than the Pi.

## What
An `opencastor-esp32` firmware plus a web-flasher page:
- Flash from Chrome via WebSerial (ESP Web Tools / esptool-js) — plug in USB, click Install
- First boot: captive-portal Wi-Fi provisioning (the phone joins the setup AP, picks the home network)
- Device generates its identity, then serves the pairing QR at `http://castor-.local` — same payload shape `castor pair` writes
- On-device RCAN verifier: the firmware IS the gateway for this class — read + actuate tiers only, same signed-envelope contract, same refusals; no unsigned control path, no debug write surface
- Drive on its own PWM with neutral-on-timeout deadman IN FIRMWARE (mirror rc-car-actuator's shutdown-neutral semantics)
- No local models — the phone is the only brain; chat rides phone tiers

## Authority rule
Identical to the whole stack: declaring is not permitting, and the signed envelope is the ONLY path to the PWM pins. A firmware build with a bypass (telnet, unauthenticated HTTP control, OTA that skips signing) is a non-starter.

## Acceptance
- Blank ESP32 devkit → browser-flashed → Wi-Fi provisioned → QR scanned → app paired and a signed drive.set twitches a servo — under ten minutes wall-clock, zero installed tools
- Kill power or drop Wi-Fi mid-command → outputs at neutral (bench-verify, same discipline as the PCA9685 latch lesson)
- Envelope refusal behavior matches the Python gateway on the same malformed/over-budget inputs (golden-vector test shared between the two)

## Pointers
- #931 (the Pi path this mirrors), castor/pairing.py `capability_surface_from_manifest` (QR payload projection)
- rc-car-actuator `pca9685.py` / `install_shutdown_neutral` — the neutral/deadman semantics to port
- robot-md-gateway envelope verification (the contract the firmware verifier must match byte-for-byte)

Contributor guide

Open the contributing guide

Research direction

Start with #931, castor/pairing.py capability_surface_from_manifest, rc-car-actuator pca9685.py and install_shutdown_neutral, and robot-md-gateway envelope verification. Compare the existing pairing, neutral/deadman, and envelope contracts before scoping the ESP32 firmware and browser flasher. Done means the stated browser-to-paired-drive flow, neutral-on-failure behavior, and shared malformed-envelope vectors pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, python
Domain
embedded-iot, networking, robotics, security, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.