Commit Graph

157 Commits

Author SHA1 Message Date
clement 82759ee536 feat(plip): voice-activated two-phase capture
CI / platformio (push) Failing after 9m50s
CI / platformio (pull_request) Failing after 10m14s
Listen-loop capture now only commits when the caller actually speaks:
phase A waits for a sustained voice onset (3 frames above threshold,
rejecting the PA-mute click), phase B records until silence. Empty
captures are no longer posted, so the NPC never replies to silence.
Also: DC-blocking high-pass on the captured mono, VAD thresholds tuned
to the quiet SLIC handset mic (onset ~1.4%, silence ~0.6%), keep the
PA on during capture (muting it collapsed the mic), and add a
/debug/miccap diagnostic endpoint (raw fixed-duration mic capture).
2026-06-17 14:39:42 +02:00
clement 8c076d81d6 fix(plip): trust real SHK, drop hook forcing
CI / platformio (pull_request) Failing after 7m44s
CI / platformio (push) Failing after 11m19s
Incoming-call greeting was silently dropped: audio CMD_PLAY gated on
phone.c's debounced phone_is_offhook(), which lags/misses the pickup
while the bell rings, but the incoming flow established off-hook only
via the raw SLIC poll in conversation.c. The two flags desynced and
playback was skipped as 'on-hook' though the handset was up.

Gate playback on the real SHK line (slic_is_offhook) instead — the
single source of truth. Remove all hook forcing (s_hook_override,
phone_force_offhook, /debug/offhook): the SHK contact is reliable in
hardware, so the firmware must trust it, never override it. Builds
clean (ESP-IDF v5.4.4).
2026-06-17 09:01:48 +02:00
clement 99d79efcdd tune(plip): +24dB mic PGA + VAD thresholds
CI / platformio (push) Failing after 11m27s
2026-06-16 19:18:52 +02:00
clement 8dffe14972 fix(plip): mute earpiece during capture 2026-06-16 19:18:45 +02:00
clement d71453d32e fix(plip): report real state in /status 2026-06-16 19:18:38 +02:00
clement ccc84da785 tune(plip): default speaker volume 98%
CI / platformio (push) Failing after 15m51s
2026-06-16 10:32:11 +02:00
clement 1561e613e1 feat(plip): incoming call + fast half-duplex
- /debug/ring?number=NN arms incoming call, starts ring
- conversation_arm_incoming() + enter_incoming_greet()
- offhook with armed call skips dial/ringback, goes to GREET
- slic_is_offhook() used during ring (GPIO debounce unreliable)
- CAPTURE_SILENCE_MS 800->600 ms, settles 250->120, 200->100 ms
2026-06-16 10:32:01 +02:00
clement 4a6fc435a7 fix(plip): ES8388 undoc DLL regs for 16kHz ADC
CI / platformio (push) Failing after 13m6s
2026-06-16 09:32:36 +02:00
clement ebcfb011d3 feat(plip): /debug/es8388 register poke endpoint
CI / platformio (push) Failing after 11m53s
Add es8388_write_reg() public API and GET /debug/es8388?reg=&val=
HTTP handler to read/write codec registers at runtime without
reflashing. Bumps max_uri_handlers 16→17.
2026-06-16 08:40:59 +02:00
clement 3c0eb75465 fix(plip): robust WiFi on mesh/DFS network
STA config: channel=0 (all-channel scan), WIFI_ALL_CHANNEL_SCAN,
WIFI_CONNECT_AP_BY_SIGNAL, failure_retry_cnt=5.
Disconnect handler: retry on every disconnect event, including during
initial association — previously the first failure at boot caused an
immediate abort and a ~30s timeout before IP was acquired.
Validated on hardware: connects reliably on ch1, RSSI -32, IP in ~2.5s.
2026-06-15 22:27:01 +02:00
clement aa7ae277ed feat(plip): voice loop + DTMF + ring cadence
CI / platformio (pull_request) Failing after 4m0s
CI / platformio (push) Failing after 14m58s
- hook polarity active-HIGH + auto-resync (was LOW)
- ring cadence FT 1.5s ON / 3.5s OFF
- DTMF Goertzel decoder (dtmf.c/h) + rotary debounce
- LISTEN half-duplex: capture → /v1/voice/reply → play
- WAV playback buffered PSRAM + mono→stereo upmix
- SPIFFS mount at boot for pre-loaded greetings
- ES8388: DAC digital vol + mic PGA + GPIO INPUT_OUTPUT
- turn_client multipart + 90s timeout + fixed routing
- debug endpoints: vol/dacvol/offhook/getfile/hookmon
2026-06-15 21:12:33 +02:00
electron 37db47ad7b Merge pull request 'fix(plip): ES8388 ADC input LIN2/RIN2 — SLIC handset mic works' (#24) from fix/plip-adc-lin2 into main
CI / platformio (push) Failing after 13m12s
2026-06-15 07:51:27 +00:00
clemsail f03156b65e fix(plip): ES8388 ADC input LIN2/RIN2 — SLIC handset mic (voice captured, was reading floating LIN1)
CI / platformio (pull_request) Failing after 3m54s
2026-06-15 09:51:15 +02:00
electron b36b703f34 Merge pull request 'fix(plip): SLIC hook polarity active-LOW + faithful PD open-drain' (#23) from feat/plip-hook-polarity into main
CI / platformio (push) Has been cancelled
2026-06-15 07:43:54 +00:00
clemsail a209b3b86c fix(plip): SLIC hook polarity active-LOW (K50835F open-collector) + faithful PD open-drain
CI / platformio (pull_request) Failing after 11m29s
2026-06-15 09:43:44 +02:00
electron 8b0bd2c262 Merge pull request 'feat(plip): SLIC integration + I2S RX mic fix + /debug/ring,slic' (#22) from feat/plip-slic-integration into main
CI / platformio (push) Failing after 5m56s
2026-06-14 23:01:39 +00:00
clemsail 54d1a1ec6e fix(plip): I2S full-duplex silently broken by mismatched TX/RX gpio_cfg — ES8388 ADC/I2S RX capture path
CI / platformio (pull_request) Failing after 4m10s
IDF5 i2s_channel_init_std_mode() constitutes full-duplex ONLY when TX and
RX std_cfg are byte-for-byte identical (memcmp). When din/dout differ
between the two calls, the driver silently moves RX to I2S_NUM_1 which has
no BCLK/WS routing, producing permanent zeros on i2s_channel_read().

Fix: use the same i2s_std_config_t for both TX and RX init calls, with
dout=GPIO26 and din=GPIO35 both set. IDF handles GPIO direction internally.

Also clean up ES8388 register sequence:
- ADCCONTROL2 = 0x00 (LIN1/RIN1 differential, LINE IN header)
- ADCCONTROL6 = 0x00 (clear ADCSMUTE, reset default was 0x30)
- ADCPOWER = 0x00 (full ADC power-up, was 0x09)
- DACCONTROL21 = 0x80 (DAC+ADC normal mode, not line bypass 0xC0)

Verified: peak=2593, rms=2482 over 48000 samples (3s @ 16kHz).
2026-06-15 00:57:02 +02:00
clemsail ccbe5720b0 fix(plip): SLIC PD push-pull HIGH (definite power-up) + /debug/slic gpio readback 2026-06-15 00:49:30 +02:00
clemsail 47fbc09fea fix(plip): ES8388 ADC unmute (ADCCONTROL6=0x00) + full ADC power-up (ADCPOWER=0x00)
ADCCONTROL6 (reg 0x0E) reset default = 0x30 which has ADCSMUTE=1 (bit5) — ADC output
muted by default. Writing 0x00 unmutes. Without this, ADCINSEL=0x50 (LIN2) is selected
but the signal is suppressed at the ADC output stage → peak=0.

ADCPOWER (reg 0x03) changed from 0x09 (intermediate Espressif open state) to 0x00 (full
power-up: all power-down bits cleared). Value 0x09 = bits 0+3 set (ADCPD_L + MICBIAS_PD)
— MICBIAS_PD in particular means the internal microphone bias is powered off, which can
starve the SLIC line-in path. 0x00 is the correct end state for recording mode per
Espressif esp8388_start(ES_MODULE_ADC) reference.

sdkconfig.defaults: add CONFIG_PLIP_HOOK_GPIO=23 / CONFIG_PLIP_HOOK_ACTIVE_HIGH=y
as explicit defaults so clean builds use the SLIC SHK pin without menuconfig.
2026-06-15 00:36:43 +02:00
clemsail 0f7047215b feat(plip): ES8388 ADC line-in (LINPUT2/RINPUT2) for SLIC audio
ADCCONTROL2 (reg 0x0A): was 0x00 (LINSEL=00 LINPUT1/RINPUT1 — onboard PCB mic),
now 0x50 (LINSEL=01 LINPUT2 / RINSEL=01 RINPUT2 — telephone handset mic via SLIC).

The SLIC K50835F routes the handset mic signal to the ES8388 LINPUT2/RINPUT2 pins.
Writing 0x00 meant the ADC was listening to the empty onboard mic, producing 0/0 RMS.
Writing 0x50 connects the SLIC audio path, enabling voice capture from the handset.

PGA stays at +24 dB (ADCCONTROL1 = 0xBB). ADC power-up sequence unchanged.
2026-06-15 00:26:49 +02:00
clemsail f531bf3e55 feat(plip): SLIC control — power-up (PD OD-HIGH), hook on SHK GPIO23, ring RM/FR
- board_config.h: add PLIP_SLIC_RM=18, FR=5, SHK=23, PD=19 (A1S KEY3-6 repurposed)
- slic.c/slic.h: new ESP-IDF module porting Ks0835SlicController:
    * slic_init(): RM/FR output LOW, SHK input+pullup, PD open-drain HIGH (power-up)
    * slic_is_offhook(): reads SHK GPIO23, HIGH = off-hook (active-high, matches A252ConfigStore default)
    * slic_ring_start/stop(): RM HIGH + FreeRTOS task toggles FR at 25 Hz (20 ms period)
- CMakeLists.txt: add slic.c to SRCS, esp_driver_gpio to PRIV_REQUIRES
- Kconfig: PLIP_HOOK_GPIO default 4→23, add PLIP_HOOK_ACTIVE_HIGH (default y)
- phone.c: hook reads SHK GPIO23 via HOOK_OFFHOOK_LEVEL/HOOK_PULSE_OPEN macros (active-HIGH);
    phone_ring_start/stop() now drives slic_ring_start/stop() for physical bell + audio tone
- main.c: slic_init() called early in boot_task before audio_init

Root cause fixed: SLIC was never powered (PD never released from reset state).
Hook was read on wrong GPIO (4) with wrong polarity. Ring drove only audio, not bell.
2026-06-15 00:26:35 +02:00
clemsail 87075ba92b feat(plip): /debug/ring + /debug/ringstop — trigger ringer over HTTP 2026-06-14 23:24:36 +02:00
electron 75d6aaf6d4 Merge pull request 'feat(plip): téléphone stage 2 — turn_client + GREET (accueil PNJ depuis gateway)' (#21) from feat/plip-phone-stage2 into main
CI / platformio (push) Failing after 3m51s
2026-06-14 20:45:00 +00:00
clemsail c740bfe20a feat(plip): turn_client + GREET state — fetch & play NPC greeting from gateway /v1/voice/turn (stage 2)
CI / platformio (pull_request) Failing after 11m23s
2026-06-14 22:43:46 +02:00
electron e68e9f5f66 Merge pull request 'feat(plip): téléphone France Télécom — stage 1 (tonalités + dialer + machine à états)' (#20) from feat/plip-phone-stage1 into main
CI / platformio (push) Failing after 4m23s
2026-06-14 20:17:25 +00:00
clemsail 2710965741 fix(plip): synchronous tones_stop (I2S arbitration), MAX_DIGITS 12, drop s_state cross-task write, pulse guard
CI / platformio (pull_request) Failing after 4m21s
2026-06-14 22:15:53 +02:00
clemsail b92f138f41 feat(plip): conversation state machine — dialtone/dialing/routing/ringback/busy (stage 1) 2026-06-14 22:01:33 +02:00
clemsail 4621f451be feat(plip): rotary pulse dialing on SHK GPIO 2026-06-14 21:57:14 +02:00
clemsail e819eae5a5 feat(plip): dialer module + /debug/dial (digit accumulator) 2026-06-14 21:55:02 +02:00
clemsail ba248e92fe feat(plip): tones module — France Télécom dial/busy/ringback 2026-06-14 21:53:50 +02:00
electron 39f4cd2313 Merge pull request 'feat(plip): mute audio output when handset on-hook' (#19) from feat/plip-mute-onhook into main
CI / platformio (push) Failing after 3m57s
2026-06-14 18:45:03 +00:00
clemsail b8ba1457fb feat(plip): mute audio output when handset on-hook (gate playback on hook state)
CI / platformio (pull_request) Failing after 3m41s
2026-06-14 20:43:33 +02:00
electron 7718f4653d Merge pull request 'feat(plip): streaming file upload + playback (MAX_BODY 256K, full TTS phrases)' (#18) from feat/plip-maxbody-256k into main
CI / platformio (push) Failing after 11m35s
2026-06-14 18:30:47 +00:00
clemsail f7104b4180 feat(plip): bump /game/file MAX_BODY 64K->256K for full TTS phrases
CI / platformio (pull_request) Failing after 3m46s
2026-06-14 20:29:25 +02:00
electron b748c2d75f Merge pull request 'feat(plip): I2S RX mic capture + POST /voice/capture (STT input leg) + ES8388 ADC register fix' (#17) from feat/plip-mic-capture into main
CI / platformio (push) Failing after 4m20s
2026-06-14 16:53:01 +00:00
clemsail dd3d239784 feat(plip): I2S RX mic capture + POST /voice/capture (WAV) — STT input leg
CI / platformio (pull_request) Failing after 4m5s
- audio.c: add full-duplex I2S RX channel (s_mic_handle, GPIO35/ASDOUT)
  enabled at boot alongside TX; streaming capture API with VAD
  (audio_capture_begin/read_frame/end) avoids large buffer allocation
- audio.h: export streaming capture API
- es8388.c: fix ADC register addresses (ADCCONTROL2=0x0A, 3=0x0B, 4=0x0C,
  5=0x0D, 8=0x10, 9=0x11 — were off by 1 from Espressif reference);
  CHIP_CTL1=0x12 (record+play), DACCONTROL21=0xC0 (ADC+DAC LRCK sync),
  ADCPOWER=0x09, FSM restart pulse (CHIPPOWER 0xF0→0x00) after CTL21 write;
  fix set_volume() to use OUT1 volume regs (0x2E/0x2F) not clock-sync reg 0x2B
- net.c: POST /voice/capture streaming WAV handler (chunked, 44-byte header
  + PCM frames, VAD onset/silence, query params max_ms/silence_ms);
  GET /debug/regs for live ES8388 register dump (diagnostic aid)
- board_config.h: PLIP_I2S_DIN=GPIO35 (ES8388 ASDOUT)

Build: clean. Endpoint functional (200 OK, valid WAV structure).
RX data currently silent — suspected hardware issue (mic not connected
to LIN1/LIN2 on this specific kit instance); firmware implementation complete.
2026-06-14 18:50:22 +02:00
electron b31388dd86 Merge pull request 'feat(p5,p6): scaffold morse + nfc puzzles behind CONFIG flags (default off)' (#16) from feat/p5-p6-scaffold into main
CI / platformio (push) Failing after 11m37s
2026-06-14 15:00:31 +00:00
clemsail d5b9297cee feat(p5,p6): scaffold morse + nfc puzzles behind CONFIG flags, default off
CI / platformio (pull_request) Failing after 3m49s
2026-06-14 16:58:42 +02:00
electron 038e0a980a Merge pull request 'feat(p7): scaffold coffre actuator behind CONFIG_ZACUS_P7_COFFRE_ENABLE (default off)' (#15) from feat/p7-coffre-scaffold into main
CI / platformio (push) Failing after 4m9s
2026-06-14 14:15:26 +00:00
clemsail fc4b58f785 feat(p7): scaffold coffre actuator (servo/relay) behind CONFIG_ZACUS_P7_COFFRE_ENABLE, default off
CI / platformio (pull_request) Failing after 12m3s
2026-06-14 16:01:33 +02:00
electron 022e343750 Merge pull request 'feat(box3,plip): POST /game/cmd — trigger cmd_exec over HTTP' (#14) from feat/annex-game-cmd into main
CI / platformio (push) Failing after 4m15s
2026-06-14 12:13:33 +00:00
clemsail 875333b6f0 fix(plip): play_wav_file — SPIFFS lazy-mount for /spiffs/ paths, skip SD gate
CI / platformio (pull_request) Failing after 10m0s
2026-06-14 14:08:08 +02:00
clemsail e9413fa898 feat(box3,plip): POST /game/cmd — trigger cmd_exec over HTTP (WiFi-direct) 2026-06-14 13:54:02 +02:00
electron 39903c376d Merge pull request 'feat(plip): ESP-IDF migration — ES8388 audio + WiFi/httpd + cmd_exec + hook/ring' (#13) from feat/plip-idf-migration into main
CI / platformio (push) Failing after 5m8s
2026-06-14 11:34:21 +00:00
clemsail 309005360b feat(plip_voice): ESP-IDF port of PLIP retro telephone annex
CI / platformio (pull_request) Failing after 4m52s
New IDF project targeting AI-Thinker ESP32-A1S Audio Kit (ESP32, ES8388
codec). Replaces the Arduino PLIP_FIRMWARE with a proper IDF component
structure aligned on box3_voice / scenario_mesh infrastructure.

Phases implemented and hardware-verified:
- A: ES8388 I2C init + I2S TX; 440 Hz test tone exits the speaker
- B: WiFi STA + httpd :80 (GET /status, POST /game/scenario, /game/file)
- C: ESP-NOW CMD receiver via shared scenario_mesh lib; op=play dispatched
     to audio; screen/evt/led logged (no display on PLIP)
- D: Off-hook GPIO monitor (GPIO4=BOOT button stand-in); ring tone cadence;
     POST /voice/hook to master on pickup/hangup (200 OK confirmed)

MAC (first observed): a0:b7:65:e7:f6:44
IP (DHCP): 192.168.0.138

Files:
  plip_voice/CMakeLists.txt          - project root; EXTRA_COMPONENT_DIRS=../lib/scenario_mesh
  plip_voice/partitions.csv          - 4MB OTA dual-bank + SPIFFS (matches PLIP_FIRMWARE)
  plip_voice/sdkconfig.defaults      - ESP32 target, 4MB flash DIO, ch=11 hint (no creds)
  plip_voice/main/Kconfig.projbuild  - PLIP_ menu (SSID/pwd/channel/master_url/hook_gpio)
  plip_voice/main/board_config.h     - A1S pin map (I2C 32/33, I2S 0/27/25/26/35, PA=21)
  plip_voice/main/es8388.[ch]        - ES8388 I2C register init sequence (legacy i2c driver)
  plip_voice/main/audio.[ch]         - I2S TX + async play queue + ring task
  plip_voice/main/net.[ch]           - WiFi STA + httpd (NVS creds with Kconfig fallback)
  plip_voice/main/cmd_exec.[ch]      - D5 CMD executor (play→audio; screen/evt/led→log)
  plip_voice/main/hook_client.[ch]   - POST /voice/hook via esp_http_client
  plip_voice/main/phone.[ch]         - Off-hook GPIO ISR + debounce + ring control
  plip_voice/main/main.c             - app_main boot sequence (all four phases)

Build: IDF 5.4.4, target esp32, clean.
Flash: /dev/cu.usbserial-0001, 4MB flash verified.
2026-06-14 13:29:33 +02:00
electron bbe79deb07 Merge pull request 'feat(box3): POST /game/file over HTTP' (#12) from feat/box3-game-file into main
CI / platformio (push) Failing after 17m50s
2026-06-14 11:09:43 +00:00
clemsail 935f86d844 feat(box3): POST /game/file — write assets to SD over HTTP (WiFi-direct)
CI / platformio (pull_request) Failing after 4m26s
2026-06-14 12:58:45 +02:00
electron 29fa8eed34 Merge pull request 'feat(box3): real screen render + real WAV/I2S play in cmd_exec' (#11) from feat/box3-screen-play-real into main
CI / platformio (push) Failing after 6m5s
2026-06-14 10:02:50 +00:00
clemsail 3e702f6fd8 feat(box3): real screen rendering + WAV I2S playback for CMD executor
CI / platformio (pull_request) Failing after 9m25s
Objective 1 — screen: full-scene faithful rendering on 320x240 LVGL display.
Palette matches idf_zacus display_ui reference: bg #0055AA (Workbench blue),
symbol #FF8800 (orange / font-48), title #FFFFFF (font-24 top), subtitle
#AAAAAA (font-14 bottom). All four effects implemented:
  - pulse: LVGL anim opacity COVER→50 breathing on symbol (600ms half-period)
  - glitch: lv_timer 120ms flicker + X-jitter on title
  - gyro: lv_arc rotating ring around symbol (1200ms full rotation)
  - none: static

Objective 2 — play: real WAV PCM-16 decode + I2S streaming.
  - SD card mounted best-effort via bsp_sdcard_mount() on first play CMD
  - WAV header parser (chunk-walker, tolerant of non-standard ordering)
  - PCM samples streamed via i2s_channel_write (same s_spk_handle as TTS)
  - Embedded test cue (C5-E5-G5, 570ms, 16kHz mono, ~18 KB) baked into
    embedded_wav.h — proves real WAV decode + I2S path without SD card
  - Graceful fallback chain: SD file → embedded cue → 880 Hz beep

main.c: cmd_exec_set_spk_handle() called after speaker_init() to pass the
I2S TX channel handle to the CMD executor.

Tested on ESP32-S3-BOX-3 (/dev/cu.usbmodem11301):
  - All four screen effects confirmed via serial log + visual observation
  - play embedded://cue streams 18240 bytes PCM at 16kHz confirmed
  - SD mount succeeds (card present), file not found → embedded fallback OK
  - No crashes, existing voice pipeline and scenario server unaffected
2026-06-14 12:01:31 +02:00
electron 0b530e70e0 Merge pull request 'feat(box3): ESP-NOW CMD executor (D5 contract)' (#10) from feat/box3-cmd-executor into main
CI / platformio (push) Failing after 9m39s
2026-06-14 09:33:59 +00:00