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
This commit is contained in:
clement
2026-06-16 10:32:01 +02:00
parent 4a6fc435a7
commit 1561e613e1
3 changed files with 75 additions and 13 deletions
+58 -10
View File
@@ -21,6 +21,7 @@
#include "tones.h"
#include "audio.h"
#include "phone.h"
#include "slic.h"
#include "turn_client.h"
#if CONFIG_PLIP_DIAL_DTMF
#include "dtmf.h"
@@ -46,7 +47,7 @@
#define CAPTURE_MAX_IRAM (128 * 1024)
#define CAPTURE_MAX_MS_PSRAM 8000
#define CAPTURE_MAX_MS_IRAM 4000
#define CAPTURE_SILENCE_MS 800
#define CAPTURE_SILENCE_MS 600 /* end-of-speech VAD: shorter = snappier reply (was 800) */
#define REPLY_POLL_MS 200 /* interval for checking hook during playback */
#define REPLY_PLAYBACK_EXTRA_MS 500 /* safety margin added to computed WAV duration */
#define BETWEEN_TURNS_MS 300 /* short pause between capture rounds */
@@ -82,6 +83,13 @@ static char s_sid[32] = {0};
* would 404 at the gateway. Capture the clean routed number here once. */
static char s_number[16] = {0};
/* Incoming-call mode: an NPC "calls" the phone. conversation_arm_incoming()
* stores the caller's persona number and rings; when the handset is then picked
* up from IDLE, we skip the outgoing dialtone/dial/ringback and go straight to
* GREET with this number (the NPC speaks first). */
static char s_incoming_number[16] = {0};
static volatile bool s_incoming_armed = false;
/* Known numbers: ringback when dialed */
static const char *KNOWN[] = {
"12", "3615", "15", "17", "18", "0142738200", NULL
@@ -108,6 +116,24 @@ static void go_idle(void)
ESP_LOGI(TAG, "-> IDLE");
}
/* Begin an answered INCOMING call: stop ringing, lock the persona number, and
* jump straight to GREET (the NPC speaks first). Drives the conversation's own
* s_offhook directly because phone.c's debounced GPIO detection is unreliable
* during ringing — we trust the SLIC's raw off-hook reading instead. */
static void enter_incoming_greet(void)
{
s_incoming_armed = false;
phone_ring_stop();
tones_stop();
dialer_reset();
s_offhook = true;
snprintf(s_number, sizeof(s_number), "%s", s_incoming_number);
snprintf(s_sid, sizeof(s_sid), "%lld", (long long)esp_timer_get_time());
audio_pa_set(true);
s_state = STATE_GREET;
ESP_LOGI(TAG, "INCOMING pickup -> GREET num=%s sid=%s", s_number, s_sid);
}
static void conv_task(void *arg)
{
(void)arg;
@@ -127,15 +153,21 @@ static void conv_task(void *arg)
}
continue;
} else {
/* Off-hook: start dial tone if we were idle */
/* Off-hook from idle */
if (s_state == STATE_IDLE) {
dialer_reset();
tones_dialtone_start();
if (s_incoming_armed) {
/* INCOMING call answered (phone.c detected the edge). */
enter_incoming_greet();
} else {
/* Outgoing call: dial tone, wait for digits. */
dialer_reset();
tones_dialtone_start();
#if CONFIG_PLIP_DIAL_DTMF
dtmf_start();
dtmf_start();
#endif
s_state = STATE_DIALTONE;
ESP_LOGI(TAG, "off-hook -> DIALTONE");
s_state = STATE_DIALTONE;
ESP_LOGI(TAG, "off-hook -> DIALTONE");
}
}
continue;
}
@@ -144,7 +176,13 @@ static void conv_task(void *arg)
/* State machine polling */
switch (s_state) {
case STATE_IDLE:
break; /* nothing to do */
/* Incoming-call pickup: phone.c's debounced edge detection is
* unreliable while the bell rings, so poll the SLIC's raw off-hook
* line directly — it reads the pickup cleanly mid-ring. */
if (s_incoming_armed && slic_is_offhook()) {
enter_incoming_greet();
}
break;
case STATE_DIALTONE:
if (!s_offhook) {
@@ -268,12 +306,12 @@ static void conv_task(void *arg)
* the mic. Never capture while anything is playing, or the
* playback feeds back and the line saturates. Wait for the
* greeting/filler/reply to finish, then let the line settle. */
vTaskDelay(pdMS_TO_TICKS(250)); /* let a just-queued clip start */
vTaskDelay(pdMS_TO_TICKS(120)); /* let a just-queued clip start (was 250) */
while (s_offhook && audio_is_playing()) {
vTaskDelay(pdMS_TO_TICKS(50));
}
if (!s_offhook) break;
vTaskDelay(pdMS_TO_TICKS(200)); /* line settle after playback */
vTaskDelay(pdMS_TO_TICKS(100)); /* line settle after playback (was 200) */
/* Capture player utterance — nothing is playing now. */
int n = audio_capture_wav(cap_buf, cap_max,
@@ -355,3 +393,13 @@ void conversation_on_hook_change(bool offhook)
s_offhook = offhook;
s_hook_changed = true;
}
void conversation_arm_incoming(const char *number)
{
snprintf(s_incoming_number, sizeof(s_incoming_number), "%s",
(number && number[0]) ? number : "17");
s_incoming_armed = true;
phone_ring_start();
ESP_LOGI(TAG, "incoming call armed (num=%s) — ringing until pickup",
s_incoming_number);
}
+5
View File
@@ -3,3 +3,8 @@
void conversation_init(void);
void conversation_on_hook_change(bool offhook);
/* Arm an INCOMING call from NPC persona `number` and start ringing. When the
* handset is picked up from idle, the conversation jumps straight to the
* greeting (persona speaks first) instead of the outgoing dial-tone path. */
void conversation_arm_incoming(const char *number);
+12 -3
View File
@@ -26,6 +26,7 @@
#include "audio.h"
#include "cmd_exec.h"
#include "phone.h"
#include "conversation.h"
#include "es8388.h"
#include "slic.h"
#include "board_config.h"
@@ -471,9 +472,17 @@ static esp_err_t handle_cmd_post(httpd_req_t *req)
static esp_err_t handle_debug_ring(httpd_req_t *req)
{
phone_ring_start();
ESP_LOGI(TAG, "debug/ring: ring started");
return send_json(req, "200 OK", "{\"ok\":true,\"ringing\":true}");
/* /debug/ring[?number=NN] — arm an INCOMING call from persona NN (default
* 17) and ring. On pickup the conversation jumps straight to the greeting. */
char query[48] = {0}, number[16] = {0};
httpd_req_get_url_query_str(req, query, sizeof(query));
httpd_query_key_value(query, "number", number, sizeof(number));
conversation_arm_incoming(number); /* defaults to "17" if empty; starts ringing */
char resp[64];
snprintf(resp, sizeof(resp), "{\"ok\":true,\"ringing\":true,\"incoming\":\"%s\"}",
number[0] ? number : "17");
ESP_LOGI(TAG, "debug/ring: incoming armed (num=%s), ringing", number[0] ? number : "17");
return send_json(req, "200 OK", resp);
}
/* ── GET /debug/ringstop (stop ring tone over HTTP) ─────────────────────── */