Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 35 additions & 102 deletions .claude/settings.local.cleaned.json
Original file line number Diff line number Diff line change
@@ -1,107 +1,40 @@
{
"permissions": {
"allow": [
"Bash(./build/projectMM:*)",
"Bash(./build/test/mm_scenarios:*)",
"Bash(./build/test/mm_tests:*)",
"Bash(/usr/bin/curl -s --max-time 12 \"https://moonmodules.org/projectMM/install/boards.json\")",
"Bash(arp -a)",
"Bash(awk:*)",
"Bash(board)",
"Bash(cat:*)",
"Bash(cd /Users/ewoud/Developer/GitHub/MoonModules/projectMM *)",
"Bash(clang++:*)",
"Bash(clang:*)",
"Bash(cmake:*)",
"Bash(cp .claude/settings.local.cleaned.json .claude/settings.local.json)",
"Bash(cp .claude/settings.local.json .claude/settings.local.cleaned.json)",
"Bash(ctest:*)",
"Bash(curl -s*github.com/MoonModules*)",
"Bash(curl -s*raw.githubusercontent.com*)",
"Bash(curl:*192.168.1.*api*)",
"Bash(curl:*localhost*)",
"Bash(echo:*)",
"Bash(ffmpeg:*)",
"Bash(ffprobe:*)",
"Bash(gh api *)",
"Bash(gh auth *)",
"Bash(gh pr *)",
"Bash(gh release *)",
"Bash(gh run *)",
"Bash(gh search *)",
"Bash(gh workflow *)",
"Bash(git -C ~/esp/esp-idf describe)",
"Bash(git check-ignore *)",
"Bash(git fetch *)",
"Bash(git ls-remote *)",
"Bash(git mv:*)",
"Bash(git rm *)",
"Bash(grep:*)",
"Bash(gunzip:*)",
"Bash(gzip -c)",
"Bash(head:*)",
"Bash(ipconfig getsummary *)",
"Bash(ls:*)",
"Bash(lsof:*)",
"Bash(networksetup -listallhardwareports)",
"Bash(node --check:*)",
"Bash(node --input-type=module -e ' *)",
"Bash(node --test:*)",
"Bash(node -c docs/install/install-orchestrator.js)",
"Bash(node -c src/ui/install-picker.js)",
"Bash(node -e ' *)",
"Bash(npm install *)",
"Bash(npm test *)",
"Bash(objcopy:*)",
"Bash(objdump:*)",
"Bash(otool:*)",
"Bash(pkill:*)",
"Bash(ps:*)",
"Bash(python3 -)",
"Bash(python3 -c ' *)",
"Bash(python3 -c *)",
"Bash(python3 -m json.tool)",
"Bash(python3)",
"Bash(sed:*)",
"Bash(sort -k1)",
"Bash(sort -t: -k2 -rn)",
"Bash(system_profiler SPUSBDataType)",
"Bash(tail:*)",
"Bash(timeout 5 dns-sd -B _http._tcp local)",
"Bash(timeout 5 dns-sd -B _http._tcp local.)",
"Bash(uv run *)",
"Edit",
"Read(//Users/ewoud/**)",
"Read(//Users/ewoud/.cache/**)",
"Read(//Users/ewoud/.espressif/tools/esp-rom-elfs/**)",
"Read(//Users/ewoud/Developer/GitHub/MoonModules/**)",
"Read(//Users/ewoud/Library/Caches/ms-playwright/**)",
"Read(//Users/ewoud/esp/esp-idf/**)",
"Read(//Users/ewoud/esp/esp-idf/components/esp_driver_rmt/src/**)",
"Read(//Users/ewoud/esp/esp-idf/components/esp_lcd/i80/**)",
"Read(//dev/**)",
"WebFetch(domain:components.espressif.com)",
"WebFetch(domain:components101.com)",
"WebFetch(domain:developer.espressif.com)",
"WebFetch(domain:devices.esphome.io)",
"WebFetch(domain:docs.espressif.com)",
"WebFetch(domain:easyelecmodule.com)",
"WebFetch(domain:files.waveshare.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:kno.wled.ge)",
"WebFetch(domain:mm.kno.wled.ge)",
"WebFetch(domain:moonmodules.org)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:www.digikey.com)",
"WebFetch(domain:www.farnell.com)",
"WebFetch(domain:www.waveshare.com)",
"WebSearch",
"Write"
],
"additionalDirectories": [
"/tmp",
"/Users/ewoud/esp/esp-idf/components/esp_mm/include",
"/Users/ewoud/Developer/GitHub/MoonModules/projectMM/.claude"
"PowerShell(& \"C:\\\\Users\\\\ewoud\\\\.local\\\\bin\\\\uv.exe\" run moondeck/*)",
"PowerShell(& \"C:\\\\Users\\\\ewoud\\\\.local\\\\bin\\\\uv.exe\" run --with * python *)",
"PowerShell(& \"C:\\\\Users\\\\ewoud\\\\.local\\\\bin\\\\uv.exe\" run python *)",
"PowerShell(& \"C:\\\\Users\\\\ewoud\\\\.local\\\\bin\\\\uv.exe\" pip *)",
"PowerShell(& \"C:\\\\Program Files\\\\CMake\\\\bin\\\\cmake.exe\" *)",
"PowerShell($env:Path = \"C:\\\\Program Files\\\\CMake\\\\bin;\" + $env:Path; *)",
"PowerShell($env:Path = \"C:\\\\Program Files\\\\GitHub CLI;\" + $env:Path; *)",
"PowerShell(Get-PnpDevice*)",
"PowerShell(Get-Item *)",
"PowerShell(Get-ItemProperty *)",
"PowerShell(Get-ChildItem *)",
"PowerShell(Get-Command * -ErrorAction SilentlyContinue*)",
"PowerShell(Get-Content *)",
"PowerShell(Get-Process *)",
"PowerShell(Get-WmiObject *)",
"PowerShell(Test-Path *)",
"PowerShell(Write-Host *)",
"PowerShell(Start-Sleep *)",
"PowerShell(Start-Process *)",
"PowerShell(New-Item *)",
"PowerShell(Remove-Item *)",
"PowerShell(Rename-Item *)",
"PowerShell(& gh *)",
"PowerShell(& git *)",
"PowerShell(Invoke-RestMethod *)",
"PowerShell(winget install *)",
"PowerShell(winget --version)",
"PowerShell($venv * python.exe *)",
"Bash(grep *)",
"Bash(Test-Path *)",
"Bash(Start-Sleep *)",
"Bash(Write-Host *)",
"Read(//c/Users/ewoud/esp/esp-idf/tools/**)",
"WebFetch(domain:github.com)"
]
}
}
}
41 changes: 41 additions & 0 deletions docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,47 @@ The ESP32 tab in MoonDeck wraps the same steps as cards (Setup → Firmware →

![MoonDeck ESP32 tab](assets/ui/moondeck_esp32.png)

### Windows: USB-serial drivers

Windows ships no drivers for the two USB-serial chips almost every ESP32 dev board uses (WCH CH340/CH341, Silicon Labs CP2102/CP2102N). macOS and Linux do — so a board that Just Works on your Mac may show up on Windows as an `Unknown` device with no `COM*` port allocated at all, in which case both MoonDeck's port dropdown and the web installer's Chrome Web Serial picker come up **empty**. This isn't a projectMM bug; it's the OS.

**How to tell what you're dealing with:**

- **MoonDeck ESP32 tab** — click Refresh; no `COM*` in the dropdown after plugging in a board.
- **Chrome web installer** — the "Select a serial port" browser dialog says *No serial ports available*.
- **Device Manager** — the board shows under *Other devices* (yellow triangle) with the chip name (e.g. *CP2102N USB to UART Bridge Controller*) and status *Error*.
- **PowerShell** — `Get-PnpDevice | Where-Object InstanceId -match "VID_10C4|VID_1A86"` shows the device with `Status: Error` and empty `SERIALCOMM` registry (`Get-ItemProperty "HKLM:\HARDWARE\DEVICEMAP\SERIALCOMM"`).

**Which driver you need**, by the vendor ID (VID) in the InstanceId:

| VID | Chip | Common boards | Driver |
|---|---|---|---|
| `1A86` | WCH CH340 / CH341 | LOLIN D32, cheap NodeMCU-ESP32 clones, some Olimex | wch.cn official CH341SER (Windows Update usually pulls it — if not, try `pnputil /scan-devices` from an elevated PowerShell first). |
| `10C4` | Silicon Labs CP2102 / CP2102N | ESP32-S3 DevKitC, ESP32-S31 CoreBoard, many newer dev kits | Silicon Labs Universal driver — [zip download](https://www.silabs.com/documents/public/software/CP210x_Universal_Windows_Driver.zip). Windows Update rarely has this one, so grab it directly. |
| `0403` | FTDI FT2232 / FT230X | Some Olimex Gateways, older ESP32-WROVER-KIT | Windows Update usually installs FTDI VCP automatically. |

**Fastest headless install** (works for both CH340 and CP210x — Silicon Labs' zip contains an `.inf` `pnputil` can install directly):

```powershell
# 1) Download + extract the Silicon Labs Universal driver (CP210x).
$dst = "$env:TEMP\cp210x_driver"
Invoke-WebRequest "https://www.silabs.com/documents/public/software/CP210x_Universal_Windows_Driver.zip" `
-OutFile "$dst.zip"
Expand-Archive -Path "$dst.zip" -DestinationPath $dst -Force

# 2) Install (elevated — accept the UAC prompt).
Start-Process powershell -Verb RunAs -ArgumentList `
"pnputil /add-driver $dst\silabser.inf /install"
```

After the driver installs and Windows finishes binding (a few seconds), the boards appear as `Silicon Labs CP210x USB to UART Bridge (COM*)` with `Status: OK`. Both MoonDeck's port list (stateless registry read, no need to restart the server) and the web installer's Chrome picker populate immediately.

**Related Windows serial gotchas — same class of issue, same page:**

- **Stale "ghost" COM ports** from previous plug attempts (visible in Device Manager under *Show hidden devices*) reserve entries in the `ComDB` registry and can block fresh COM allocations even after replug. Clean them with `pnputil /remove-device "USB\VID_…"` (also elevated) — then replug.
- **Wedged CH340 driver from an earlier install** (`Status: Unknown` even though the driver is installed) — same pattern: `pnputil /delete-driver oem*.inf /uninstall /force` for the wch.cn driver, replug, let Windows Update supply a fresh one.
- **Cable / port** — some phone-charging cables carry only VBUS + GND (no data lines) and enumerate as briefly-then-vanish. Try a known-good cable and a rear USB-2 port before spending more time on drivers.

### ESP-IDF version

**Pinned to `v6.1-beta1`** (commit `b1d13e9f`, a signed pre-release tag). `setup_esp_idf.py` holds the exact commit in `PINNED_IDF_VERSION`, warns loudly when the installed tree differs, and by default offers to check the pin out so a stray `git pull` or a fresh shallow clone landing on a newer commit converges back rather than silently building against the wrong tree (`--no-checkout` keeps it warn-only). Minimum is ESP-IDF v5.1 (C++20 needs GCC 12+); the project uses v6.x APIs (`esp_eth_phy_new_generic`, the component manager for mDNS, the modern RMT/parlio/LCD drivers) so v5.x would need adjustments.
Expand Down
7 changes: 7 additions & 0 deletions docs/gettingstarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ which? Unplug, look at the list, plug back in — the new entry is your device.)

![Selecting the USB port](assets/gettingstarted/01-02-select-port.png)

**Windows users — dialog says "No serial ports available"?** Windows doesn't
ship drivers for the USB-serial chips most ESP32 boards use (WCH CH340, Silicon
Labs CP2102). One-time install fixes it for every future flash — full
step-by-step + the download link is in
[building.md § Windows: USB-serial drivers](building.md#windows-usb-serial-drivers).
macOS and Linux ship these drivers built in, so it's a Windows-only step.

Once a port is chosen, the installer recognises the chip and tells you how many
devices match it, so you know you're on the right track before you pick one.

Expand Down
9 changes: 9 additions & 0 deletions docs/history/lessons.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,12 @@ HA MQTT discovery's toggle-off publishes an empty retained config (so HA removes
## Raw-HTML `<img src>` is not validated by MkDocs `--strict`, so a wrong `../` depth ships a silent 404

The catalog/summary pages hand-author each card image as a raw `<img src="../../assets/...">`, and the build hook moves the tag into a table cell without touching its src. MkDocs `--strict` validates markdown `![]()` links but NOT raw-HTML `<img src>`, so 34 card images with a wrong `../` depth (the catalog pages sit one level shallower than the generated moxygen pages, so they need `../../assets`, not the moxygen pages' `../../../assets`) resolved to a nonexistent repo-root `/assets/` and 404'd on the live site while every gate passed green. The desktop build, ctest, and `--strict` all miss it; only loading the deployed page shows it. Fix + guard: a `test/python` check resolves every catalog `<img src>` on disk (`test_mkdocs_slug.py::test_catalog_card_images_resolve_on_disk`). General: a generated-site check that only validates the syntaxes its linter knows leaves the others as silent-404 territory, verify the actual rendered output, or gate the un-validated syntax explicitly.

## Lessons from this branch (windows-esp32-fixes)

Windows ESP32 bring-up end-to-end plus the beta1 pin-bump aftermath. Four gotchas each layered under a working build.

- **A "private" LL_ HAL symbol can be the de-facto stable interface, while its public SOC_ replacement is transient.** ESP-IDF's HAL docs mark `RMT_LL_TX_CANDIDATES_PER_INST` (`hal/rmt_ll.h`) as internal, but it's shipped consistently across every v5.x and v6.x snapshot this codebase has been built against — except one in-between v6.1-dev commit that briefly removed it in favour of `SOC_RMT_TX_CANDIDATES_PER_GROUP` (`soc/soc_caps.h`). Following the docs and switching to the SOC symbol broke on beta1, which restored `RMT_LL_*` and removed the SOC one. When a codebase already uses an "internal" HAL symbol proven stable across releases, upstream docs calling it internal aren't binding — the moving target may actually be the "public" alternative during a component-split refactor. Cross-check what Espressif *ships*, not what they *document*. The net-zero-code round-trip on `platform_config.h` in this branch is the receipt.
- **Two `build_esp32.py` invocations against the same project race on `esp32/managed_components/`.** IDF's component manager writes to a project-scope directory, not per-build-dir. Kicking off the S31 build while S3-n16r8 was still resolving components produced a partial `managed_components/` where one component's `CMakeLists.txt` pointed at source files that didn't land. Symptom: `CMake Error: Cannot find source file` mid-configure. Serialize IDF builds — either wait for one to finish before the next, or introduce a lock at wrapper level. `moondeck/build/build_esp32.py` has no queue today; serialisation is the caller's responsibility.
- **A failed `set-target` leaves the build dir with a fallback `esp32` sdkconfig that the wrapper's fast path trusts silently.** `build_esp32.py`'s "dir exists → skip set-target" fast path assumes the existing dir is coherent for the requested firmware. It isn't when a previous `set-target esp32p4` bailed early (e.g. the RISC-V toolchain wasn't in PATH) after IDF wrote a partial sdkconfig defaulting to esp32. The next invocation skips set-target and builds a classic esp32 binary in a `build/esp32-esp32p4-eth/` directory. Symptom: an esp32p4-labelled artifact esptool refuses to flash to a P4 ("chip is ESP32-P4, not ESP32"). Fix in this branch: `stale_feature_cache()` gains a `chip` param and reads `IDF_TARGET:STRING=` from `CMakeCache.txt` before the existing feature-flag checks; a mismatch (or missing target line) forces the wipe-and-reconfigure path. Generalisation: any fast path that skips work because "the previous run left state" needs to validate that state is coherent, not just present.
- **Python scripts emitting non-ASCII crash on Windows when stdout is piped.** `sys.stdout` on Windows defaults to cp1252, which has no `⚠`, `→`, box-drawing, or emoji. Direct-to-terminal often works because the Windows terminal negotiates UTF-8 on modern versions, but piping (`Tee-Object`, redirection, CI log capture) uses the raw stdio encoding and raises `UnicodeEncodeError` mid-message. `sys.stdout.reconfigure(encoding="utf-8", errors="replace")` at script entry is a one-line fix, no-op on POSIX. Every projectMM Python script that may emit non-ASCII AND be piped needs the reconfigure — worth generalising into a shared helper when the next script hits the same trap.
Loading
Loading