feat(bt): call_manager HFP appels entrant/sortant
- FSM étendue : ST_OUTGOING, ST_INCOMING, ST_ACTIVE - call_manager_bt_event() : pont léger BT→FSM (flags + strncpy) - Transitions hook : answer/hangup/dial HFP câblés - go_idle() coupe SCO si actif - bt_hfp.c : désenregistrement SCO sur AUDIO_DISCONNECTED - CMake REQUIRES += bt_hfp slic_ks0835 - app_main : call_manager_start() avant bt_hfp_init()
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#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é");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-11
@@ -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'");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user