feat(bt): pile HFP-HF + evenements

- composant bt_hfp : init Bluedroid Classic, GAP, esp_hf_client
- garde SSP retiree (pas de CONFIG_EXAMPLE_SSP_ENABLED dans ce projet)
- evenements haut niveau : SLC, appel entrant, audio SCO
- cable dans app_main apres call_manager_start
- build vert 1349 steps, boot sans panique confirme sur materiel
This commit is contained in:
clement
2026-06-19 13:06:19 +02:00
parent eb2d7fb860
commit 578ef4ffb2
5 changed files with 264 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
idf_component_register(
SRCS "bt_hfp.c"
INCLUDE_DIRS "include"
REQUIRES bt
)
+213
View File
@@ -0,0 +1,213 @@
/*
* bt_hfp.c — Bluetooth Classic HFP-HF (Bluedroid esp_hf_client)
*
* Corrections d'API par rapport au brief original :
* 1. Gardes #if CONFIG_EXAMPLE_SSP_ENABLED RETIRÉES — bloc SSP inconditionnel
* (CONFIG_EXAMPLE_SSP_ENABLED n'existe pas dans ce projet; SSP activé par défaut IDF).
* 2. ESP_BT_IO_CAP_IO est un uint8_t (#define = 1), pas une enum — cast explicite conservé.
* 3. Toutes les signatures vérifiées contre les headers réels IDF v5.4 :
* esp_bt_gap_set_scan_mode(c_mode, d_mode) — 2 args : OK
* esp_hf_client_reject_call(void) — 0 args : OK
* esp_bt_gap_ssp_confirm_reply(bda, bool) : OK
* esp_bt_gap_set_security_param(param_type, &val, len) : OK
*/
#include "bt_hfp.h"
#include <string.h>
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_bt_api.h"
#include "esp_hf_client_api.h"
#define TAG "bt_hfp"
static bt_hfp_event_cb_t s_cb = NULL;
static esp_bd_addr_t s_peer = {0};
static volatile bool s_slc = false;
static void emit(bt_hfp_event_t ev, const char *number, bool msbc)
{
if (s_cb) {
bt_hfp_event_data_t d = { .number = number, .msbc = msbc };
s_cb(ev, &d);
}
}
/* ---- GAP callback : appairage / authentification / SSP ---- */
static void gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
switch (event) {
case ESP_BT_GAP_AUTH_CMPL_EVT:
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(TAG, "appairage OK: %s", param->auth_cmpl.device_name);
} else {
ESP_LOGE(TAG, "appairage echoue: %d", param->auth_cmpl.stat);
}
break;
/*
* SSP (Simple Secure Pairing) — garde CONFIG_EXAMPLE_SSP_ENABLED RETIREE :
* ce projet n'a pas de Kconfig.projbuild d'exemple. SSP est actif par defaut
* en IDF; on accepte automatiquement la confirmation numerique.
*/
case ESP_BT_GAP_CFM_REQ_EVT:
ESP_LOGI(TAG, "SSP CFM_REQ — acceptation automatique du code %"PRIu32,
param->cfm_req.num_val);
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
break;
case ESP_BT_GAP_KEY_NOTIF_EVT:
ESP_LOGI(TAG, "SSP passkey: %"PRIu32, param->key_notif.passkey);
break;
case ESP_BT_GAP_KEY_REQ_EVT:
ESP_LOGI(TAG, "SSP passkey requis");
break;
case ESP_BT_GAP_PIN_REQ_EVT:
if (param->pin_req.min_16_digit) {
ESP_LOGI(TAG, "PIN_REQ 16 chiffres");
esp_bt_pin_code_t pin16 = {
'0','0','0','0','0','0','0','0',
'0','0','0','0','0','0','0','0'
};
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin16);
} else {
ESP_LOGI(TAG, "PIN_REQ 4 chiffres");
esp_bt_pin_code_t pin4 = { '0','0','0','0' };
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin4);
}
break;
default:
break;
}
}
/* ---- HFP-HF callback : traduit les evenements bas-niveau ---- */
static void hf_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param)
{
switch (event) {
case ESP_HF_CLIENT_CONNECTION_STATE_EVT:
if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED) {
memcpy(s_peer, param->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
s_slc = true;
ESP_LOGI(TAG, "SLC connecte");
emit(BT_HFP_EV_SLC_CONNECTED, NULL, false);
} else if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_DISCONNECTED) {
s_slc = false;
ESP_LOGI(TAG, "SLC deconnecte");
emit(BT_HFP_EV_SLC_DISCONNECTED, NULL, false);
}
break;
case ESP_HF_CLIENT_CIND_CALL_SETUP_EVT:
if (param->call_setup.status == ESP_HF_CALL_SETUP_STATUS_INCOMING) {
ESP_LOGI(TAG, "appel entrant (CIND)");
emit(BT_HFP_EV_INCOMING, NULL, false);
}
break;
case ESP_HF_CLIENT_CLIP_EVT:
ESP_LOGI(TAG, "numero appelant: %s",
param->clip.number ? param->clip.number : "?");
emit(BT_HFP_EV_INCOMING, param->clip.number, false);
break;
case ESP_HF_CLIENT_CIND_CALL_EVT:
if (param->call.status == ESP_HF_CALL_STATUS_CALL_IN_PROGRESS) {
ESP_LOGI(TAG, "appel actif");
emit(BT_HFP_EV_CALL_ACTIVE, NULL, false);
} else {
ESP_LOGI(TAG, "fin d'appel");
emit(BT_HFP_EV_CALL_ENDED, NULL, false);
}
break;
case ESP_HF_CLIENT_AUDIO_STATE_EVT:
if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED ||
param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) {
bool msbc = (param->audio_stat.state ==
ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC);
ESP_LOGI(TAG, "audio SCO connecte (%s)", msbc ? "mSBC 16k" : "CVSD 8k");
emit(BT_HFP_EV_AUDIO_CONNECTED, NULL, msbc);
} else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED) {
ESP_LOGI(TAG, "audio SCO deconnecte");
emit(BT_HFP_EV_AUDIO_DISCONNECTED, NULL, false);
}
break;
default:
break;
}
}
/* ---- API publique ---- */
esp_err_t bt_hfp_init(bt_hfp_event_cb_t cb)
{
s_cb = cb;
/* Liberer la RAM reservee pour le BLE : on n'utilise que Classic BT. */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "controller_init: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "controller_enable: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "bluedroid_init: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "bluedroid_enable: %s", esp_err_to_name(ret));
return ret;
}
ESP_ERROR_CHECK(esp_bt_gap_set_device_name("RTC_BL_PHONE"));
ESP_ERROR_CHECK(esp_bt_gap_register_callback(gap_cb));
ESP_ERROR_CHECK(esp_hf_client_register_callback(hf_cb));
ESP_ERROR_CHECK(esp_hf_client_init());
/*
* SSP : configuration du mode IO. Garde CONFIG_EXAMPLE_SSP_ENABLED RETIREE.
* ESP_BT_IO_CAP_IO (= 1, uint8_t) = DisplayYesNo — l'ESP affiche un code,
* l'utilisateur confirme sur le mobile. Acceptation automatique dans gap_cb.
*/
esp_bt_sp_param_t pt = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
ESP_ERROR_CHECK(esp_bt_gap_set_security_param(pt, &iocap, sizeof(uint8_t)));
/* PIN legacy (fallback pour appareils anciens). */
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
esp_bt_pin_code_t pin_code = { '0', '0', '0', '0' };
esp_bt_gap_set_pin(pin_type, 4, pin_code);
/* Rendre le peripherique connectable ET decouvrable. */
ESP_ERROR_CHECK(
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE)
);
ESP_LOGI(TAG, "bt_hfp pret — appairer le mobile sur 'RTC_BL_PHONE'");
return ESP_OK;
}
esp_err_t bt_hfp_dial(const char *number) { return esp_hf_client_dial(number); }
esp_err_t bt_hfp_answer(void) { return esp_hf_client_answer_call(); }
esp_err_t bt_hfp_hangup(void) { return esp_hf_client_reject_call(); }
esp_err_t bt_hfp_send_dtmf(char code) { return esp_hf_client_send_dtmf(code); }
bool bt_hfp_slc_connected(void) { return s_slc; }
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
BT_HFP_EV_SLC_CONNECTED,
BT_HFP_EV_SLC_DISCONNECTED,
BT_HFP_EV_INCOMING,
BT_HFP_EV_CALL_ACTIVE,
BT_HFP_EV_CALL_ENDED,
BT_HFP_EV_AUDIO_CONNECTED,
BT_HFP_EV_AUDIO_DISCONNECTED,
} bt_hfp_event_t;
typedef struct { const char *number; bool msbc; } bt_hfp_event_data_t;
typedef void (*bt_hfp_event_cb_t)(bt_hfp_event_t ev, const bt_hfp_event_data_t *data);
/* Init Bluetooth Classic + Bluedroid + HFP-HF. cb receives high-level events. */
esp_err_t bt_hfp_init(bt_hfp_event_cb_t cb);
esp_err_t bt_hfp_dial(const char *number); /* ATD */
esp_err_t bt_hfp_answer(void); /* ATA */
esp_err_t bt_hfp_hangup(void); /* AT+CHUP */
esp_err_t bt_hfp_send_dtmf(char code);
bool bt_hfp_slc_connected(void);
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -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 dtmf dialer hook_monitor call_manager
REQUIRES nvs_flash config_store hal_es8388 hal_i2s audio_router slic_ks0835 dtmf dialer hook_monitor call_manager bt_hfp
)
+12 -1
View File
@@ -7,9 +7,18 @@
#include "slic.h"
#include "dialer.h"
#include "call_manager.h"
#include "bt_hfp.h"
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");
@@ -22,5 +31,7 @@ void app_main(void)
audio_router_init();
dialer_init();
call_manager_start();
ESP_LOGI(TAG, "téléphonie locale prête — décroche pour composer");
/* 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");
}