diff --git a/components/bt_hfp/bt_hfp.c b/components/bt_hfp/bt_hfp.c index d3fabd7..fb1a81f 100644 --- a/components/bt_hfp/bt_hfp.c +++ b/components/bt_hfp/bt_hfp.c @@ -149,6 +149,9 @@ static void hf_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *para esp_hf_client_register_data_callback(sco_incoming_cb, sco_outgoing_cb); } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED) { ESP_LOGI(TAG, "audio SCO deconnecte"); + /* Désenregistrement propre des callbacks data SCO avant d'émettre + * l'événement — évite des appels fantômes après fin de SCO. */ + esp_hf_client_register_data_callback(NULL, NULL); emit(BT_HFP_EV_AUDIO_DISCONNECTED, NULL, false); } break; diff --git a/components/call_manager/CMakeLists.txt b/components/call_manager/CMakeLists.txt index a4b22b8..c6e6ad8 100644 --- a/components/call_manager/CMakeLists.txt +++ b/components/call_manager/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "call_manager.c" INCLUDE_DIRS "include" - REQUIRES hook_monitor dialer dtmf audio_router hal_i2s + REQUIRES hook_monitor dialer dtmf audio_router hal_i2s bt_hfp slic_ks0835 ) diff --git a/components/call_manager/call_manager.c b/components/call_manager/call_manager.c index 707d4eb..39ffeca 100644 --- a/components/call_manager/call_manager.c +++ b/components/call_manager/call_manager.c @@ -1,10 +1,18 @@ /* - * call_manager.c — machine d'état d'appel LOCALE (sans Bluetooth). - * Adapté de plip_voice/main/conversation.c, dépouillé du routing/greet/scene. + * call_manager.c — machine d'état d'appel avec pont HFP Bluetooth. * - * IDLE : raccroché — silence, PA coupée - * DIALTONE : décroché — tonalité de numérotation, attente du 1er chiffre - * DIALING : chiffres en cours — attente de 3 s de silence -> numéro figé/logué + * É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 pose uniquement des flags volatils et copie le numéro via strncpy. + * La boucle call_task() consomme ces flags dans son propre contexte. */ #include "call_manager.h" #include "hook_monitor.h" @@ -12,71 +20,193 @@ #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 "esp_log.h" #include +#include #define TAG "call_manager" #define DIAL_COMPLETE_SILENCE_MS 3000 -typedef enum { ST_IDLE, ST_DIALTONE, ST_DIALING } call_state_t; +/* ---- FSM ---- */ +typedef enum { + ST_IDLE, + ST_DIALTONE, + ST_DIALING, + ST_OUTGOING, + ST_INCOMING, + ST_ACTIVE +} call_state_t; +/* ---- État hook (mis à jour depuis le callback hook_monitor, contexte quelconque) ---- */ static volatile bool s_offhook = false; static volatile bool s_hook_dirty = false; +/* ---- Pont BT : flags posés par call_manager_bt_event() (contexte tâche BT) ---- */ +static volatile bool s_bt_dirty = false; +static volatile bt_hfp_event_t s_bt_ev = BT_HFP_EV_SLC_DISCONNECTED; +static char s_incoming_num[32]; /* copié avec strncpy — jamais stocker le ptr */ +static volatile bool s_sco_msbc = false; + +/* ---- État SCO (mis à jour uniquement dans call_task) ---- */ +static bool s_sco_up = false; + +/* ------------------------------------------------------------------ */ +/* 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) +{ + s_bt_ev = ev; + if (ev == BT_HFP_EV_INCOMING && d && d->number) { + strncpy(s_incoming_num, d->number, sizeof(s_incoming_num) - 1); + s_incoming_num[sizeof(s_incoming_num) - 1] = '\0'; + } + if (ev == BT_HFP_EV_AUDIO_CONNECTED && d) { + s_sco_msbc = d->msbc; + } + s_bt_dirty = true; +} + +/* ------------------------------------------------------------------ */ +/* 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; } -static void go_idle(void) -{ - audio_router_tones_stop(); - dtmf_stop(); - hal_audio_pa_set(false); - dialer_reset(); - ESP_LOGI(TAG, "-> IDLE (raccroché)"); -} - +/* ------------------------------------------------------------------ */ +/* Tâche principale de la FSM d'appel */ +/* ------------------------------------------------------------------ */ static void call_task(void *arg) { (void)arg; call_state_t st = ST_IDLE; for (;;) { - if (s_hook_dirty) { - s_hook_dirty = false; - if (!s_offhook) { - go_idle(); - st = ST_IDLE; - } else if (st == ST_IDLE) { - /* décroché depuis le repos : tonalité + armement numérotation */ - dialer_reset(); - audio_router_dialtone_start(); /* active la PA en interne */ - dtmf_start(); /* arme la capture DTMF */ - ESP_LOGI(TAG, "-> DIALTONE (décroché)"); - st = ST_DIALTONE; + + /* --- 1. Événements BT HFP ---- */ + if (s_bt_dirty) { + s_bt_dirty = false; + switch (s_bt_ev) { + + case BT_HFP_EV_INCOMING: + if (st == ST_IDLE || st == ST_DIALTONE) { + audio_router_tones_stop(); + 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: + slic_ring_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: + if (s_sco_up) { + audio_router_sco_end(); + s_sco_up = false; + } + break; + + case BT_HFP_EV_CALL_ENDED: + 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; + + 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 */ + slic_ring_stop(); + bt_hfp_answer(); + /* -> ACTIVE sera déclenché par BT_HFP_EV_AUDIO_CONNECTED */ + ESP_LOGI(TAG, "bt_hfp_answer() envoyé"); + } 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 composé : couper la tonalité, passer en DIALING */ + /* Premier chiffre : couper la tonalité, passer en DIALING */ audio_router_tones_stop(); - hal_audio_pa_set(false); /* silence pendant la numerotation (DTMF = micro) */ - ESP_LOGI(TAG, "-> DIALING, numéro: \"%s\"", dialer_current()); + 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) { - ESP_LOGI(TAG, "NUMÉRO COMPOSÉ: \"%s\" (impulsion + DTMF)", - dialer_current()); - /* Phase 4 : ici on lancera l'appel HFP. Pour l'instant on fige. */ + /* 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_IDLE; /* on attend le raccroché pour revenir proprement */ + st = ST_OUTGOING; + /* -> ACTIVE sera déclenché par BT_HFP_EV_AUDIO_CONNECTED */ } } @@ -84,6 +214,9 @@ static void call_task(void *arg) } } +/* ------------------------------------------------------------------ */ +/* API publique */ +/* ------------------------------------------------------------------ */ void call_manager_start(void) { ESP_ERROR_CHECK(hal_i2s_capture_begin()); @@ -92,5 +225,5 @@ void call_manager_start(void) return; } xTaskCreatePinnedToCore(call_task, "call_mgr", 4096, NULL, 5, NULL, 1); - ESP_LOGI(TAG, "call_manager démarré (mode local, sans BT)"); + ESP_LOGI(TAG, "call_manager démarré"); } diff --git a/components/call_manager/include/call_manager.h b/components/call_manager/include/call_manager.h index ccf0ba8..9088931 100644 --- a/components/call_manager/include/call_manager.h +++ b/components/call_manager/include/call_manager.h @@ -1,16 +1,32 @@ #pragma once +#include "bt_hfp.h" + #ifdef __cplusplus extern "C" { #endif -/* Start the local call state machine (no Bluetooth yet): - * on-hook -> idle (silence, PA off) - * off-hook -> dial tone + collect digits (pulse + DTMF) - * number complete (3 s silence) -> log number - * Call once after audio + slic + dialer + dtmf + hook_monitor are ready. */ +/** + * Démarre la machine d'état d'appel (hook_monitor + call_task). + * Doit être appelé AVANT bt_hfp_init() pour que le callback soit prêt. + * + * États FSM : + * 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 BT — attente AUDIO_CONNECTED → ACTIVE + * INCOMING appel entrant BT — cloche + attente décroché → bt_hfp_answer() + * ACTIVE audio SCO actif — pont bidirectionnel I2S↔BT + */ void call_manager_start(void); +/** + * Callback BT HFP à passer à bt_hfp_init(). + * Appelé depuis la tâche BT temps-réel → LÉGER : flags + strncpy uniquement. + * Ne jamais stocker le pointeur d->number. + */ +void call_manager_bt_event(bt_hfp_event_t ev, const bt_hfp_event_data_t *d); + #ifdef __cplusplus } #endif diff --git a/main/app_main.c b/main/app_main.c index 77261cc..5a5d10d 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -11,14 +11,6 @@ static const char *TAG = "rtc_phone"; -/* Handler de log temporaire pour les evenements BT HFP (Task 2). - * Remplace par call_manager en Task 5. */ -static void bt_log_cb(bt_hfp_event_t ev, const bt_hfp_event_data_t *d) -{ - ESP_LOGI(TAG, "BT event %d (number=%s msbc=%d)", (int)ev, - d->number ? d->number : "-", (int)d->msbc); -} - void app_main(void) { ESP_LOGI(TAG, "RTC BL PHONE — socle ESP-IDF v5.4"); @@ -30,8 +22,11 @@ void app_main(void) * call_manager déclenchera audio_router_dialtone_start() au décroché. */ audio_router_init(); dialer_init(); + /* call_manager_start() AVANT bt_hfp_init() : le callback doit être + * enregistré et prêt à recevoir des événements BT dès l'init. */ call_manager_start(); - /* Pile BT HFP-HF — NVS déjà initialisé par config_store_init(). */ - ESP_ERROR_CHECK(bt_hfp_init(bt_log_cb)); - ESP_LOGI(TAG, "telephonie locale + BT HFP prets"); + /* Pile BT HFP-HF — NVS déjà initialisé par config_store_init(). + * call_manager_bt_event remplace le handler de log temporaire (Task 2). */ + ESP_ERROR_CHECK(bt_hfp_init(call_manager_bt_event)); + ESP_LOGI(TAG, "call_manager + bt_hfp prets — appairer sur 'RTC_BL_PHONE'"); }