feat(tel): call_manager local + PA single-owner
- Crée composant call_manager: FSM IDLE/DIALTONE/DIALING - PA single-owner: es8388_init la coupe (niveau 0) au démarrage - dtmf: #include dialer.h, volatile task_handle, got<0 vs got==0 - hook_monitor: guard double-start + stack 4096 - app_main: retire sondes + tonalité boot, câble call_manager_start
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "call_manager.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES hook_monitor dialer dtmf audio_router hal_i2s
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* call_manager.c — machine d'état d'appel LOCALE (sans Bluetooth).
|
||||
* Adapté de plip_voice/main/conversation.c, dépouillé du routing/greet/scene.
|
||||
*
|
||||
* 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é
|
||||
*/
|
||||
#include "call_manager.h"
|
||||
#include "hook_monitor.h"
|
||||
#include "dialer.h"
|
||||
#include "dtmf.h"
|
||||
#include "audio_router.h"
|
||||
#include "hal_i2s.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define TAG "call_manager"
|
||||
|
||||
#define DIAL_COMPLETE_SILENCE_MS 3000
|
||||
|
||||
typedef enum { ST_IDLE, ST_DIALTONE, ST_DIALING } call_state_t;
|
||||
|
||||
static volatile bool s_offhook = false;
|
||||
static volatile bool s_hook_dirty = false;
|
||||
|
||||
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é)");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (st == ST_DIALTONE && !dialer_idle()) {
|
||||
/* premier chiffre composé : couper la tonalité, passer en DIALING */
|
||||
audio_router_tones_stop();
|
||||
ESP_LOGI(TAG, "-> DIALING, numéro: \"%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. */
|
||||
dtmf_stop();
|
||||
st = ST_IDLE; /* on attend le raccroché pour revenir proprement */
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
}
|
||||
|
||||
void call_manager_start(void)
|
||||
{
|
||||
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é (mode local, sans BT)");
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#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. */
|
||||
void call_manager_start(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "dtmf.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES hal_i2s
|
||||
# NOTE Task 6 : ajouter "dialer" aux REQUIRES quand le composant existera.
|
||||
# dialer_push_digit est appelé dans dtmf_task qui n'est pas encore activée.
|
||||
REQUIRES hal_i2s dialer
|
||||
)
|
||||
|
||||
+5
-11
@@ -52,15 +52,8 @@
|
||||
#include "esp_log.h"
|
||||
#define TAG "dtmf"
|
||||
|
||||
/* NOTE Task 4 : dialer.h sera disponible quand le composant dialer existera.
|
||||
* Pour l'instant, déclaration forward pour éviter une dépendance de build
|
||||
* avant Task 4. L'include réel sera rétabli en Task 6 avec REQUIRES dialer. */
|
||||
#ifdef CONFIG_RTC_DTMF_DIALER_READY
|
||||
/* Task 6 : dialer est disponible — on inclut l'en-tête réel. */
|
||||
#include "dialer.h"
|
||||
#else
|
||||
/* Stub forward — dialer_push_digit sera lié quand dialer existera (Task 4/6). */
|
||||
void dialer_push_digit(int digit);
|
||||
#endif
|
||||
|
||||
#endif /* DTMF_HOST_TEST */
|
||||
|
||||
@@ -248,8 +241,8 @@ no_tone:
|
||||
#define DTMF_TASK_PRIO 3
|
||||
#define DTMF_FRAME_SIZE 320
|
||||
|
||||
static volatile bool s_armed_flag = false;
|
||||
static TaskHandle_t s_task_handle = NULL;
|
||||
static volatile bool s_armed_flag = false;
|
||||
static volatile TaskHandle_t s_task_handle = NULL;
|
||||
|
||||
static void dtmf_task(void *arg)
|
||||
{
|
||||
@@ -279,7 +272,8 @@ static void dtmf_task(void *arg)
|
||||
}
|
||||
|
||||
int got = hal_i2s_capture_read_frame(frame, DTMF_FRAME_SIZE, &rms_sq);
|
||||
if (got <= 0) continue;
|
||||
if (got < 0) { vTaskDelay(pdMS_TO_TICKS(20)); continue; }
|
||||
if (got == 0) continue;
|
||||
|
||||
char sym = dtmf_detect_frame(frame, got);
|
||||
if (sym == '\0') continue;
|
||||
|
||||
@@ -235,9 +235,9 @@ esp_err_t es8388_init(void)
|
||||
if (i2c_write_reg(ES8388_ADC_POWER, 0x00) != ESP_OK) return ESP_FAIL;
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); /* let ADC analog settle after full power-up */
|
||||
|
||||
/* 15. Enable PA (power amplifier for speaker). */
|
||||
/* 15. Configure PA GPIO en sortie, coupé au repos (propriétaire unique : hal_audio_pa_set). */
|
||||
gpio_set_direction(PLIP_PA_ENABLE, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(PLIP_PA_ENABLE, 1);
|
||||
gpio_set_level(PLIP_PA_ENABLE, 0); /* PA pilotée par call_manager (off au repos) */
|
||||
|
||||
/* Verify key register values to confirm ADC path is configured correctly. */
|
||||
uint8_t ctl1=0, adcpwr=0, adcinsel=0, dacctl21=0, chippower=0, adcctl3=0;
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#define HANGUP_THRESHOLD_MS 2500 /* ouverture > seuil = vrai raccroché */
|
||||
|
||||
static hook_change_cb_t s_cb = NULL;
|
||||
static volatile bool s_offhook = false;
|
||||
static volatile bool s_offhook = false;
|
||||
static TaskHandle_t s_task = NULL;
|
||||
|
||||
/* slic_is_offhook() renvoie true si la boucle est fermée (combiné décroché,
|
||||
* cadran au repos). Pendant une impulsion, la boucle s'ouvre brièvement →
|
||||
@@ -98,9 +99,11 @@ static void hook_task(void *arg)
|
||||
|
||||
esp_err_t hook_monitor_start(hook_change_cb_t cb)
|
||||
{
|
||||
if (s_task != NULL) return ESP_ERR_INVALID_STATE;
|
||||
s_cb = cb;
|
||||
if (xTaskCreatePinnedToCore(hook_task, "hook", 3072, NULL, 6, NULL, 1) != pdPASS) {
|
||||
if (xTaskCreatePinnedToCore(hook_task, "hook", 4096, NULL, 6, &s_task, 1) != pdPASS) {
|
||||
ESP_LOGE(TAG, "échec création tâche hook");
|
||||
s_task = NULL;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES nvs_flash config_store hal_es8388 hal_i2s audio_router slic_ks0835 dialer
|
||||
REQUIRES nvs_flash config_store hal_es8388 hal_i2s audio_router slic_ks0835 dtmf dialer hook_monitor call_manager
|
||||
)
|
||||
|
||||
+10
-21
@@ -5,33 +5,22 @@
|
||||
#include "hal_i2s.h"
|
||||
#include "audio_router.h"
|
||||
#include "slic.h"
|
||||
#include "dialer.h"
|
||||
#include "call_manager.h"
|
||||
|
||||
static const char *TAG = "rtc_phone";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "RTC BL PHONE — socle ESP-IDF v5.4 (esp32 classique)");
|
||||
ESP_LOGI(TAG, "RTC BL PHONE — socle ESP-IDF v5.4");
|
||||
ESP_ERROR_CHECK(config_store_init());
|
||||
ESP_ERROR_CHECK(hal_i2s_init());
|
||||
/* SLIC init AVANT le dial tone pour eviter la contention GPIO/mutex
|
||||
* avec la tache tone (i2s_channel_write qui tourne en parallele). */
|
||||
ESP_ERROR_CHECK(slic_init());
|
||||
/* audio_router_init crée la tâche de tonalités SANS la démarrer.
|
||||
* call_manager déclenchera audio_router_dialtone_start() au décroché. */
|
||||
audio_router_init();
|
||||
/* Sonde RMS micro (valide chemin LIN2/RIN2) — a retirer en Task 6. */
|
||||
ESP_ERROR_CHECK(hal_i2s_capture_begin());
|
||||
int16_t cap[320];
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int64_t rms_sq = 0;
|
||||
int got = hal_i2s_capture_read_frame(cap, 320, &rms_sq);
|
||||
ESP_LOGI(TAG, "MIC sonde: offhook=%d frame=%d rms_sq=%lld",
|
||||
(int)slic_is_offhook(), got, (long long)rms_sq);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
audio_router_dialtone_start();
|
||||
ESP_LOGI(TAG, "tonalite de numerotation demarree (combine decroche pour ecouter)");
|
||||
ESP_LOGI(TAG, "boot OK — Phase 3 Task 2 capture micro hal_i2s");
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "heartbeat");
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
/* SLIC init AVANT le démarrage des tâches audio lourdes (ordre anti-deadlock GPIO). */
|
||||
ESP_ERROR_CHECK(slic_init());
|
||||
dialer_init();
|
||||
call_manager_start();
|
||||
ESP_LOGI(TAG, "téléphonie locale prête — décroche pour composer");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user