279 lines
10 KiB
C
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é");
|
|
}
|