feat: chain wifi webui espnow and bt track execution
This commit is contained in:
@@ -116,8 +116,21 @@
|
||||
<input type="text" id="btHfpAddr" placeholder="MAC téléphone HFP (AA:BB:CC:DD:EE:FF)">
|
||||
<button type="submit">HFP Connect</button>
|
||||
</form>
|
||||
<form id="btDialForm">
|
||||
<input type="text" id="btDialNumber" placeholder="Numéro (ex: 0612345678)">
|
||||
<button type="submit">Dial</button>
|
||||
</form>
|
||||
<div class="inline-actions">
|
||||
<button id="btHfpDisconnectBtn">HFP Disconnect</button>
|
||||
<button id="btRedialBtn">Redial</button>
|
||||
<button id="btAnswerBtn">Answer</button>
|
||||
<button id="btHangupBtn">Hangup</button>
|
||||
<button id="btCallsBtn">Calls Query</button>
|
||||
</div>
|
||||
<div class="inline-actions">
|
||||
<button id="btDiscoverableOnBtn">Discoverable ON</button>
|
||||
<button id="btDiscoverableOffBtn">Discoverable OFF</button>
|
||||
<button id="btPbapSyncBtn">PBAP Sync</button>
|
||||
<button id="btBleStartBtn">BLE Start</button>
|
||||
<button id="btBleStopBtn">BLE Stop</button>
|
||||
<button id="btRefreshBtn">Rafraîchir BT</button>
|
||||
|
||||
+118
-1
@@ -80,9 +80,12 @@ async function refreshStatus() {
|
||||
const wifiState = status.wifi?.state || "n/a";
|
||||
const mqttConnected = status.mqtt?.connected ? "on" : "off";
|
||||
const peers = status.espnow?.peer_count ?? 0;
|
||||
const btCallState = status.bluetooth?.call_state || "n/a";
|
||||
const btConnected = status.bluetooth?.connected ? "on" : "off";
|
||||
const pbapSupported = status.bluetooth?.pbap_supported ? "yes" : "no";
|
||||
line.textContent =
|
||||
`board=${status.board_profile || "n/a"} telephony=${telephonyState} hook=${hook} ` +
|
||||
`wifi=${wifiState} mqtt=${mqttConnected} espnow_peers=${peers}`;
|
||||
`wifi=${wifiState} mqtt=${mqttConnected} espnow_peers=${peers} bt=${btConnected} bt_call=${btCallState} pbap=${pbapSupported}`;
|
||||
setJson("statusJson", status);
|
||||
} catch (error) {
|
||||
line.textContent = `Erreur statut: ${error.message}`;
|
||||
@@ -424,6 +427,120 @@ function bindEvents() {
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btDialForm").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const number = document.getElementById("btDialNumber").value.trim();
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/hfp/dial", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: JSON.stringify({ number }),
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btRedialBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/hfp/redial", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btAnswerBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/hfp/answer", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btHangupBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/hfp/hangup", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btCallsBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/hfp/calls", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btDiscoverableOnBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/discoverable/on", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btDiscoverableOffBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/discoverable/off", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btPbapSyncBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/pbap/sync", {
|
||||
method: "POST",
|
||||
headers: jsonHeaders(),
|
||||
body: "{}",
|
||||
});
|
||||
setJson("actionResult", result);
|
||||
await Promise.all([refreshBluetooth(), refreshStatus()]);
|
||||
} catch (error) {
|
||||
setJson("actionResult", { error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btBleStartBtn").addEventListener("click", async () => {
|
||||
try {
|
||||
const result = await requestJson("/api/bluetooth/ble/start", {
|
||||
|
||||
@@ -38,6 +38,8 @@ Chaque fiche détaille : rôle, API, notifications, points de validation, synerg
|
||||
| WebUI | plan_webui.md |
|
||||
| Spec WebUI route parity | spec_webui_route_parity_and_coverage_v1.md |
|
||||
| Spec BT HFP/PBAP dial | spec_bt_hfp_pbap_dialing_v1.md |
|
||||
| API ESP-NOW v1 | espnow_api_v1.md |
|
||||
| Faisabilité migration PBAP | bt_pbap_migration_feasibility.md |
|
||||
| État des specs | SPECS_STATE.md |
|
||||
|
||||
## Procédures QA & hardware
|
||||
|
||||
@@ -44,3 +44,47 @@
|
||||
- Écart documentation / exécution (doD non uniformément tracée)
|
||||
- Dépendances CI sur les branches de support non conservées (si nettoyage de branches engagé)
|
||||
- Blocs matériels/firmware S3 (liens Bluetooth/HFP) pouvant masquer la visibilité produit
|
||||
|
||||
## 6) Enchaînement Track A / Track B (Bluetooth, WiFi, WebUI, ESP-NOW)
|
||||
|
||||
### Track A — Delivery rapide (état courant)
|
||||
|
||||
- WiFi:
|
||||
- endpoints Web/API présents: `/api/network/wifi`, `/connect`, `/disconnect`, `/reconnect`, `/scan`.
|
||||
- fallback AP actif en perte STA (`state=ap_fallback`).
|
||||
- WebUI Bluetooth:
|
||||
- endpoints étendus pour discoverable, dial/redial/answer/hangup/calls, pbap sync.
|
||||
- affichage états BT enrichis (`call_state`, `slc_connected`, `pbap_supported`).
|
||||
- ESP-NOW:
|
||||
- protocole v1 documenté (`docs/espnow_api_v1.md`).
|
||||
- compat legacy conservée (`cmd/raw/command/action`).
|
||||
- Bluetooth HFP:
|
||||
- commandes d'appel opérationnelles.
|
||||
- PBAP toujours `unsupported` sur stack actuelle (comportement explicitement tracé).
|
||||
|
||||
### Track B — Migration BT/PBAP (préparation)
|
||||
|
||||
- Faisabilité documentée: `docs/bt_pbap_migration_feasibility.md`
|
||||
- Build migration préparé: `env:esp32dev-bt-migrate`
|
||||
- Interface d'abstraction ajoutée: `src/bluetooth/IBluetoothBackend.h`
|
||||
|
||||
### Gates de validation
|
||||
|
||||
- Gate local minimal:
|
||||
- `platformio run -e esp32dev`
|
||||
- `bash scripts/test_terminal.sh`
|
||||
- `scripts/hw_validation.py --port-a252 /dev/cu.usbserial-0001`
|
||||
- Gate CI:
|
||||
- workflows `Repo State` + `Firmware CI` verts sur `main`
|
||||
|
||||
## 7) Exécution hardware locale (2026-02-21, ESP32 Audio Kit)
|
||||
|
||||
- Cible testée: `/dev/cu.usbserial-0001`
|
||||
- Upload firmware: `platformio run -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001` -> `PASS`
|
||||
- Gate local: `bash scripts/test_terminal.sh` -> `PASS` (build unit test, host dtmf, parity parser, parity routes)
|
||||
- Smoke HW: `python3 scripts/hw_validation.py --port-a252 /dev/cu.usbserial-0001 --report-json artifacts/hw_validation_report.json --report-md artifacts/hw_validation_report.md` -> `PASS`
|
||||
- Snapshot runtime: `artifacts/runtime_snapshot.json` (inclut `BT_STATUS`, `WIFI_STATUS`, `ESPNOW_STATUS`, `/api/status`)
|
||||
- WiFi stability: `artifacts/wifi_stability_report.json` -> `PASS` (disconnect => `ap_fallback`, reconnect => `connected`, credentials persistées)
|
||||
- HFP opérable (stack actuelle): `artifacts/hfp_operational_report.json` -> `PASS` (discoverable on/off ok, `BT_DIAL` sans SLC en erreur contrôlée, `BT_PBAP_SYNC unsupported` explicite)
|
||||
- ESP-NOW protocole v1: `artifacts/espnow_protocol_v1_report.json` -> `FAIL` en bench 1 carte (pas de peer radio actif, `ESPNOW_SEND` retourne `ERR`)
|
||||
- Décision de gate: WiFi/WebUI/Bluetooth HFP command-level `GO`; ESP-NOW E2E v1 two-board `BLOCKED` jusqu’à disponibilité d’une 2e carte
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Faisabilité migration Bluetooth PBAP
|
||||
|
||||
Date: 2026-02-21
|
||||
Repo: `RTC_BL_PHONE`
|
||||
|
||||
## Résumé exécutif
|
||||
|
||||
- Constats actuels:
|
||||
- HFP est opérable côté commandes firmware.
|
||||
- PBAP n'est pas exposé par la stack Arduino-ESP32/Bluedroid utilisée actuellement.
|
||||
- Décision court terme:
|
||||
- conserver Track A livrable (HFP + WebUI + WiFi + ESP-NOW),
|
||||
- lancer Track B migration stack pour PBAP.
|
||||
|
||||
## 1) État technique actuel
|
||||
|
||||
- Stack firmware active: Arduino ESP32 + Bluedroid (HFP client).
|
||||
- PBAP: API indisponible dans ce périmètre actuel.
|
||||
- Symptôme attendu: `BT_PBAP_SYNC` retourne `unsupported` avec cause explicite.
|
||||
|
||||
## 2) Cibles étudiées
|
||||
|
||||
1. Arduino-ESP32 actuel (baseline):
|
||||
- Avantage: stable, faible risque régression.
|
||||
- Limite: PBAP absent.
|
||||
2. Mode mixte Arduino + ESP-IDF (backend BT dédié):
|
||||
- Avantage: accès plus bas niveau profils BT.
|
||||
- Risque: complexité build/intégration.
|
||||
3. Backend BT alternatif (abstraction `IBluetoothBackend`):
|
||||
- Avantage: migration progressive sans casser Track A.
|
||||
- Risque: surcharge maintenance temporaire.
|
||||
|
||||
## 3) Critère GO / NOGO Track B
|
||||
|
||||
GO si:
|
||||
- PBAP sync fonctionne réellement (au moins 1 contact avec `display_name`, `phone_number`).
|
||||
- Build reproductible via `platformio.ini` (`env:esp32dev-bt-migrate`).
|
||||
- Pas de régression des commandes HFP existantes.
|
||||
|
||||
NOGO si:
|
||||
- PBAP non atteignable en 2 itérations de migration contrôlée.
|
||||
- Régressions HFP non résolues.
|
||||
|
||||
## 4) Plan de pivot
|
||||
|
||||
Si NOGO:
|
||||
1. marquer PBAP `blocked by stack`,
|
||||
2. conserver Track A en production,
|
||||
3. ouvrir lot dédié migration profonde (estimation + risques + dépendances).
|
||||
|
||||
## 5) Implémentation préparatoire déjà posée
|
||||
|
||||
- Environnement de build migration ajouté:
|
||||
- `env:esp32dev-bt-migrate` (`-DBT_BACKEND_EXPERIMENTAL=1`)
|
||||
- Interface d'abstraction introduite:
|
||||
- `src/bluetooth/IBluetoothBackend.h`
|
||||
|
||||
## 6) Risques
|
||||
|
||||
- Risque principal: coût de migration stack BT supérieur au bénéfice court terme.
|
||||
- Risque secondaire: divergence comportements HFP entre backend baseline et backend migré.
|
||||
@@ -0,0 +1,76 @@
|
||||
# ESP-NOW API v1 (`rtcbl/1`)
|
||||
|
||||
Date: 2026-02-21
|
||||
Scope: contrat d'échange entre `RTC_BL_PHONE` (A252) et la seconde carte.
|
||||
|
||||
## 1. Objectif
|
||||
|
||||
Normaliser les trames ESP-NOW pour:
|
||||
- corréler requête/réponse,
|
||||
- conserver compatibilité legacy,
|
||||
- simplifier l'intégration du second repo.
|
||||
|
||||
## 2. Requête v1
|
||||
|
||||
```json
|
||||
{
|
||||
"proto": "rtcbl/1",
|
||||
"id": "req-001",
|
||||
"ts": 1730000000,
|
||||
"source": "a252",
|
||||
"target": "peer|broadcast",
|
||||
"cmd": "STATUS|CALL|WIFI_STATUS|BT_STATUS|...",
|
||||
"args": {}
|
||||
}
|
||||
```
|
||||
|
||||
Règles:
|
||||
- `proto` obligatoire pour activer le mode v1.
|
||||
- `id` recommandé (corrélation réponse).
|
||||
- `cmd` obligatoire.
|
||||
- `args` optionnel. S'il est présent, il est sérialisé et transmis au dispatcher.
|
||||
|
||||
## 3. Réponse v1
|
||||
|
||||
```json
|
||||
{
|
||||
"proto": "rtcbl/1",
|
||||
"id": "req-001",
|
||||
"ok": true,
|
||||
"code": "STATUS",
|
||||
"data": {},
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
Règles:
|
||||
- `id` reprend la requête.
|
||||
- `ok=false` => `error` non vide.
|
||||
- `data` contient le JSON de la commande si disponible.
|
||||
- fallback possible `data_raw` si la réponse n'est pas JSON.
|
||||
|
||||
## 4. Compatibilité legacy
|
||||
|
||||
Le firmware continue d'accepter les formats existants:
|
||||
- `{"cmd":"..."}`
|
||||
- `{"raw":"..."}`
|
||||
- `{"command":"..."}`
|
||||
- `{"action":"..."}`
|
||||
- variantes imbriquées via `event`, `message`, `payload`
|
||||
|
||||
## 5. Commandes recommandées v1
|
||||
|
||||
- `STATUS`
|
||||
- `CALL`
|
||||
- `WIFI_STATUS`
|
||||
- `MQTT_STATUS`
|
||||
- `ESPNOW_STATUS`
|
||||
- `BT_STATUS`
|
||||
|
||||
## 6. Intégration second repo
|
||||
|
||||
Checklist minimum côté seconde carte:
|
||||
1. Émettre `proto=rtcbl/1` + `id` unique par requête.
|
||||
2. Implémenter timeout de réponse (2-5s).
|
||||
3. Corréler sur `id`.
|
||||
4. Prévoir fallback legacy si `proto` absent en réponse.
|
||||
@@ -46,3 +46,51 @@
|
||||
| `mqtt connect/disconnect/publish` | PASS | endpoints `/api/network/mqtt/*` presents front/backend |
|
||||
| `espnow on/off/send/peer` | PASS | endpoints `/api/network/espnow/*` presents front/backend |
|
||||
| `control actions` | PASS | endpoint `/api/control` present front/backend |
|
||||
|
||||
## Enchaînement Track A — Bluetooth / WiFi / WebUI / ESP-NOW
|
||||
|
||||
- Date UTC: 2026-02-21T22:30:00Z
|
||||
- Scope: stabilisation continue Track A + préparation Track B PBAP.
|
||||
|
||||
### Vérifications ajoutées
|
||||
|
||||
| Domaine | Vérification | Verdict |
|
||||
|---|---|---|
|
||||
| Bluetooth Web/API | endpoints discoverable/dial/redial/answer/hangup/calls/pbap_sync exposés | PASS |
|
||||
| WebUI BT | actions UI alignées sur endpoints backend | PASS |
|
||||
| WiFi | endpoints connect/disconnect/reconnect/scan inchangés et disponibles | PASS |
|
||||
| ESP-NOW | contrat v1 documenté + compat legacy conservée | PASS |
|
||||
| PBAP | retourne explicitement `unsupported` tant que migration non finalisée | PASS |
|
||||
|
||||
### Artefacts de référence
|
||||
|
||||
- `artifacts/route_parity_report.json`
|
||||
- `artifacts/webui_bt_parity_report.json`
|
||||
- `artifacts/wifi_stability_report.json`
|
||||
- `artifacts/espnow_protocol_v1_report.json`
|
||||
- `artifacts/hfp_operational_report.json`
|
||||
|
||||
## Exécution locale consolidée (ESP32 Audio Kit, 2026-02-21)
|
||||
|
||||
- Port test: `/dev/cu.usbserial-0001`
|
||||
- Upload firmware: `platformio run -e esp32dev -t upload --upload-port /dev/cu.usbserial-0001` -> `PASS`
|
||||
- Gate local: `bash scripts/test_terminal.sh` -> `PASS`
|
||||
- Validation HW: `scripts/hw_validation.py --port-a252 /dev/cu.usbserial-0001 --report-json artifacts/hw_validation_report.json --report-md artifacts/hw_validation_report.md` -> `PASS`
|
||||
- Snapshot runtime: `artifacts/runtime_snapshot.json`
|
||||
- WiFi stability (`artifacts/wifi_stability_report.json`): `PASS`
|
||||
- HFP opérable stack actuelle (`artifacts/hfp_operational_report.json`): `PASS`
|
||||
- ESP-NOW protocole v1 (`artifacts/espnow_protocol_v1_report.json`): `FAIL` sur bench 1 carte (absence de peer, `ESPNOW_SEND` en erreur contrôlée)
|
||||
|
||||
### Détails clés observés
|
||||
|
||||
- WiFi:
|
||||
- `WIFI_DISCONNECT` force `state=ap_fallback` avec AP actif.
|
||||
- `WIFI_RECONNECT` restaure `state=connected` en STA et coupe l’AP fallback.
|
||||
- SSID restauré: `Les cils`; credentials persistées.
|
||||
- Bluetooth/HFP:
|
||||
- `BT_DISCOVERABLE_ON/OFF`: `OK`.
|
||||
- `BT_DIAL` sans SLC connecté: erreur contrôlée (attendue).
|
||||
- `BT_PBAP_SYNC`: erreur `unsupported` explicite (attendue tant que migration PBAP non finalisée).
|
||||
- ESP-NOW:
|
||||
- `ESPNOW_STATUS.ready=true`.
|
||||
- `ESPNOW_SEND` v1 et legacy en échec sur banc mono-carte (pas de pair receveur actif), à retester en configuration 2 cartes.
|
||||
|
||||
@@ -29,6 +29,14 @@ build_flags =
|
||||
${env.build_flags}
|
||||
-DBOARD_PROFILE_A252
|
||||
|
||||
[env:esp32dev-bt-migrate]
|
||||
board = esp32dev
|
||||
board_build.partitions = huge_app.csv
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-DBOARD_PROFILE_A252
|
||||
-DBT_BACKEND_EXPERIMENTAL=1
|
||||
|
||||
[env:test]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef BLUETOOTH_I_BLUETOOTH_BACKEND_H
|
||||
#define BLUETOOTH_I_BLUETOOTH_BACKEND_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "core/PlatformProfile.h"
|
||||
|
||||
struct PbapContact {
|
||||
String display_name;
|
||||
String phone_number;
|
||||
};
|
||||
|
||||
class IBluetoothBackend {
|
||||
public:
|
||||
virtual ~IBluetoothBackend() = default;
|
||||
|
||||
virtual bool begin(BoardProfile profile) = 0;
|
||||
virtual bool connect(const String& mac) = 0;
|
||||
virtual bool disconnect() = 0;
|
||||
|
||||
virtual bool setDiscoverable(bool enabled) = 0;
|
||||
virtual bool dial(const String& number) = 0;
|
||||
virtual bool redial() = 0;
|
||||
virtual bool answer() = 0;
|
||||
virtual bool hangup() = 0;
|
||||
virtual bool queryCalls() = 0;
|
||||
|
||||
virtual bool syncPbap() = 0;
|
||||
virtual bool isPbapSupported() const = 0;
|
||||
};
|
||||
|
||||
#endif // BLUETOOTH_I_BLUETOOTH_BACKEND_H
|
||||
+80
-1
@@ -151,6 +151,55 @@ bool extractBridgeCommand(JsonVariantConst payload, String& out_cmd, uint8_t dep
|
||||
return false;
|
||||
}
|
||||
|
||||
bool buildRtcBlV1BridgeCommand(JsonVariantConst payload,
|
||||
String& out_cmd,
|
||||
String& out_request_id,
|
||||
bool& out_is_v1) {
|
||||
out_is_v1 = false;
|
||||
if (!payload.is<JsonObjectConst>()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonObjectConst obj = payload.as<JsonObjectConst>();
|
||||
const String proto = obj["proto"] | "";
|
||||
if (!proto.equalsIgnoreCase("rtcbl/1")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const String cmd = obj["cmd"] | "";
|
||||
if (cmd.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_cmd = cmd;
|
||||
out_cmd.trim();
|
||||
if (out_cmd.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_request_id = obj["id"] | "";
|
||||
out_is_v1 = true;
|
||||
|
||||
if (obj["args"].isNull()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String args;
|
||||
serializeJson(obj["args"], args);
|
||||
args.trim();
|
||||
if (!args.isEmpty() && args != "null") {
|
||||
out_cmd += " ";
|
||||
out_cmd += args;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMacAddressString(const String& value) {
|
||||
uint8_t mac[6] = {0};
|
||||
return A252ConfigStore::parseMac(value, mac);
|
||||
}
|
||||
|
||||
AudioConfig buildI2sConfig(const A252PinsConfig& pins_cfg, const A252AudioConfig& audio_cfg) {
|
||||
AudioConfig cfg;
|
||||
cfg.port = I2S_NUM_0;
|
||||
@@ -780,7 +829,10 @@ void registerCommands() {
|
||||
|
||||
void processInboundBridgeCommand(const String& source, const JsonVariantConst& payload) {
|
||||
String cmd;
|
||||
if (!extractBridgeCommand(payload, cmd)) {
|
||||
String request_id;
|
||||
bool is_rtcbl_v1 = false;
|
||||
if (!buildRtcBlV1BridgeCommand(payload, cmd, request_id, is_rtcbl_v1) &&
|
||||
!extractBridgeCommand(payload, cmd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -794,6 +846,33 @@ void processInboundBridgeCommand(const String& source, const JsonVariantConst& p
|
||||
String payload_json;
|
||||
serializeJson(event, payload_json);
|
||||
g_mqtt.publish("event", payload_json, false);
|
||||
|
||||
if (!is_rtcbl_v1 || !isMacAddressString(source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonDocument response;
|
||||
response["proto"] = "rtcbl/1";
|
||||
response["id"] = request_id;
|
||||
response["ok"] = result.ok;
|
||||
response["code"] = result.code;
|
||||
response["error"] = result.ok ? "" : (result.code.isEmpty() ? result.raw : result.code);
|
||||
|
||||
if (!result.json.isEmpty()) {
|
||||
JsonDocument parsed;
|
||||
if (deserializeJson(parsed, result.json) == DeserializationError::Ok) {
|
||||
JsonVariant data = response["data"];
|
||||
data.set(parsed.as<JsonVariantConst>());
|
||||
} else {
|
||||
response["data_raw"] = result.json;
|
||||
}
|
||||
} else if (!result.raw.isEmpty()) {
|
||||
response["data_raw"] = result.raw;
|
||||
}
|
||||
|
||||
String response_payload;
|
||||
serializeJson(response, response_payload);
|
||||
g_espnow.sendJson(source, response_payload);
|
||||
}
|
||||
|
||||
void printHelp() {
|
||||
|
||||
@@ -244,6 +244,33 @@ void WebServerManager::registerRoutes() {
|
||||
});
|
||||
server_.on("/api/bluetooth/hfp/disconnect", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_HFP_DISCONNECT"); });
|
||||
server_.on("/api/bluetooth/discoverable/on", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_DISCOVERABLE_ON"); });
|
||||
server_.on("/api/bluetooth/discoverable/off", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_DISCOVERABLE_OFF"); });
|
||||
server_.on("/api/bluetooth/hfp/dial", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||||
JsonDocument doc;
|
||||
if (!extractJsonBody(request, doc)) {
|
||||
request->send(400, "application/json", "{\"error\":\"invalid json body\"}");
|
||||
return;
|
||||
}
|
||||
const String number = doc["number"] | "";
|
||||
if (!isValidInput(number, 32)) {
|
||||
request->send(400, "application/json", "{\"error\":\"invalid number\"}");
|
||||
return;
|
||||
}
|
||||
handleDispatch(request, "BT_DIAL " + number);
|
||||
});
|
||||
server_.on("/api/bluetooth/hfp/redial", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_REDIAL"); });
|
||||
server_.on("/api/bluetooth/hfp/answer", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_ANSWER"); });
|
||||
server_.on("/api/bluetooth/hfp/hangup", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_HANGUP"); });
|
||||
server_.on("/api/bluetooth/hfp/calls", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_CALLS"); });
|
||||
server_.on("/api/bluetooth/pbap/sync", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_PBAP_SYNC"); });
|
||||
server_.on("/api/bluetooth/ble/start", HTTP_POST,
|
||||
[this](AsyncWebServerRequest* request) { handleDispatch(request, "BT_BLE_START"); });
|
||||
server_.on("/api/bluetooth/ble/stop", HTTP_POST,
|
||||
|
||||
Reference in New Issue
Block a user