Commit Graph

86 Commits

Author SHA1 Message Date
Claude Worker claude2 898808c396 docs: top README points to idf_zacus + box3 2026-06-11 02:58:10 +02:00
Claude Worker claude2 a7463464c2 docs: correct sensor comment to OV3660 2026-06-11 02:57:05 +02:00
Claude Worker claude2 080581f39f Merge qr fixes: adaptive geometry + mirror + QVGA 2026-06-11 02:56:47 +02:00
Claude Worker claude2 6d7b670256 docs: refresh firmware READMEs (puzzles+UI+REST) 2026-06-11 02:56:47 +02:00
Claude Worker claude2 fac94d5885 fix(qr): QVGA viewfinder, mirror, sensor tuning 2026-06-11 02:43:51 +02:00
Claude Worker claude2 ed336967a1 feat(box3): QR size + dim screen for camera 2026-06-11 02:43:51 +02:00
Claude Worker claude2 132b88337b fix(qr): adaptive frame size + OV3660 tuning 2026-06-11 02:03:13 +02:00
Claude Worker claude2 859c975654 feat(box3): larger QR for camera decode 2026-06-11 02:03:13 +02:00
Claude Worker claude2 087dbd3c03 feat(box3): QR + melody stimulus generator 2026-06-10 23:15:33 +02:00
Claude Worker claude2 e8125a20e5 Merge feat/freenove-local-puzzles: local puzzles + scenario hook + UI port 2026-06-10 23:06:04 +02:00
Claude Worker claude2 0e4cea9c39 feat(game): POST /game/file provisions apps 2026-06-10 22:46:05 +02:00
Claude Worker claude2 fd1a517989 feat(ui): cracktro intro + file browser + apps 2026-06-10 22:38:44 +02:00
Claude Worker claude2 563dfea368 feat(ui): Workbench shell + boot zoom intro 2026-06-10 22:20:28 +02:00
Claude Worker claude2 a552e1976e feat(ui): 5-way buttons (view + brightness) 2026-06-10 22:09:47 +02:00
Claude Worker claude2 d0f217f5d9 feat(ui): scene IR metadata + fx glitch/gyro 2026-06-10 22:01:17 +02:00
Claude Worker claude2 29c0eecfb9 fix(game): avoid littlefs double-register noise 2026-06-10 21:45:13 +02:00
Claude Worker claude2 ff2ab2abb5 feat(ui): live QR viewfinder in scene view 2026-06-10 21:40:58 +02:00
Claude Worker claude2 2de7b20cd8 feat(ui): scene view + original fonts, 3MB apps 2026-06-10 21:28:31 +02:00
Claude Worker claude2 21b06665e8 feat(ui): LVGL layer + PWM backlight 2026-06-10 21:20:45 +02:00
Claude Worker claude2 9ac287c843 feat(ui): ST7796 status screen via LovyanGFX 2026-06-10 21:06:16 +02:00
Claude Worker claude2 0aa9271038 feat(game): step arming + puzzle state REST 2026-06-10 19:44:10 +02:00
Claude Worker claude2 b66932c800 feat(game): parse step puzzle binding from IR 2026-06-10 19:33:53 +02:00
Claude Worker claude2 e216490bf3 feat(local): arm API carries id + fragment 2026-06-10 19:23:24 +02:00
Claude Worker claude2 8eab308185 feat(local): wire local puzzles into master 2026-06-10 16:13:19 +02:00
Claude Worker claude2 d715d45b34 feat(local): sound puzzle via mic broker 2026-06-10 15:53:27 +02:00
Claude Worker claude2 b6a9d62d9a feat(local): QR puzzle via camera + quirc 2026-06-10 15:42:48 +02:00
Claude Worker claude2 0f0d8405f7 feat(local): mic broker; voice_pipeline uses it 2026-06-10 15:29:10 +02:00
Claude Worker claude2 275197139c feat(master): puzzle state aggregation logic 2026-06-10 14:33:33 +02:00
Claude Worker claude2 96f3a9ad65 feat(local): melody validator + tests 2026-06-10 14:24:15 +02:00
Claude Worker claude2 31c739fd82 feat(local): QR sequence validator + tests
Ordered-scan validator for QR puzzle sequences with host-based
Unity test harness. Tests cover: correct order acceptance, reset on
wrong scan, and duplicate-of-last handling.
2026-06-10 14:17:58 +02:00
Claude Worker claude2 d9ba124d16 docs(local): freeze Media Kit v1.2 pin map 2026-06-10 13:56:59 +02:00
Claude Worker claude2 10af7b28b2 chore: ignore .worktrees/ 2026-06-10 13:49:10 +02:00
Claude Worker claude2 87046eadf0 fix(box3): BOX-3 BSP + backlight
CI / platformio (pull_request) Failing after 9m37s
The project depended on espressif/esp-box (first-gen BOX: ST7789 +
TT21100 touch), which mis-initialises a real ESP32-S3-BOX-3 (ILI9341 +
GT911) and leaves the screen black. Switch the dependency to
espressif/esp-box-3 and use the generic bsp/esp-bsp.h header.

Also: the voice app started the display but never lit the backlight
("LCD backlight must be enabled separately" per the BSP). plip_ui_init
now calls bsp_display_brightness_init()/set(100) and uses a dark-blue
background with white text so the UI is actually visible.

Resolves clean (esp-box-3 3.2.0, esp_lcd_ili9341 2.0.2,
esp_lcd_touch_gt911 1.2.0); build green.
2026-06-10 13:20:59 +02:00
Claude Worker claude2 b812e059da feat(box3): on-screen phone with touch keypad
CI / platformio (pull_request) Failing after 4m27s
plip_ui.{h,c}: LVGL phone on the BOX-3 LCD driven by the plip_virtual
hook state. Idle/ringing shows a phone glyph + status ("Appel
entrant…"); off-hook shows a 3x4 dial keypad (1-9,*,0,#) with a live
number field. A fixed bottom touch button toggles the hook —
"Décrocher" (green) while ringing, "Raccrocher" (red) off-hook,
disabled when idle. Dialed digits are reported to the master
(reason "dial:<n>") and surfaced in GET /status.

plip_virtual gains the public pickup/hangup/dial API, a state
callback for the UI, and a state getter; enables Montserrat 24/48.

Build green; flashed and verified on a real ESP32-S3-BOX-3.
2026-06-10 12:43:20 +02:00
Claude Worker claude2 5c97013f8c feat(box3): virtual PLIP annex (phone-less)
CI / platformio (pull_request) Failing after 14m53s
New main/plip_virtual.{h,c}: implements the PLIP REST contract on the
existing port-80 httpd (POST /ring {duration_ms} with French cadence
1.5s/3.5s 440 Hz on the speaker, POST /stop, GET /status, POST /play
501 — audio flows through the WS TTS path). The BOOT button becomes
the virtual hook switch when ringing/off-hook (falls back to the
streaming toggle otherwise) and every transition is reported to the
master POST <ZACUS_MASTER_URL>/voice/hook {state,reason}, the exact
zacus_hook_client contract — the master cannot tell the annexes apart.
New Kconfig ZACUS_MASTER_URL (default 10.2.5.42). scenario_server
exposes its httpd handle for co-registration.

Build green (zacus-box3-voice.bin).
2026-06-10 12:28:36 +02:00
Claude Worker claude2 185d1186fe feat(master): GET/POST /game/peers endpoint
CI / platformio (pull_request) Failing after 14m56s
HTTP management of the ESP-NOW relay peer registry — replaces the
desktop NvsConfigurator round-trip. POST {alias, mac} persists to the
NVS "peers" namespace (the exact format main.c seeds at boot) and
registers the peer live in the scenario_mesh table (no reboot). GET
lists the registry. Provisioning PLIP becomes:
  curl -X POST master/game/peers -d '{"alias":"plip","mac":"..."}'

Build green (zacus_master.bin).
2026-06-10 11:55:23 +02:00
Claude Worker claude2 7fe0bd2af6 chore: drop sdkconfig.old from tracking
CI / platformio (pull_request) Failing after 4m11s
idf.py set-target regenerates these backups on every target switch;
they slipped into the previous commit. Ignore them like sdkconfig.
2026-06-10 10:51:31 +02:00
Claude Worker claude2 13bbb56244 refactor: single scenario_mesh in shared lib/
The component was vendored byte-identical in idf_zacus/components/ and
box3_voice/components/ (documented drift risk). Hoist the single copy
to lib/scenario_mesh, referenced from both projects via
EXTRA_COMPONENT_DIRS in their root CMakeLists. The two deliberate
frame-format reimplementations (puzzle demux in espnow_common, PLIP's
Arduino scenario_now) are now called out in the receiver-patch doc.

All 3 firmwares rebuilt green (p7_coffre, idf_zacus, box3_voice).
2026-06-10 10:50:15 +02:00
Claude Worker claude2 7be1961122 docs: PLIP re-scoped into scenario hot-load
CI / platformio (pull_request) Failing after 5m2s
PLIP_FIRMWARE now has its own ESP-NOW receiver
(src/scenario_now.{h,cpp}); update the receiver-patch status and the
PLIP section accordingly. HTTP stays the recommended push path once
PLIP's REST server lands.
2026-06-10 10:24:12 +02:00
Claude Worker claude2 b2267f2261 feat(scenario-mesh): ESP-NOW scenario hot-load receivers + green builds
CI / platformio (pull_request) Failing after 6m18s
- scenario_mesh: ESP-NOW frame protocol component (master + box3_voice):
  chunking, per-source reassembly, deferred apply off the Wi-Fi callback.
- game_endpoint: POST /game/scenario/relay (master) + shared scenario_apply.
- box3_voice: scenario receiver wiring (scenario_mesh_init).
- espnow_slave (shared by all puzzles): demux scenario frames inside the single
  recv callback so a misrouted relay can't corrupt the MSG_* stream; reassemble
  per source MAC; optional consumer hook (logs+drops by default, puzzles have no
  scenario engine). Add missing esp_mac.h include for MACSTR/MAC2STR.
- CMakeLists (scenario_mesh + 4 puzzle mains): drop invalid `esp_now` REQUIRES;
  the ESP-NOW API lives in esp_wifi. Fixes "Failed to resolve component esp_now".
- docs: scenario-mesh receiver patch note (puzzles done via defensive demux;
  PLIP out of scope — Wi-Fi/HTTP-only, no ESP-NOW stack).

Builds green under ESP-IDF 5.4.4: idf_zacus, box3_voice, p7_coffre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 04:11:28 +02:00
L'électron rare d220d94607 feat(box3): scenario hot-load HTTP server
Mirrors the master's hot-load endpoint on the BOX-3 with a
self-contained httpd (no shared component) since box3_voice is
a separate IDF project. Storage uses the existing SPIFFS
partition; behaviour matches the master (validate, .bak rotate,
write, deferred restart).

main.c hooks scenario_server_start() after wifi_init_sta(); the
http server binds once the STA gets an IP.

Move idf_component.yml to main/ so the IDF Component Manager
actually fetches the managed dependencies. Pin button to 2.5.x
and esp_codec_dev to 1.2.0 to stay compatible with esp-box 1.x
legacy i2c. Set CONFIG_I2C_ENABLE_LEGACY_DRIVERS=y for the same
reason; a residual upstream BSP conflict still aborts boot on
hardware (esp-box 1.x vs driver_ng) - tracked separately.

sdkconfig.defaults documents that WiFi credentials must be set
via menuconfig and never committed.
2026-05-24 10:47:08 +02:00
L'électron rare 2a596b9fcb feat(idf): POST /game/scenario hot-load
game_endpoint gains a /game/scenario handler that accepts a Runtime 3
IR JSON (<=64 KiB), validates schema_version + non-empty steps array,
rotates the existing scenario to .bak, writes to LittleFS at
/littlefs/scenario.json on partition 'storage', and reboots after
the HTTP response flushes so the engine picks up the new IR.

ota_server bumps max_uri_handlers 8 -> 16 to fit the extra handlers
(/game/scenario plus headroom).

sdkconfig.qemu disables PSRAM + esp-sr so the firmware boots inside
qemu-system-xtensa without crashing on the unemulated octal PSRAM.
QEMU_HOWTO.md documents the workflow and the WiFi/Ethernet gap that
still blocks full HTTP smoke under emulation.
2026-05-24 10:46:29 +02:00
L'électron rare a743cfc87f feat(idf): voice_dispatcher fuzzy aliases 2026-05-04 00:55:23 +02:00
L'électron rare 71bc9a5e37 feat(idf): mDNS + /game/group_profile endpoint 2026-05-04 00:33:49 +02:00
L'électron rare ec6f363606 feat(idf): hints lifecycle + group_profile NVS
hints_client gains puzzle_start, attempt_failed, set_group_profile (whitelist TECH/NON_TECH/MIXED/BOTH default MIXED). npc_engine.set_step now auto-fires hints_client_puzzle_start; new helpers current_puzzle_id and report_failed_attempt. voice_dispatcher detects failure keywords (non/faux/mauvais/rate/marche pas) and bumps the failed-attempt counter before the hint fast-path. main.c reads NVS zacus/group_profile at boot. Build green: 1.40 MB / 2 MB partition (30% free).
2026-05-04 00:25:18 +02:00
L'électron rare de22adc08c chore(idf): app partition 1.5MB -> 2MB (OTA dual)
Bump factory + ota_0 + ota_1 from 0x180000 to 0x200000 (2 MB each)
to give voice_pipeline / STT / hints_client / esp-sr slices ~25% free
margin (was 7%). OTA dual-bank rollback symmetry preserved (all three
app slots same size). Storage (LittleFS) grows from 2 MB to 5 MB to
host MP3 cue pool; model (esp-sr SPIFFS) stays at 1 MB. Layout uses
12.1 MB of the 16 MB flash, leaves 3.87 MB unused for future growth.

idf.py build reports zacus_master.bin = 0x1639a0 (1.39 MB), 31% free
in 0x200000 partition. Reordered model before storage in CSV so auto
offsets cascade as planned (model at 0x620000, storage at 0x720000).

No sdkconfig change. Migration only requires erasing flash on first
deploy because partition table changed.
2026-05-04 00:20:25 +02:00
L'électron rare d47c23068a feat(idf): voice_pipeline_ws speak_* parsing
Slice 9b: parse {type:speak_start,sample_rate,format} and {type:speak_end,duration_ms,backend,latency_ms} from voice-bridge, route binary frames to voice_pipeline_play_chunk during the speak window. State machine transitions LISTENING -> SPEAKING -> IDLE drive the mute gate added in slice 9. End-to-end TTS playback now works: ESP32 wake -> stream -> /voice/ws -> whisper -> intent -> F5-TTS PCM stream -> I2S out.
2026-05-04 00:16:36 +02:00
L'électron rare 3585a65c8f feat(idf): PLIP /voice/hook endpoint + TTS enable
Context:
The PLIP retro-telephone annex (Si3210 SLIC + RJ9 combine) needs a
way to drive the master ESP32 voice pipeline directly from the hook
switch, so the escape-room narrative "le telephone sonne, decroche
pour parler a Zacus" works without relying on the wake-word.

Approach:
- Expose the existing ota_server httpd_handle_t via a new getter so a
  second component can register routes on the same listener (port 80)
  instead of standing up a parallel httpd.
- New voice_hook_endpoint component attaches POST /voice/hook +
  GET /voice/hook/state to that shared handle. Off-hook arms the
  pipeline (LISTENING + capture + WS streaming, bypassing wake-word);
  on-hook tears it down (stop streaming + stop capture + IDLE).
- Enable enable_tts_playback so TTS frames received over the bridge
  WS land on the MAX98357A DAC. Wake-word stays enabled (mixed mode:
  hook is primary, "hi esp" is fallback).

Changes:
- ota_server: add ota_server_get_handle() returning the static
  httpd_handle_t for late URI handler registration.
- voice_hook_endpoint: new component (CMakeLists, header, .c, README).
  POST handler validates body (<=256 B), parses cJSON, dispatches
  state on/off (idempotent), returns JSON status. GET state handler
  exposes voice_state + wake_word_active + streaming for debugging.
  Explicit GET on /voice/hook returns 405 with Allow: POST so PLIP
  integrators see an actionable error instead of a 404.
- main.c: include header, init voice_hook_endpoint after ota_server
  succeeds, flip voice_cfg.enable_tts_playback = true.
- main/CMakeLists.txt: add voice_hook_endpoint to REQUIRES.
- README.md: document the wire protocol PLIP must call (state values,
  response codes, curl examples, mixed-mode behaviour).

Impact:
- PLIP firmware can now drive the Zacus voice loop over Wi-Fi via
  REST without any code change on the master side beyond this slice.
- TTS replies from the MacStudio voice-bridge are audible on the
  master speaker (slice 9 shipped the API; slice 10 turns it on).
- App size 0x1639a0 (1.39 MB) in 0x180000 partition — 7% headroom,
  no partitions.csv resize needed.
2026-05-04 00:13:54 +02:00
L'électron rare 6d3e989ca3 feat(idf): TTS playback I2S TX + mute gate
voice_pipeline slice 9: I2S_NUM_1 TX channel for TTS playback, voice_pipeline_play_start/chunk/end APIs (clock reconfig 16k mic vs 24k F5), mute-during-TTS gate in capture_task (state==SPEAKING skips AFE feed to prevent echo retriggering wake). enable_tts_playback opt-in default false. Build green: 1.45 MB app, 8% free.
2026-05-04 00:05:02 +02:00
L'électron rare c3a771ca8b feat(idf): voice_dispatcher (STT->hints fast-path) 2026-05-03 22:07:03 +02:00