diff --git a/components/call_manager/CMakeLists.txt b/components/call_manager/CMakeLists.txt new file mode 100644 index 0000000..a4b22b8 --- /dev/null +++ b/components/call_manager/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "call_manager.c" + INCLUDE_DIRS "include" + REQUIRES hook_monitor dialer dtmf audio_router hal_i2s +) diff --git a/components/call_manager/call_manager.c b/components/call_manager/call_manager.c new file mode 100644 index 0000000..3de70bb --- /dev/null +++ b/components/call_manager/call_manager.c @@ -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 + +#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)"); +} diff --git a/components/call_manager/include/call_manager.h b/components/call_manager/include/call_manager.h new file mode 100644 index 0000000..ccf0ba8 --- /dev/null +++ b/components/call_manager/include/call_manager.h @@ -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 diff --git a/components/dtmf/CMakeLists.txt b/components/dtmf/CMakeLists.txt index ca22e23..a3d49d0 100644 --- a/components/dtmf/CMakeLists.txt +++ b/components/dtmf/CMakeLists.txt @@ -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 ) diff --git a/components/dtmf/dtmf.c b/components/dtmf/dtmf.c index bf3b518..b92b726 100644 --- a/components/dtmf/dtmf.c +++ b/components/dtmf/dtmf.c @@ -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; diff --git a/components/hal_es8388/es8388.c b/components/hal_es8388/es8388.c index 49501af..51f6f9c 100644 --- a/components/hal_es8388/es8388.c +++ b/components/hal_es8388/es8388.c @@ -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; diff --git a/components/hook_monitor/hook_monitor.c b/components/hook_monitor/hook_monitor.c index 8ad67da..aad758b 100644 --- a/components/hook_monitor/hook_monitor.c +++ b/components/hook_monitor/hook_monitor.c @@ -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; diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b5c8f3b..b8e76b5 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -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 ) diff --git a/main/app_main.c b/main/app_main.c index abfd4de..d4b9183 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -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"); }