Files
RTC_BL_PHONE/components/call_manager/call_manager.c
T
clement 935772c1b1
Repo State / repo-state (push) Failing after 21s
fix(bt): coupe DTMF sur appel entrant
2026-06-19 17:15:06 +02:00

279 lines
10 KiB
C

/*
* call_manager.c — machine d'état d'appel avec pont HFP Bluetooth.
*
* États :
* IDLE raccroché — silence, PA coupée
* DIALTONE décroché au repos — tonalité + attente premier chiffre
* DIALING chiffres en cours — 3 s silence → bt_hfp_dial() → OUTGOING
* OUTGOING appel sortant émis — attente AUDIO_CONNECTED → ACTIVE
* INCOMING appel entrant BT — cloche sonne + attente décroché
* ACTIVE audio SCO bidirectionnel — pont I2S↔BT
*
* Thread-safety :
* call_manager_bt_event() est appelé depuis la tâche BT (contexte temps-réel).
* Elle poste dans une file FreeRTOS (xQueueSend non-bloquant) — sans conserver
* de pointeur. La boucle call_task() draine la file à chaque tour (50 ms).
* Avec une file de 8 slots, deux événements BT rapprochés ne se perdent plus.
*/
#include "call_manager.h"
#include "hook_monitor.h"
#include "dialer.h"
#include "dtmf.h"
#include "audio_router.h"
#include "hal_i2s.h"
#include "slic.h"
#include "bt_hfp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include <stdbool.h>
#include <string.h>
#define TAG "call_manager"
#define DIAL_COMPLETE_SILENCE_MS 3000
/* ---- FSM ---- */
typedef enum {
ST_IDLE,
ST_DIALTONE,
ST_DIALING,
ST_OUTGOING,
ST_INCOMING,
ST_ACTIVE
} call_state_t;
/* ---- Item de file BT ---- */
typedef struct {
bt_hfp_event_t ev;
bool msbc;
char number[32];
} bt_evt_item_t;
static QueueHandle_t s_bt_q = NULL;
/* ---- État hook (mis à jour depuis le callback hook_monitor, contexte quelconque) ---- */
static volatile bool s_offhook = false;
static volatile bool s_hook_dirty = false;
/* ---- Variables d'état SCO et numéro entrant (mis à jour uniquement dans call_task) ---- */
static char s_incoming_num[32];
static bool s_sco_msbc = false;
/* ---- État SCO (mis à jour uniquement dans call_task) ---- */
static bool s_sco_up = false;
static volatile bool s_sco_ready = false; /* SCO connecte (incl. sonnerie in-band) */
/* ------------------------------------------------------------------ */
/* Callback appelé depuis la tâche BT — DOIT rester léger */
/* ------------------------------------------------------------------ */
void call_manager_bt_event(bt_hfp_event_t ev, const bt_hfp_event_data_t *d)
{
if (!s_bt_q) return;
bt_evt_item_t it = { .ev = ev, .msbc = (d && d->msbc), .number = {0} };
if (d && d->number) {
strncpy(it.number, d->number, sizeof(it.number) - 1);
}
xQueueSend(s_bt_q, &it, 0); /* non bloquant : contexte tâche BT temps-réel */
}
/* ------------------------------------------------------------------ */
/* go_idle : retour à l'état de repos (coupe tout) */
/* ------------------------------------------------------------------ */
static void go_idle(void)
{
audio_router_tones_stop();
if (s_sco_up) {
audio_router_sco_end();
s_sco_up = false;
}
dtmf_stop();
slic_ring_stop();
hal_audio_pa_set(false);
dialer_reset();
ESP_LOGI(TAG, "-> IDLE");
}
/* ------------------------------------------------------------------ */
/* callback changement d'état du crochet */
/* ------------------------------------------------------------------ */
static void on_hook_change(bool offhook)
{
s_offhook = offhook;
s_hook_dirty = true;
}
/* ------------------------------------------------------------------ */
/* Tâche principale de la FSM d'appel */
/* ------------------------------------------------------------------ */
static void call_task(void *arg)
{
(void)arg;
call_state_t st = ST_IDLE;
for (;;) {
/* --- 1. Événements BT HFP : drain complet de la file ---- */
bt_evt_item_t bev;
while (s_bt_q && xQueueReceive(s_bt_q, &bev, 0) == pdTRUE) {
if (bev.ev == BT_HFP_EV_INCOMING && bev.number[0]) {
strncpy(s_incoming_num, bev.number, sizeof(s_incoming_num) - 1);
s_incoming_num[sizeof(s_incoming_num) - 1] = '\0';
}
if (bev.ev == BT_HFP_EV_AUDIO_CONNECTED) s_sco_msbc = bev.msbc;
switch (bev.ev) {
case BT_HFP_EV_INCOMING:
if (st == ST_IDLE || st == ST_DIALTONE) {
audio_router_tones_stop();
dtmf_stop(); /* pas de detection DTMF pendant un appel entrant */
slic_ring_start(); /* fait sonner la cloche */
st = ST_INCOMING;
ESP_LOGI(TAG, "-> INCOMING %s", s_incoming_num);
}
break;
case BT_HFP_EV_AUDIO_CONNECTED:
s_sco_ready = true;
if (st == ST_INCOMING) {
/* SCO ouvert pour la sonnerie in-band du mobile : NE PAS
* ouvrir le pont audio ni passer ACTIVE — la cloche doit
* sonner jusqu'au decroche reel. */
ESP_LOGI(TAG, "SCO in-band (sonnerie) — on continue de sonner");
} else {
/* Appel sortant decroche par le correspondant, ou reprise SCO. */
slic_ring_stop();
dtmf_stop();
audio_router_sco_begin(s_sco_msbc);
s_sco_up = true;
st = ST_ACTIVE;
ESP_LOGI(TAG, "-> ACTIVE (audio SCO %s)",
s_sco_msbc ? "mSBC 16k" : "CVSD 8k");
}
break;
case BT_HFP_EV_AUDIO_DISCONNECTED:
s_sco_ready = false;
if (s_sco_up) {
audio_router_sco_end();
s_sco_up = false;
}
break;
case BT_HFP_EV_CALL_ENDED:
s_sco_ready = false;
slic_ring_stop();
if (s_sco_up) {
audio_router_sco_end();
s_sco_up = false;
}
/* Si encore décroché : tonalité d'occupation */
if (hook_monitor_offhook()) {
audio_router_busy_start();
st = ST_DIALTONE; /* attente du raccroché */
} else {
st = ST_IDLE;
}
ESP_LOGI(TAG, "-> appel terminé");
break;
case BT_HFP_EV_SLC_DISCONNECTED:
/* Perte du lien BT Service Level Connection : fermer tout
* proprement et retourner à IDLE. */
if (s_sco_up) {
audio_router_sco_end();
s_sco_up = false;
}
slic_ring_stop();
go_idle();
st = ST_IDLE;
ESP_LOGW(TAG, "lien BT perdu -> IDLE");
break;
default:
break;
}
}
/* --- 2. Événements hook (crochet) ---- */
if (s_hook_dirty) {
s_hook_dirty = false;
if (!s_offhook) {
/* Raccroché : terminer l'appel BT si actif */
if (st == ST_ACTIVE || st == ST_OUTGOING || st == ST_INCOMING) {
bt_hfp_hangup();
}
go_idle();
st = ST_IDLE;
} else {
/* Décroché */
if (st == ST_INCOMING) {
/* Décroché pendant sonnerie : répondre + ouvrir le pont. */
slic_ring_stop();
bt_hfp_answer();
if (s_sco_ready) {
/* SCO deja etabli (sonnerie in-band) : ouvrir le pont. */
dtmf_stop();
audio_router_sco_begin(s_sco_msbc);
s_sco_up = true;
st = ST_ACTIVE;
ESP_LOGI(TAG, "décroché -> answer + pont SCO -> ACTIVE");
} else {
/* Pas encore de SCO : le pont s'ouvrira a AUDIO_CONNECTED. */
st = ST_ACTIVE;
ESP_LOGI(TAG, "bt_hfp_answer() envoyé, attente SCO");
}
} else if (st == ST_IDLE) {
/* Décroché au repos : tonalité + armement DTMF */
dialer_reset();
audio_router_dialtone_start();
dtmf_start();
ESP_LOGI(TAG, "-> DIALTONE (décroché)");
st = ST_DIALTONE;
}
/* ST_ACTIVE, ST_OUTGOING : décroché redondant, ignorer */
}
}
/* --- 3. Progression de la numérotation ---- */
if (st == ST_DIALTONE && !dialer_idle()) {
/* Premier chiffre : couper la tonalité, passer en DIALING */
audio_router_tones_stop();
hal_audio_pa_set(false);
ESP_LOGI(TAG, "-> DIALING, numéro en cours: \"%s\"", dialer_current());
st = ST_DIALING;
} else if (st == ST_DIALING) {
if (!dialer_idle() && dialer_ms_since_last() > DIAL_COMPLETE_SILENCE_MS) {
/* 3 s de silence : numéro complet → appel BT */
ESP_LOGI(TAG, "appel sortant: \"%s\"", dialer_current());
bt_hfp_dial(dialer_current());
dtmf_stop();
st = ST_OUTGOING;
/* -> ACTIVE sera déclenché par BT_HFP_EV_AUDIO_CONNECTED */
}
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
/* ------------------------------------------------------------------ */
/* API publique */
/* ------------------------------------------------------------------ */
void call_manager_start(void)
{
s_bt_q = xQueueCreate(8, sizeof(bt_evt_item_t));
ESP_ERROR_CHECK(hal_i2s_capture_begin());
if (hook_monitor_start(on_hook_change) != ESP_OK) {
ESP_LOGE(TAG, "hook_monitor_start a échoué");
return;
}
xTaskCreatePinnedToCore(call_task, "call_mgr", 4096, NULL, 5, NULL, 1);
ESP_LOGI(TAG, "call_manager démarré");
}