Harden HFP call controls and add runtime peer MAC configuration #3
@@ -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 <mac>`.
|
||||
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 <mac>` : 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 <numero>` : é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.
|
||||
|
||||
+4
-9
@@ -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
|
||||
|
||||
+380
-130
@@ -1,217 +1,467 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#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 <mac>".
|
||||
|
||||
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<uint8_t>(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 <mac> -> 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 <number> -> 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 <mac> 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 <mac>");
|
||||
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<char>(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<char>(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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user