The marginal A1S cradle contact flickers open mid-call; treating a
brief open as a hangup dropped the live conversation. Raise the
prolonged-open hangup threshold to 2.5 s and make the resync
asymmetric: a PICKUP is confirmed fast (600 ms, calls answer promptly)
while a HANGUP needs the line to stay open 2.5 s. Brief flickers no
longer end the call; a real hangup still fires ~2.5 s later.
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).
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).
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.
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.
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).
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.
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.
- 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.
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