From b8ba1457fb3e276584e81a721cad1eb2dcb7ce3a Mon Sep 17 00:00:00 2001 From: electron-rare Date: Sun, 14 Jun 2026 20:43:33 +0200 Subject: [PATCH] feat(plip): mute audio output when handset on-hook (gate playback on hook state) --- plip_voice/main/audio.c | 18 +++++++++++++++++- plip_voice/main/audio.h | 6 ++++++ plip_voice/main/phone.c | 30 ++++++++++++++++++++++++++---- plip_voice/main/phone.h | 5 +++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/plip_voice/main/audio.c b/plip_voice/main/audio.c index d7352d9..3732452 100644 --- a/plip_voice/main/audio.c +++ b/plip_voice/main/audio.c @@ -17,6 +17,7 @@ #include "audio.h" #include "board_config.h" #include "es8388.h" +#include "phone.h" #include #include @@ -24,6 +25,7 @@ #include #include +#include "driver/gpio.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_spiffs.h" @@ -371,10 +373,18 @@ static void audio_worker_task(void *arg) ESP_LOGI(TAG, "stop"); break; case CMD_RING: - ESP_LOGI(TAG, "ring start"); + /* Ring must sound on-hook (caller hears it before picking up). + * Force PA on for the duration of the ring regardless of hook state. */ + ESP_LOGI(TAG, "ring start — forcing PA on"); + audio_pa_set(true); xTaskCreate(ring_task, "ring", 4096, NULL, 4, NULL); break; case CMD_PLAY: { + /* Gate playback on hook state: no sound if handset is on-hook. */ + if (!phone_is_offhook()) { + ESP_LOGI(TAG, "play ignored: on-hook (handset down)"); + break; + } const char *p = cmd.path; if (!p || !*p || strncmp(p, "embedded:", 9) == 0) { ESP_LOGI(TAG, "play: embedded cue"); @@ -393,6 +403,12 @@ static void audio_worker_task(void *arg) /* ── Public API ──────────────────────────────────────────────────────────── */ +void audio_pa_set(bool enable) +{ + gpio_set_level(PLIP_PA_ENABLE, enable ? 1 : 0); + ESP_LOGI(TAG, "PA %s", enable ? "ON" : "OFF"); +} + esp_err_t audio_init(void) { /* 1. ES8388 I2C init + register sequence. */ diff --git a/plip_voice/main/audio.h b/plip_voice/main/audio.h index 8354609..278dd0c 100644 --- a/plip_voice/main/audio.h +++ b/plip_voice/main/audio.h @@ -43,6 +43,12 @@ esp_err_t audio_stop(void); * audio_stop() is called. Non-blocking — spawns an internal task. */ esp_err_t audio_ring_start(void); +/* Enable or disable the power amplifier (GPIO21 PA_ENABLE). + * Call audio_pa_set(true) on off-hook, audio_pa_set(false) on on-hook. + * ring_start forces PA on internally; ring_stop calls audio_pa_set(false) + * only if the handset is on-hook. */ +void audio_pa_set(bool enable); + /* * Capture microphone audio and encode as WAV (16 kHz, mono, S16-LE). * diff --git a/plip_voice/main/phone.c b/plip_voice/main/phone.c index 1d1b5b6..0d4eff9 100644 --- a/plip_voice/main/phone.c +++ b/plip_voice/main/phone.c @@ -23,6 +23,7 @@ static volatile bool s_edge_pending = false; static volatile bool s_ringing = false; +static volatile bool s_offhook = false; /* true = handset picked up */ /* IRAM_ATTR: ISR must live in IRAM on original ESP32. */ static void IRAM_ATTR on_hook_isr(void *arg) @@ -66,8 +67,20 @@ static void phone_task(void *arg) /* Read and report initial level so master state machine is in sync. */ int last_level = gpio_get_level(hook_gpio); const char *init_state = (last_level == 0) ? "off" : "on"; + s_offhook = (last_level == 0); ESP_LOGI(TAG, "phone task ready, hook GPIO=%d level=%d (%s)", hook_gpio, last_level, init_state); + + /* Set PA state to match initial hook position. + * On-hook at boot → mute PA so no audio escapes while handset is down. */ + if (!s_offhook) { + ESP_LOGI(TAG, "boot: on-hook → PA off (mute)"); + audio_pa_set(false); + } else { + ESP_LOGI(TAG, "boot: off-hook → PA on (unmute)"); + audio_pa_set(true); + } + hook_client_report(init_state, "boot"); for (;;) { @@ -78,14 +91,18 @@ static void phone_task(void *arg) if (level != last_level) { last_level = level; if (level == 0) { - /* Off-hook: handset picked up. Stop ringing if active. */ - ESP_LOGI(TAG, "off-hook (pickup) detected"); + /* Off-hook: handset picked up. Enable PA, stop ringing. */ + s_offhook = true; + ESP_LOGI(TAG, "off-hook (pickup) detected — PA on"); + audio_pa_set(true); phone_ring_stop(); hook_client_report("off", "pickup"); } else { - /* On-hook: handset hung up. */ - ESP_LOGI(TAG, "on-hook (hangup) detected"); + /* On-hook: handset hung up. Stop playback, kill PA. */ + s_offhook = false; + ESP_LOGI(TAG, "on-hook (hangup) detected — PA off, stopping audio"); audio_stop(); + audio_pa_set(false); hook_client_report("on", "hangup"); } } @@ -101,3 +118,8 @@ esp_err_t phone_init(void) TASK_PRIO, NULL, 1); return (ok == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM; } + +bool phone_is_offhook(void) +{ + return s_offhook; +} diff --git a/plip_voice/main/phone.h b/plip_voice/main/phone.h index 97753a8..a9b1795 100644 --- a/plip_voice/main/phone.h +++ b/plip_voice/main/phone.h @@ -10,6 +10,7 @@ * Hook events: forwarded to hook_client_report(). */ +#include #include "esp_err.h" #ifdef __cplusplus @@ -26,6 +27,10 @@ void phone_ring_start(void); /* Stop ringing. */ void phone_ring_stop(void); +/* Returns true if the handset is currently off-hook (picked up). + * Safe to call from any task; backed by a volatile flag updated in phone_task. */ +bool phone_is_offhook(void); + #ifdef __cplusplus } #endif