You've already read start-here.html — the narrative tour. This page is the reference manual: every launch.sh option, every platformio.ini directive, every "wait, what's actually happening here?" — explained with the underlying commands, failure modes, and cross-links to the audit log.

🚀 1 — Launcher anatomy — every option of launch.sh

The launch.sh menu has 20 numbered options grouped into sections. Numbers always work — letters (s, b, w, g) just expand the visual section.

Group: 🧰 Toolchain (s)

0 Setup my robot — one-click install ⭐ FAST PATH
What
Runs all 4 levels of bootstrap in sequence: Python check → PlatformIO install → ESP32 platform fetch → optional VS Code extension. Equivalent to running options 1, 2, 3, then opening VS Code.
Function
act_bootstrap() — calls act_check, act_install_pio, act_install_deps in order.
Duration
~5–10 minutes on a fresh machine (the ESP32 platform is ~150 MB).
Failure modes
  • No Python found → on Windows, runs winget install Python.Python.3.13 auto-magically. On macOS/Linux, you need to install Python 3.10+ manually first.
  • MSYS2 environment with broken POSIX Python → launcher detects this and points you to pacman -S mingw-w64-ucrt-x86_64-python.
  • Network failure during platform fetch → just rerun. PlatformIO resumes downloads.
See also
1 Check toolchain — what's installed? DIAG
What
Diagnostic-only. Prints what was found and what's missing: Python · PlatformIO Core · ESP32 platform · esptool. Doesn't change anything.
Function
act_check() — wraps have python, have pio, version probes.
When to use
First thing after cloning. Tells you which next options you need to run.
2 Install PlatformIO Core
What
Installs PlatformIO Core (the Python package, not the IDE). Makes pio available as a CLI command.
Underlying
pip install -U platformio
Size
~30 MB download.
Common gotcha
pio may install to a directory not on your PATH. The launcher works around this by always invoking via python -m platformio. To get bare pio working, add the suggested export PATH=... line to your ~/.bashrc.
3 Install ESP32 platform + libraries
What
Downloads the ESP32 platform package (Arduino-ESP32 core + RISC-V toolchain + IDF) and project library deps from platformio.ini. Pinned: platform 6.7.0 (ESP-IDF 5.1).
Underlying
pio pkg install
Size
~150 MB. The biggest single download in the project.
First-time only
Subsequent runs are no-ops if the cache is intact (~/.platformio/packages/).

Group: 🔥 Deploy (b)

4 Compile DAILY
What
Compiles the firmware. No upload. Output goes to .pio/build/esp32-c3-devkitm-1/.
Underlying
pio run
Output
Three binaries: bootloader.bin (offset 0x0) · partitions.bin (0x8000) · firmware.bin (0x10000) + an .elf with debug symbols.
Duration
~60 s on a clean cache, ~5–10 s incremental.
Failure modes
  • "library X not found" → run option 16 (update libs).
  • Linker error "section overflow" → firmware too big for partition. Adjust board_build.partitions.
  • "redefinition of NUM_LEDS" → known macro collision. See audit C5.
See also
5 Clean
What
Deletes the .pio/build/ directory. Forces a from-scratch recompile next time.
Underlying
pio run -t clean
When to use
  • After changing build_flags or lib_deps — incremental builds may not pick up the change.
  • When something feels haunted ("worked yesterday, broken today, no edits made").
  • Never on a critical path — adds 60 s to next build.
6 Upload (flash) DAILY
What
Uploads the already-compiled firmware to the chip. Must compile first (option 4 or option 10).
Underlying
pio run -t upload --upload-port COM8 (Windows) — port detected automatically or via option 9.
Step-by-step
  1. esptool toggles DTR/RTS to put the chip in download mode.
  2. Sends a sync ping at 115200 baud.
  3. Negotiates higher baud (typically 921600).
  4. Writes bootloader, partitions, firmware sequentially.
  5. Hard-resets the chip → it boots the new code.
Failure modes
  • "Failed to connect" → BOOT/RESET trick (Mission 09).
  • "Could not open COM8" → native USB CDC re-enumeration race on Windows (audit BUG-005).
  • Permission denied on Linux → sudo usermod -aG dialout $USER.
7 Erase flash DESTRUCTIVE
What
Wipes ALL flash memory on the chip. Bootloader, partitions, firmware, NVS, OTA, all gone.
Underlying
esptool --chip esp32c3 --port COM8 erase_flash
When to use
  • After flashing weird firmware that bricks the boot.
  • When NVS is corrupted (saved settings cause crashes).
  • Before transferring the chip to someone else.
Recovery
Re-run option 6 to reflash. Erased flash + flash → fully working chip again.
8 Serial monitor DAILY
What
Live serial output from the chip. Shows boot logs, Serial.println() messages, crash dumps.
Underlying
pio device monitor -b 115200 --port COM8
Quit
Ctrl+C on Linux/macOS. MSYS2 swallows it — use Ctrl+] or close the terminal (audit BUG-002).
Web alternative
monitor.html — same baud rate, browser-based, supports send.
9 Select port — picker
What
Lists detected serial devices (USB-to-UART chips and native USB-CDC like the C3) and asks which one to use for subsequent commands. Stores the choice in $PORT.
Underlying
pio device list + interactive prompt.
When auto-detect fails
Multiple chips plugged in · MSYS2 picking wrong one · the chip's COM port number changed after Windows enumeration. This menu fixes it.
Override from CLI
PORT=COM7 ./launch.sh upload
10 Full flash — compile + upload + monitor ⭐ ONE-SHOT
What
The complete dev cycle in one command. Compiles, uploads, then drops you into the serial monitor to watch the boot.
Underlying
act_compile && act_upload && act_monitor — chained with && so failure stops the chain.
When to use
Daily iteration. The mission you'll run 100 times.
Hotkey
./launch.sh flash — same thing, no menu.

Group: 🌐 Cyberspace (w)

11 Open in VS Code
What
Opens the project folder in VS Code. If the PlatformIO IDE extension isn't installed, prompts to install it (~70 MB download).
Underlying
code . + optional code --install-extension platformio.platformio-ide
Prereq
code command on PATH. Install via VS Code's command palette → "Install 'code' command in PATH".
12 PlatformIO Home — browser dashboard
What
Launches PlatformIO's local web UI. Project browser, library manager, board explorer, IDE-like view in your browser.
Underlying
pio home → opens http://localhost:8008/
When to use
When you want to browse PlatformIO's library registry visually, or compare boards.
13 Serve 02_web/ on localhost:8000 WEB
What
Starts a local HTTP server in 02_web/ and opens the browser. Lets you use flash.html + monitor.html (Web Serial requires HTTPS or localhost).
Underlying
cd 02_web && python -m http.server 8000
Use case
Workshop with multiple students — your laptop serves the tools, students hit http://<your-ip>:8000/ from their phones / tablets / chromebooks.
14 Package web flasher
What
Compiles the firmware then copies the 3 binaries to 02_web/firmware/ so flash.html can install them via Web Serial.
Underlying
pio run + cp .pio/build/esp32-c3-devkitm-1/{bootloader,partitions,firmware}.bin 02_web/firmware/
Required for
The web flasher to work. Without packaged binaries, flash.html shows nothing.
When to run
Every time you change firmware AND want web users to get the latest. Otherwise web users keep getting the previous build.

Group: 📜 Misc

15 Show recent logs
What
Lists files in 03_logs/ (past command outputs). Each ./launch.sh action with with_timer wrapping logs to a timestamped file.
Use case
Comparing successful builds against failing ones · grepping for error patterns across runs · auditing what was run when.
16 Update libraries + platform
What
Pulls newer versions of every lib_deps entry and the platform itself, respecting your version pins. Optionally upgrades PlatformIO Core (with confirmation).
Underlying
pio pkg update + optional pio upgrade
⚠️ Caution
Pinned versions like fastled/FastLED @ ^3.7.0 only allow patch upgrades. Updating ESP-IDF was the cause of BUG-001 in the first place — pinning helps, but always test after updating.

Group: 📡 Git ops (g)

17 git status
What
Shows uncommitted changes, untracked files, current branch, distance from origin.
Underlying
git status
18 git log — last 20 commits
What
Pretty one-liner log of recent commits.
Underlying
git log --oneline --decorate -20
19 git diff
What
Shows working-tree changes vs HEAD. Pipes through less if available.
Underlying
git diff HEAD
// CLI shortcuts Every menu option also has a CLI form: ./launch.sh compile · ./launch.sh upload · ./launch.sh monitor · ./launch.sh flash · ./launch.sh serve · ./launch.sh package-web. Skip the menu when you know what you want.
⚙️ 2 — platformio.ini decoded — every directive

Annotated walk-through of every line in this project's platformio.ini, with explanation and gotchas.

[platformio] section

DirectiveValueWhat it does
src_dir 01_src Where source files live. Default is src/; we override to 01_src/ so Arduino IDE folder-name rule still works.

[env:esp32-c3-devkitm-1] section

DirectiveValueWhat it does
platform espressif32 @ 6.7.0 Pinned — version 6.7.0 ships ESP-IDF 5.1 with the rewritten RMT driver. Earlier versions had the ISR re-entry race that crashed FastLED on C3 (BUG-001). The @ X.Y.Z syntax means EXACT version. Without the pin, you'd get whatever's latest at install time → silent regressions.
board esp32-c3-devkitm-1 Selects the board profile from PlatformIO's database. Determines: pin map, flash size (4 MB), partition layout, upload speed, default baud. The seeed_xiao_esp32c3 alternative (commented out) has a different pin map.
framework arduino Activates the Arduino-ESP32 API: setup()/loop(), pinMode(), Serial.println(), etc. Built on top of ESP-IDF — you can mix and match. The espidf alternative (commented) gives raw IDF with no Arduino layer.
monitor_speed 115200 Baud rate the serial monitor uses. Must match what your firmware calls in Serial.begin(). Wrong baud → garbage characters.
build_flags
FASTLED_RMT_MAX_CHANNELS 1 FastLED defaults to 8 RMT channels (ESP32 classic has 8). The C3 has only 2. Capping to 1 prevents FastLED from trying to claim non-existent channels. Safety: belt-and-suspenders even with the IDF 5.1 fix.
FASTLED_RMT_BUILTIN_DRIVER 1 Tells FastLED to use IDF's tested RMT driver instead of FastLED's bundled custom one. The custom driver was the cracked egg in BUG-001; this flag opts out of it.
lib_deps
RemoteXY (unconstrained) Phone-app library for Bluetooth control. NOT pinned because pinning didn't fix BUG-004 (PRO check is enforced server-side, not in the lib).
fastled/FastLED @ ^3.7.0 NeoPixel driver. ^3.7.0 means "3.x but ≥ 3.7" — patch updates allowed, major upgrades blocked.
madhephaestus/ESP32Servo (unconstrained) Servo PWM driver. Author prefix prevents PIO from grabbing a different lib of the same name.
adafruit/Adafruit GFX Library (unconstrained) Generic 2D drawing primitives — the OLED driver depends on it.
adafruit/Adafruit SSD1306 (unconstrained) Concrete OLED driver for the SSD1306 chip on our display.

Common directives NOT in this project (but useful to know)

DirectiveWhat it would do
upload_speedDefault for the C3 is auto-negotiated at 921600. Override only if you have flaky USB cables.
upload_protocolesp-builtin uses USB-JTAG instead of the CDC reset dance. Could fix BUG-005.
monitor_filtersesp32_exception_decoder auto-runs addr2line on crashes. Highly recommended for debugging.
board_build.partitionsCustom partition table CSV. Default is a balanced 1.2 MB app. huge_app.csv gives 3 MB if you don't need OTA.
board_build.usb_cdcToggles USB CDC console (Serial-over-USB). Already enabled by default for C3 boards with native USB.
extra_scriptsRun Python scripts at compile time — e.g. inject git hash into firmware version string.
// PIN, OR NOT TO PIN? Pin platforms hard (@ 6.7.0): platform changes mean toolchain + IDF changes — high blast radius.
Pin libraries with caret (@ ^X.Y.Z): patch fixes welcome, breaking changes blocked.
Leave libraries unpinned only when the lib is your own / very stable / when pinning made things worse (RemoteXY case).
💡 3 — ESP32-C3 hardware concepts

RMT — Remote Control Transceiver

Hardware peripheral for generating precise pulse trains without CPU involvement. Originally for IR remotes, but FastLED uses it to drive WS2812 NeoPixels at 800 kHz with sub-150-ns timing — impossible to bit-bang reliably.

ChipRMT channelsNotes
ESP32 classic8Lots of headroom, FastLED has no issues.
ESP32-S2 / S34Comfortable.
ESP32-C3 (this board)2Tight. Any IR receiver or second LED strip directly competes.
ESP32-C62Same as C3.

The full forensic — including the IDF 4.x ISR re-entry bug that triggered BUG-001 — is in the audit page.

Partition table

Flash memory is divided into named regions. The bootloader reads partitions.bin at offset 0x8000 to know where everything else lives.

# Default Arduino-ESP32 layout (4 MB flash):
nvs       data 0x9000   0x5000     # 20 KB — saved settings
otadata   data 0xe000   0x2000     # 8 KB — OTA pointer
app0      app  0x10000  0x140000   # 1.25 MB — your firmware
app1      app  0x150000 0x140000   # 1.25 MB — OTA backup
spiffs    data 0x290000 0x170000   # 1.4 MB — file system

If you don't need OTA, switch to huge_app.csv (3 MB single app slot). For audio assets, switch to large_spiffs.csv.

USB JTAG/Serial vs UART

The ESP32-C3 Super Mini has native USB — the chip itself implements USB-CDC over its built-in USB JTAG/Serial peripheral. There is NO external UART chip (no CP210x, no CH340).

AspectExternal UART (e.g. CP210x)Native USB CDC (this board)
SpeedUp to 921 600 baud~12 Mbps theoretical
Drivers (Windows)Manual install requiredBuilt into Win 10/11
Reset behaviorExternal chip toggles RTS/DTRChip resets itself · COM port disappears + re-enumerates
Failure modeDriver mismatchRe-enumeration race (BUG-005)

GPIO strapping pins — gotchas

Some GPIOs have special meaning at boot. Using them for sensors / LEDs without thinking can prevent the chip from booting.

GPIOBoot roleAudit ref
2Strapping — VDD_SPI voltage select. Pulled at boot.
8Strapping + onboard LED on the Super Mini (active LOW, blue). Conflicts with I²C SDA without a pull-up.H1
9Strapping + BOOT button. Pulled LOW at power-on enters ROM bootloader.H2

ESP32-C3 SuperMini — board-specific quirks

The exact dev-board this project uses. Reference page: randomnerdtutorials.com / Getting Started with ESP32-C3 Super Mini. Things that bite you on this specific board:

QuirkWhat & whyMitigation
Onboard LED is active LOW The blue LED on GPIO 8 is wired so that digitalWrite(8, LOW) turns it on, HIGH turns it off. Standard blink sketch looks inverted. Invert your logic, or move status indicator to another GPIO.
Antenna defect (some batches) Wi-Fi range is poor on certain production runs. Cause: the cap labelled C3 sits ~1.5 mm from the antenna trace instead of the design spec ~3.3 mm — detunes the matching network. Inspect under magnification before assembly. If it's a defective batch, the only real fix is switching boards.
Breadboard kills Wi-Fi Metal contacts under the antenna detune it. Range drops to a few metres. Raise the board 15 + mm above the breadboard with stacking headers, or mount it on the edge so the antenna overhangs.
Power LED drains the battery The red "power on" LED steals ~500 µA in deep sleep — about 10× the chip's own minimum. Desolder the LED (or its current-limit resistor) for battery-powered builds. Brings sleep current to the datasheet's ~43 µA.
USB CDC On Boot must be enabled The board has no external UART chip — only the chip's native USB-Serial/JTAG. Without the right Arduino IDE setting, Serial.print() goes nowhere. Arduino IDE → Tools → USB CDC On Boot → Enabled. PlatformIO sets this automatically for the esp32-c3-devkitm-1 board.
Deep-sleep upload trap If your firmware enters deep sleep within seconds of boot, esptool can't sync — the chip is asleep before the auto-reset finishes. BOOT/RESET trick (hold BOOT, tap RST, release BOOT) before retrying upload. Or build a delay-on-boot guard into firmware before the sleep call.
UART defaults: GPIO 20 (RX) / 21 (TX) If you use Serial1 or expose a hardware UART, these are the default pins on the Arduino-ESP32 core. Or remap with Serial1.begin(baud, mode, RX, TX) to any free GPIO.
// Specs at a glance RISC-V single-core @ 160 MHz · 4 MB flash · 400 KB SRAM · 384 KB ROM · 11 broken-out GPIOs · 2× 12-bit ADCs (6 channels) · 6× PWM channels · WiFi 2.4 GHz · BLE 5 · USB-C native · 3.3 V regulator (~500 mA) · accepts 3.3 V – 6 V on VIN.
🐛 4 — Crash forensics — addr2line in depth

When the ESP32-C3 panics, you get a "Guru Meditation" register dump in the serial monitor. Here's how to turn the hex into source-line info.

The dump structure

Guru Meditation Error: Core 0 panic'ed (Instruction access fault)
Core 0 register dump:
MEPC    : 0x3fc9027c   # where it died
RA      : 0x4038c53c   # return address (where it would have gone)
SP      : 0x3fc8f8d0   # stack pointer at crash
MCAUSE  : 0x00000001   # why it died
MTVAL   : 0x3fc9027c   # offending address (load/store cause)

MCAUSE codes

MCAUSENameMeans
0x01Instruction access faultTried to fetch code from non-executable memory. Classic stack-overflow signature (RA pointed into RAM).
0x02Illegal instructionCPU saw bytes that don't decode as RISC-V. Usually corruption — bad function pointer.
0x05Load access faultTried to read from invalid memory. Look at MTVAL.
0x07Store access faultTried to write to invalid memory. Buffer overrun on a global, or null-pointer write.
0x0BEnvironment callSoftware ecall — usually intentional in ISR.

Address ranges — where it points

RangeMeaningAction
0x4038xxxx0x403FIROM — flash codeDecode with addr2line.
0x4080xxxxIRAM — code in RAM (interrupt handlers)Decode with addr2line.
0x3FC0xxxx0x3FCFDRAM — data + stackIf MEPC points here = stack overflow / corrupted return address.
0x3C0xxxxxDROM — read-only data in flashIf MEPC points here = jumped into a string constant. Bad function pointer.

Running addr2line

# Linux / macOS / Git Bash:
~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-addr2line \
    -fipe .pio/build/esp32-c3-devkitm-1/firmware.elf \
    0x4038c53c 0x403801ee 0x4038907e

# Output:
ESP32RMTController::interruptHandler at .../FastLED/.../idf4_rmt_impl.cpp:827
vPortEnterCritical at .../freertos/port/riscv/port.c:246
xPortEnterCriticalTimeout at .../freertos/port/riscv/port.c:198
FlagWhat
-fshow function name
-ishow inlined-function chain
-ppretty-print (one line per address)
-e <path>ELF file path
-Cdemangle C++ symbols (try if functions look mangled)

Common crash signatures

// STACK OVERFLOW MEPC in DRAM (0x3FCxxxxx) + repeating stack frames at fixed offsets + decrementing counter. Exact pattern of BUG-001. Fix: reduce recursion depth, increase stack size, or fix the runaway loop.
// NULL-POINTER WRITE MCAUSE 0x07 + MTVAL 0x00000000 (or near). Usually a not-yet-initialized object's method called too early. Add a null check or move init earlier.
// HEAP CORRUPTION MCAUSE 0x07 + MTVAL pointing into a known heap region (0x3FC8xxxx) but the object should still exist. Use-after-free or buffer overrun. Try CONFIG_HEAP_POISONING_COMPREHENSIVE in sdkconfig.
// AUTO-DECODE Add monitor_filters = esp32_exception_decoder to platformio.ini and the serial monitor will auto-run addr2line on every crash. Saves the manual step.
📡 5 — Web Serial API — what powers the browser tools

The Web Serial API (W3C) lets a webpage talk directly to USB-serial devices — no plugins, no native install. flash.html and monitor.html use it.

Browser support

BrowserSupport
Chrome 89+✅ Yes
Edge 89+✅ Yes
Opera 76+✅ Yes
Firefox❌ No (philosophical objection — Mozilla considers Web Serial too dangerous)
Safari❌ No

Security model

  • HTTPS or localhost only. File:// URLs and plain HTTP rejected.
  • User gesture required. The port-picker dialog only opens after a real user click.
  • Per-origin permission. Once granted, the origin remembers the device for next time.
  • One owner at a time. Only one app can hold a serial port. Close PlatformIO monitor before connecting from the browser.

Minimal example

// 1 — request a port (must be in a click handler)
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });

// 2 — read a stream of bytes
const decoder = new TextDecoderStream();
port.readable.pipeTo(decoder.writable);
const reader = decoder.readable.getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(value);   // chunk of UTF-8 text
}

// 3 — write
const writer = port.writable.getWriter();
await writer.write(new TextEncoder().encode("hello\n"));
writer.releaseLock();

// 4 — close
await port.close();
// SEE ALSO Full Web Serial spec: wicg.github.io/serial · esp-web-tools (used by flash.html): github.com/esphome/esp-web-tools

Got a topic to add? Open an issue: github.com/abourdim/robot_1/issues · Other guides: user · build · instructor · start-here