From 2bf8c40611f5b73ba96e43821f66cfb3be7fb0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:42:13 +0100 Subject: [PATCH] Harden HFP call controls and runtime peer configuration --- README.md | 47 +++-- platformio.ini | 13 +- src/main.cpp | 510 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 411 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index 7c1971c..7d9c827 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,38 @@ # RTC_BL_PHONE -Projet PlatformIO ESP32 pour recycler un téléphone RTC ancien (combiné, clavier, hook). +Projet PlatformIO ESP32 pour recycler un téléphone RTC ancien (combiné, clavier, hook), avec intégration Bluetooth HFP pour les appels (émission/réception). ## Démarrage rapide 1. Ouvrir le dossier dans PlatformIO. -2. Compiler et flasher l'environnement `esp32-s3-devkitc-1` (par défaut). -3. Ouvrir le moniteur série à 115200 bauds. -4. Utiliser les commandes série (`h`, `r`, `o`, `d`, `c`) pour piloter la machine d'états. +2. Option A: renseigner l'adresse MAC dans `src/main.cpp` (`DEFAULT_PEER_ADDR`). +3. Option B: la définir au runtime avec la commande série `p `. +4. Compiler et flasher l'environnement `esp32dev` (par défaut). +5. Ouvrir le moniteur série à 115200 bauds. +6. Connecter puis piloter les appels via commandes série. + +## Commandes série +- `h` : aide +- `s` : statut runtime (hook, HFP, audio, call) +- `p ` : configure la MAC du téléphone (`AA:BB:CC:DD:EE:FF`) +- `b` : connexion HFP vers le téléphone (Audio Gateway) +- `x` : déconnexion HFP +- `m ` : émission d'appel +- `a` : décrocher un appel entrant +- `e` : raccrocher / rejeter +- `v <0..15>` : volume speaker HFP + +## Cibles matérielles +- **ESP32 (Classic BT)** : support HFP complet (`esp32dev`). +- **ESP32-S3** : Bluetooth Classic non supporté par le silicium, HFP indisponible (le firmware reste compilable avec messages de fallback). + +## Comportement hook/ring +- Si combiné **raccroché** (`ON_HOOK`) : ligne coupée. +- Si appel entrant : `pinRingCmd` activé, sonnerie pilotable côté AG1171S. +- Si décroché pendant sonnerie : `answer` automatique. +- Si raccroché pendant appel : `end/reject` automatique. ## Choix de cartes ESP32 Voir `docs/solutions_rtc_phone_esp32.md` pour la shortlist des DevKit utilisables (ESP32-DevKitC, ESP32-S3-DevKitC-1, NodeMCU-32S, LOLIN32), les liens de référence web, et les solutions d’interface (direct combiné/clavier, SLIC/FXS, ATA externe), dont une variante AG1171S (Silvertel). ## Plan projet (chef de projet) Voir `docs/plan_chef_projet_esp32s3_ag1171s.md` pour le planning en phases, les risques, les critères d'acceptation et les livrables de la version ESP32-S3 + AG1171S. - -## Contenu -- `platformio.ini`: configuration multi-env (ESP32-S3 par défaut + ESP32 legacy). -- `src/main.cpp`: squelette firmware machine d'états pour intégration AG1171S. -- `docs/solutions_rtc_phone_esp32.md`: comparaison des architectures et recommandations. -- `docs/plan_chef_projet_esp32s3_ag1171s.md`: plan d'exécution projet version V0.1. -2. Compiler et flasher l'environnement `esp32dev`. -3. Ouvrir le moniteur série à 115200 bauds. - -## Choix de cartes ESP32 -Voir `docs/solutions_rtc_phone_esp32.md` pour la shortlist des DevKit utilisables (ESP32-DevKitC, ESP32-S3-DevKitC-1, NodeMCU-32S, LOLIN32) et les liens de référence web. - -## Contenu -- `platformio.ini`: configuration initiale du projet. -- `src/main.cpp`: prototype minimal (détection décroché/raccroché). -- `docs/solutions_rtc_phone_esp32.md`: comparaison des meilleures architectures + sélection de DevKit ESP32 recommandés. diff --git a/platformio.ini b/platformio.ini index bd79eb8..0a919ca 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,22 +1,17 @@ [platformio] -default_envs = esp32-s3-devkitc-1 +default_envs = esp32dev [env] platform = espressif32 -default_envs = esp32dev - -[env:esp32dev] -platform = espressif32 -board = esp32dev framework = arduino monitor_speed = 115200 build_flags = -DCORE_DEBUG_LEVEL=1 -[env:esp32-s3-devkitc-1] -board = esp32-s3-devkitc-1 - [env:esp32dev] board = esp32dev lib_deps = bblanchon/ArduinoJson@^7.0.4 + +[env:esp32-s3-devkitc-1] +board = esp32-s3-devkitc-1 diff --git a/src/main.cpp b/src/main.cpp index 1b6ed2f..f360595 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,217 +1,467 @@ #include +#include + +#include "esp_bt.h" +#include "esp_bt_device.h" +#include "esp_bt_main.h" +#include "esp_err.h" +#include "esp_hf_client_api.h" +#include "soc/soc_caps.h" + /* - RTC_BL_PHONE - ESP32-S3 + AG1171S control skeleton + RTC_BL_PHONE - ESP32 + AG1171S + Bluetooth HFP - Goal: - - Provide a practical firmware base for a private analog line project. - - Keep telephony analog front-end on AG1171S side. - - Let ESP32 manage call-state logic and debug observability. + Livrable: + - Appel sortant / entrant via HFP + - Gestion hook (décroché / raccroché) + - Pilotage ring/line enable pour combiné RTC - Notes: - - Pin mapping must be validated against your AG1171S application schematic. - - Never connect to PSTN without compliant isolation/protection design. + Note matériel: + - HFP nécessite Bluetooth Classic => ESP32 (pas ESP32-S3) */ enum class PhoneState : uint8_t { ON_HOOK, - OFF_HOOK, + IDLE, + RINGING, DIALING, IN_CALL, - RINGING }; struct PhonePins { - uint8_t pinHookSense; - uint8_t pinRingCmd; - uint8_t pinLineEnable; - uint8_t pinLed; + uint8_t hookSense; + uint8_t ringCmd; + uint8_t lineEnable; + uint8_t led; }; #if CONFIG_IDF_TARGET_ESP32S3 -constexpr PhonePins PINS{ - .pinHookSense = 4, // AG1171S hook/off-hook indication input to ESP32-S3 - .pinRingCmd = 5, // ESP32-S3 output: request ring cadence generator/enable - .pinLineEnable = 6, // ESP32-S3 output: line feed enable (through safe driver) - .pinLed = 48 // ESP32-S3 DevKitC-1 RGB/Status-compatible GPIO -}; +constexpr PhonePins PINS{.hookSense = 4, .ringCmd = 5, .lineEnable = 6, .led = 48}; #else -constexpr PhonePins PINS{ - .pinHookSense = 27, - .pinRingCmd = 26, - .pinLineEnable = 25, - .pinLed = 2 -}; +constexpr PhonePins PINS{.hookSense = 27, .ringCmd = 26, .lineEnable = 25, .led = 2}; #endif constexpr uint32_t SERIAL_BAUD = 115200; constexpr uint32_t DEBOUNCE_MS = 25; +constexpr char DEVICE_NAME[] = "RTC_BL_PHONE"; +constexpr char DEFAULT_PEER_ADDR[] = "00:00:00:00:00:00"; // A remplacer ou via commande "p ". PhoneState g_state = PhoneState::ON_HOOK; bool g_hookOffHook = false; +bool g_hfpReady = false; +bool g_hfpConnected = false; +bool g_audioConnected = false; +bool g_callActive = false; +bool g_callIncoming = false; +bool g_callSetupOutgoing = false; uint32_t g_lastHookEdgeMs = 0; +String g_serialLine; +String g_peerAddrString = DEFAULT_PEER_ADDR; +esp_bd_addr_t g_peerAddr = {0}; const char* stateToString(PhoneState state) { switch (state) { case PhoneState::ON_HOOK: return "ON_HOOK"; - case PhoneState::OFF_HOOK: return "OFF_HOOK"; + case PhoneState::IDLE: return "IDLE"; + case PhoneState::RINGING: return "RINGING"; case PhoneState::DIALING: return "DIALING"; case PhoneState::IN_CALL: return "IN_CALL"; - case PhoneState::RINGING: return "RINGING"; } - return "UNKNOWN"; } -void setState(PhoneState newState) { - if (newState == g_state) { - return; +bool parseBdAddr(const char* mac, esp_bd_addr_t out) { + unsigned int v[6] = {0}; + if (sscanf(mac, "%x:%x:%x:%x:%x:%x", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6) { + return false; } - g_state = newState; - Serial.printf("[RTC_PHONE] state=%s\n", stateToString(g_state)); + for (int i = 0; i < 6; ++i) { + if (v[i] > 0xFF) { + return false; + } + out[i] = static_cast(v[i]); + } + return true; +} +bool updatePeerAddr(const String& addr) { + esp_bd_addr_t parsed = {0}; + if (!parseBdAddr(addr.c_str(), parsed)) { + return false; + } + memcpy(g_peerAddr, parsed, sizeof(g_peerAddr)); + g_peerAddrString = addr; + return true; +} + +bool isPeerAddrConfigured() { + return g_peerAddrString != DEFAULT_PEER_ADDR; +} + +void applyOutputsForState() { switch (g_state) { case PhoneState::ON_HOOK: - digitalWrite(PINS.pinLineEnable, LOW); - digitalWrite(PINS.pinRingCmd, LOW); - digitalWrite(PINS.pinLed, LOW); + digitalWrite(PINS.lineEnable, LOW); + digitalWrite(PINS.ringCmd, LOW); + digitalWrite(PINS.led, LOW); break; - - case PhoneState::OFF_HOOK: - digitalWrite(PINS.pinLineEnable, HIGH); - digitalWrite(PINS.pinRingCmd, LOW); - digitalWrite(PINS.pinLed, HIGH); + case PhoneState::IDLE: + digitalWrite(PINS.lineEnable, HIGH); + digitalWrite(PINS.ringCmd, LOW); + digitalWrite(PINS.led, HIGH); break; - - case PhoneState::DIALING: - digitalWrite(PINS.pinLineEnable, HIGH); - digitalWrite(PINS.pinRingCmd, LOW); - digitalWrite(PINS.pinLed, HIGH); - break; - - case PhoneState::IN_CALL: - digitalWrite(PINS.pinLineEnable, HIGH); - digitalWrite(PINS.pinRingCmd, LOW); - digitalWrite(PINS.pinLed, HIGH); - break; - case PhoneState::RINGING: - digitalWrite(PINS.pinLineEnable, HIGH); - digitalWrite(PINS.pinRingCmd, HIGH); - digitalWrite(PINS.pinLed, HIGH); + digitalWrite(PINS.lineEnable, HIGH); + digitalWrite(PINS.ringCmd, HIGH); + digitalWrite(PINS.led, HIGH); break; + case PhoneState::DIALING: + case PhoneState::IN_CALL: + digitalWrite(PINS.lineEnable, HIGH); + digitalWrite(PINS.ringCmd, LOW); + digitalWrite(PINS.led, HIGH); + break; + } +} + +void setState(PhoneState newState) { + if (g_state == newState) { + return; + } + g_state = newState; + applyOutputsForState(); + Serial.printf("[RTC_PHONE] state=%s\n", stateToString(g_state)); +} + +void refreshPhoneState() { + if (!g_hookOffHook) { + setState(PhoneState::ON_HOOK); + } else if (g_callIncoming && !g_callActive) { + setState(PhoneState::RINGING); + } else if (g_callSetupOutgoing && !g_callActive) { + setState(PhoneState::DIALING); + } else if (g_callActive) { + setState(PhoneState::IN_CALL); + } else { + setState(PhoneState::IDLE); } } void printHelp() { - Serial.println("[RTC_PHONE] Commands:"); - Serial.println(" h -> help"); - Serial.println(" r -> enter RINGING"); - Serial.println(" o -> force ON_HOOK"); - Serial.println(" d -> force DIALING"); - Serial.println(" c -> force IN_CALL"); + Serial.println("[RTC_PHONE] Commandes:"); + Serial.println(" h -> help"); + Serial.println(" s -> status"); + Serial.println(" p -> set peer MAC HFP (AA:BB:CC:DD:EE:FF)"); + Serial.println(" b -> connect HFP AG"); + Serial.println(" x -> disconnect HFP AG"); + Serial.println(" a -> answer incoming"); + Serial.println(" e -> end/reject call"); + Serial.println(" m -> dial number"); + Serial.println(" v <0..15> -> set speaker volume"); +} + +void printStatus() { + Serial.printf("[RTC_PHONE] hook=%s state=%s ready=%s hfp=%s audio=%s incoming=%s outgoing=%s active=%s peer=%s\n", + g_hookOffHook ? "OFF_HOOK" : "ON_HOOK", stateToString(g_state), g_hfpReady ? "YES" : "NO", + g_hfpConnected ? "YES" : "NO", g_audioConnected ? "YES" : "NO", g_callIncoming ? "YES" : "NO", + g_callSetupOutgoing ? "YES" : "NO", g_callActive ? "YES" : "NO", g_peerAddrString.c_str()); +} + +#if SOC_BT_CLASSIC_SUPPORTED +void onCallTerminatedByState() { + if (!g_callActive && !g_callSetupOutgoing) { + g_audioConnected = false; + } +} + +void hfpCallback(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t* param) { + switch (event) { + case ESP_HF_CLIENT_CONNECTION_STATE_EVT: + g_hfpConnected = (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED); + if (!g_hfpConnected) { + g_audioConnected = false; + } + Serial.printf("[HFP] conn_state=%d\n", param->conn_stat.state); + break; + + case ESP_HF_CLIENT_AUDIO_STATE_EVT: + g_audioConnected = (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED || + param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC); + Serial.printf("[HFP] audio_state=%d\n", param->audio_stat.state); + break; + + case ESP_HF_CLIENT_RING_IND_EVT: + g_callIncoming = true; + Serial.println("[HFP] incoming ring"); + break; + + case ESP_HF_CLIENT_CALL_IND_EVT: + g_callActive = (param->call.ind != 0); + Serial.printf("[HFP] call=%d\n", param->call.ind); + onCallTerminatedByState(); + break; + + case ESP_HF_CLIENT_CALL_SETUP_IND_EVT: + g_callSetupOutgoing = (param->call_setup.status == ESP_HF_CALL_SETUP_STATUS_OUTGOING_DIALING || + param->call_setup.status == ESP_HF_CALL_SETUP_STATUS_OUTGOING_ALERTING); + if (param->call_setup.status == ESP_HF_CALL_SETUP_STATUS_INCOMING) { + g_callIncoming = true; + } else if (param->call_setup.status == ESP_HF_CALL_SETUP_STATUS_IDLE) { + g_callIncoming = false; + } + Serial.printf("[HFP] call_setup=%d\n", param->call_setup.status); + onCallTerminatedByState(); + break; + + case ESP_HF_CLIENT_CLIP_EVT: + if (param->clip.number) { + Serial.printf("[HFP] caller=%s\n", param->clip.number); + } + break; + + default: + break; + } + + refreshPhoneState(); +} + +bool initHfp() { + if (!updatePeerAddr(g_peerAddrString)) { + Serial.println("[HFP] MAC invalide. Utilisez: p AA:BB:CC:DD:EE:FF"); + return false; + } + + esp_err_t err = esp_bt_controller_mem_release(ESP_BT_MODE_BLE); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] mem_release failed: %s\n", esp_err_to_name(err)); + return false; + } + + const esp_bt_controller_config_t btCfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + err = esp_bt_controller_init(&btCfg); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] bt_controller_init failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] bt_controller_enable failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_bluedroid_init(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] bluedroid_init failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_bluedroid_enable(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] bluedroid_enable failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_bt_dev_set_device_name(DEVICE_NAME); + if (err != ESP_OK) { + Serial.printf("[HFP] set_device_name failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_hf_client_register_callback(hfpCallback); + if (err != ESP_OK) { + Serial.printf("[HFP] register_callback failed: %s\n", esp_err_to_name(err)); + return false; + } + + err = esp_hf_client_init(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + Serial.printf("[HFP] init failed: %s\n", esp_err_to_name(err)); + return false; + } + + g_hfpReady = true; + Serial.println("[HFP] stack ready"); + if (!isPeerAddrConfigured()) { + Serial.println("[HFP] ATTENTION: configurez le peer avec p avant b"); + } + return true; +} + +void connectHfp() { + if (!g_hfpReady) { + Serial.println("[HFP] stack non initialisee"); + return; + } + if (!isPeerAddrConfigured()) { + Serial.println("[HFP] peer MAC non configure. Utilisez: p "); + return; + } + const esp_err_t err = esp_hf_client_connect(g_peerAddr); + Serial.printf("[HFP] connect -> %s\n", esp_err_to_name(err)); +} + +void disconnectHfp() { + if (!g_hfpReady) { + Serial.println("[HFP] stack non initialisee"); + return; + } + const esp_err_t err = esp_hf_client_disconnect(g_peerAddr); + Serial.printf("[HFP] disconnect -> %s\n", esp_err_to_name(err)); +} + +void answerCall() { + if (!g_hfpConnected || !g_callIncoming) { + Serial.println("[HFP] aucun appel entrant a decrocher"); + return; + } + const esp_err_t err = esp_hf_client_answer_call(); + Serial.printf("[HFP] answer -> %s\n", esp_err_to_name(err)); +} + +void endCall() { + if (!g_hfpConnected) { + Serial.println("[HFP] non connecte"); + return; + } + + esp_err_t err = ESP_FAIL; + if (g_callIncoming && !g_callActive) { + err = esp_hf_client_reject_call(); + Serial.printf("[HFP] reject -> %s\n", esp_err_to_name(err)); + } else { + err = esp_hf_client_terminate_call(); + Serial.printf("[HFP] terminate -> %s\n", esp_err_to_name(err)); + } +} + +void dialNumber(const String& number) { + if (!g_hfpConnected) { + Serial.println("[HFP] non connecte"); + return; + } + if (number.length() == 0) { + Serial.println("[HFP] numero vide"); + return; + } + const esp_err_t err = esp_hf_client_dial(number.c_str()); + Serial.printf("[HFP] dial(%s) -> %s\n", number.c_str(), esp_err_to_name(err)); +} + +void setSpeakerVolume(int value) { + if (!g_hfpConnected) { + Serial.println("[HFP] non connecte"); + return; + } + const int clipped = constrain(value, 0, 15); + const esp_err_t err = esp_hf_client_volume_update(ESP_HF_VOLUME_CONTROL_TARGET_SPK, clipped); + Serial.printf("[HFP] volume=%d -> %s\n", clipped, esp_err_to_name(err)); +} +#else +bool initHfp() { + Serial.println("[HFP] indisponible: cible sans Bluetooth Classic (ex: ESP32-S3)"); + return false; +} + +void connectHfp() { Serial.println("[HFP] non supporte sur cette cible"); } +void disconnectHfp() { Serial.println("[HFP] non supporte sur cette cible"); } +void answerCall() { Serial.println("[HFP] non supporte sur cette cible"); } +void endCall() { Serial.println("[HFP] non supporte sur cette cible"); } +void dialNumber(const String&) { Serial.println("[HFP] non supporte sur cette cible"); } +void setSpeakerVolume(int) { Serial.println("[HFP] non supporte sur cette cible"); } +#endif + +void executeCommand(const String& line) { + if (line == "h") { + printHelp(); + } else if (line == "s") { + printStatus(); + } else if (line == "b") { + connectHfp(); + } else if (line == "x") { + disconnectHfp(); + } else if (line == "a") { + answerCall(); + } else if (line == "e") { + endCall(); + } else if (line.startsWith("m ")) { + dialNumber(line.substring(2)); + } else if (line.startsWith("v ")) { + setSpeakerVolume(line.substring(2).toInt()); + } else if (line.startsWith("p ")) { + const String mac = line.substring(2); + if (updatePeerAddr(mac)) { + Serial.printf("[HFP] peer configure: %s\n", g_peerAddrString.c_str()); + } else { + Serial.println("[HFP] format MAC invalide. Ex: AA:BB:CC:DD:EE:FF"); + } + } else if (!line.isEmpty()) { + Serial.printf("[RTC_PHONE] commande inconnue: %s\n", line.c_str()); + } } void handleSerialCommands() { while (Serial.available() > 0) { - const char cmd = static_cast(Serial.read()); - - switch (cmd) { - case 'h': - printHelp(); - break; - case 'r': - setState(PhoneState::RINGING); - break; - case 'o': - setState(PhoneState::ON_HOOK); - break; - case 'd': - setState(PhoneState::DIALING); - break; - case 'c': - setState(PhoneState::IN_CALL); - break; - default: - break; + const char c = static_cast(Serial.read()); + if (c == '\n' || c == '\r') { + executeCommand(g_serialLine); + g_serialLine = ""; + } else { + g_serialLine += c; } } } void updateHookState() { - const bool rawOffHook = (digitalRead(PINS.pinHookSense) == LOW); + const bool rawOffHook = (digitalRead(PINS.hookSense) == LOW); const uint32_t nowMs = millis(); if (rawOffHook != g_hookOffHook && (nowMs - g_lastHookEdgeMs) > DEBOUNCE_MS) { g_lastHookEdgeMs = nowMs; g_hookOffHook = rawOffHook; - Serial.printf("[RTC_PHONE] hook=%s\n", g_hookOffHook ? "OFF_HOOK" : "ON_HOOK"); - if (g_hookOffHook) { - setState(PhoneState::OFF_HOOK); - } else { - setState(PhoneState::ON_HOOK); + if (!g_hookOffHook && (g_callActive || g_callSetupOutgoing || g_callIncoming)) { + endCall(); + } else if (g_hookOffHook && g_callIncoming && !g_callActive) { + answerCall(); } + + refreshPhoneState(); } } void setup() { Serial.begin(SERIAL_BAUD); - pinMode(PINS.pinHookSense, INPUT_PULLUP); - pinMode(PINS.pinRingCmd, OUTPUT); - pinMode(PINS.pinLineEnable, OUTPUT); - pinMode(PINS.pinLed, OUTPUT); + pinMode(PINS.hookSense, INPUT_PULLUP); + pinMode(PINS.ringCmd, OUTPUT); + pinMode(PINS.lineEnable, OUTPUT); + pinMode(PINS.led, OUTPUT); - digitalWrite(PINS.pinRingCmd, LOW); - digitalWrite(PINS.pinLineEnable, LOW); - digitalWrite(PINS.pinLed, LOW); + digitalWrite(PINS.ringCmd, LOW); + digitalWrite(PINS.lineEnable, LOW); + digitalWrite(PINS.led, LOW); Serial.println("\n[RTC_PHONE] Boot OK"); #if CONFIG_IDF_TARGET_ESP32S3 Serial.println("[RTC_PHONE] Target: ESP32-S3"); #else - Serial.println("[RTC_PHONE] Target: ESP32 (legacy mapping)"); + Serial.println("[RTC_PHONE] Target: ESP32"); #endif - Serial.println("[RTC_PHONE] Profile: AG1171S control skeleton"); - printHelp(); + Serial.println("[RTC_PHONE] Profile: AG1171S + Bluetooth HFP"); - setState(PhoneState::ON_HOOK); + printHelp(); + g_hookOffHook = (digitalRead(PINS.hookSense) == LOW); + initHfp(); + refreshPhoneState(); } void loop() { updateHookState(); handleSerialCommands(); delay(10); -// Prototype minimal pour valider le câblage d'un téléphone RTC recyclé. -// - Hook switch: détection décroché/raccroché -// - Clavier: lecture en matrice (adaptateur nécessaire selon le modèle) -// - Audio: à implémenter ensuite via codec I2S / interface analogique dédiée - -constexpr uint8_t PIN_HOOK = 27; // Adapter selon le câblage réel -constexpr uint8_t PIN_LED = 2; // LED status carte - -bool offHook = false; - -void setup() { - Serial.begin(115200); - pinMode(PIN_HOOK, INPUT_PULLUP); - pinMode(PIN_LED, OUTPUT); - digitalWrite(PIN_LED, LOW); - - Serial.println("\n[RTC_PHONE] Boot OK"); - Serial.println("[RTC_PHONE] MVP: hook + logique d'etat"); -} - -void loop() { - bool hookState = digitalRead(PIN_HOOK) == LOW; // LOW = décroché (exemple) - if (hookState != offHook) { - offHook = hookState; - digitalWrite(PIN_LED, offHook ? HIGH : LOW); - Serial.printf("[RTC_PHONE] Etat combine: %s\n", offHook ? "DECROCHE" : "RACCROCHE"); - } - - delay(20); } -- 2.54.0