diff --git a/data/webui/index.html b/data/webui/index.html
deleted file mode 100644
index 1ea5bef..0000000
--- a/data/webui/index.html
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
- RTC_BL_PHONE Web UI
-
-
-
-
- RTC_BL_PHONE Web UI
- Chargement...
-
-
-
-
-
-
-
-
- Configuration
-
-
-
-
-
-
-
Audio config (JSON)
-
-
-
-
-
-
-
Pins config (JSON)
-
-
-
-
-
-
-
-
-
- Réseau et bridges
-
-
-
WiFi
-
-
-
-
-
-
-
-
-
-
-
ESP-NOW
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/data/webui/script.js b/data/webui/script.js
deleted file mode 100644
index 07dfb3e..0000000
--- a/data/webui/script.js
+++ /dev/null
@@ -1,433 +0,0 @@
-const SECTION_MAP = {
- dashboard: "dashboardSection",
- config: "configSection",
- network: "networkSection",
- control: "controlSection",
-};
-let realtimeSource = null;
-let realtimeConnected = false;
-let fallbackPollingTimer = null;
-
-function showSection(section) {
- Object.values(SECTION_MAP).forEach((id) => {
- const el = document.getElementById(id);
- if (el) {
- el.classList.remove("active");
- }
- });
- const sectionEl = document.getElementById(SECTION_MAP[section]);
- if (sectionEl) {
- sectionEl.classList.add("active");
- }
-}
-
-function setJson(id, value) {
- const el = document.getElementById(id);
- if (!el) {
- return;
- }
- if (typeof value === "string") {
- el.textContent = value;
- return;
- }
- el.textContent = JSON.stringify(value, null, 2);
-}
-
-async function requestJson(url, options = {}) {
- const response = await fetch(url, options);
- const text = await response.text();
- let parsed = {};
- if (text) {
- try {
- parsed = JSON.parse(text);
- } catch (_) {
- parsed = { raw: text };
- }
- }
- if (!response.ok) {
- throw new Error(`HTTP ${response.status} ${text || ""}`.trim());
- }
- return parsed;
-}
-
-function jsonHeaders() {
- return { "Content-Type": "application/json" };
-}
-
-function parseJsonInput(id) {
- const raw = document.getElementById(id).value.trim();
- if (!raw) {
- return {};
- }
- return JSON.parse(raw);
-}
-
-function parsePayloadValue(rawPayload) {
- const trimmed = rawPayload.trim();
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
- try {
- return JSON.parse(trimmed);
- } catch (_) {
- return rawPayload;
- }
- }
- return rawPayload;
-}
-
-function parseRealtimeData(raw) {
- try {
- return JSON.parse(raw);
- } catch (_) {
- return { raw };
- }
-}
-
-function applyStatusSnapshot(status) {
- const line = document.getElementById("statusLine");
- const telephonyState = status.telephony?.state || "n/a";
- const hook = status.telephony?.hook || "n/a";
- const wifiState = status.wifi?.state || "n/a";
- const peers = status.espnow?.peer_count ?? 0;
- const liveState = realtimeConnected ? "live=on" : "live=off";
- line.textContent =
- `board=${status.board_profile || "n/a"} telephony=${telephonyState} hook=${hook} ` +
- `wifi=${wifiState} espnow_peers=${peers} ${liveState}`;
-
- setJson("statusJson", status);
- if (status.wifi) {
- setJson("wifiJson", status.wifi);
- }
- if (status.espnow) {
- setJson("espnowJson", status.espnow);
- setJson("espnowPeersJson", { peers: status.espnow.peers || [] });
- }
-}
-
-function connectRealtime() {
- if (!window.EventSource) {
- return;
- }
- if (realtimeSource) {
- realtimeSource.close();
- }
-
- realtimeSource = new EventSource("/api/events");
-
- realtimeSource.onopen = () => {
- realtimeConnected = true;
- };
-
- realtimeSource.addEventListener("hello", () => {
- realtimeConnected = true;
- });
-
- realtimeSource.addEventListener("status", (event) => {
- realtimeConnected = true;
- const status = parseRealtimeData(event.data);
- applyStatusSnapshot(status);
- });
-
- realtimeSource.addEventListener("dispatch", (event) => {
- const payload = parseRealtimeData(event.data);
- const out = { stream: "dispatch" };
- if (payload && typeof payload === "object" && !Array.isArray(payload)) {
- Object.assign(out, payload);
- } else {
- out.payload = payload;
- }
- setJson("actionResult", out);
- });
-
- realtimeSource.addEventListener("effect", (event) => {
- const payload = parseRealtimeData(event.data);
- const out = { stream: "effect" };
- if (payload && typeof payload === "object" && !Array.isArray(payload)) {
- Object.assign(out, payload);
- } else {
- out.payload = payload;
- }
- setJson("actionResult", out);
- });
-
- realtimeSource.onerror = () => {
- realtimeConnected = false;
- };
-}
-
-function ensureFallbackPolling() {
- if (fallbackPollingTimer !== null) {
- return;
- }
- fallbackPollingTimer = window.setInterval(() => {
- if (!realtimeConnected) {
- refreshStatus().catch(() => {});
- }
- }, 2000);
-}
-
-async function refreshStatus() {
- try {
- const status = await requestJson("/api/status");
- applyStatusSnapshot(status);
- } catch (error) {
- const line = document.getElementById("statusLine");
- const liveState = realtimeConnected ? "live=on" : "live=off";
- line.textContent = `Erreur statut: ${error.message}`;
- line.textContent += ` ${liveState}`;
- setJson("statusJson", { error: error.message });
- }
-}
-
-async function refreshConfig() {
- try {
- const [pins, audio] = await Promise.all([
- requestJson("/api/config/pins"),
- requestJson("/api/config/audio"),
- ]);
- setJson("configJson", { pins, audio });
- const audioInput = document.getElementById("audioConfigInput");
- const pinsInput = document.getElementById("pinsConfigInput");
- if (audioInput) {
- audioInput.value = JSON.stringify(audio, null, 2);
- }
- if (pinsInput) {
- pinsInput.value = JSON.stringify(pins, null, 2);
- }
- } catch (error) {
- setJson("configJson", { error: error.message });
- }
-}
-
-async function refreshNetwork() {
- try {
- const [wifi, espnow, peers] = await Promise.all([
- requestJson("/api/network/wifi"),
- requestJson("/api/network/espnow"),
- requestJson("/api/network/espnow/peer"),
- ]);
- setJson("wifiJson", wifi);
- setJson("espnowJson", espnow);
- setJson("espnowPeersJson", peers);
- } catch (error) {
- const err = { error: error.message };
- setJson("wifiJson", err);
- setJson("espnowJson", err);
- setJson("espnowPeersJson", err);
- }
-}
-
-async function sendControl(action) {
- const result = await requestJson("/api/control", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify({ action }),
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- return result;
-}
-
-function bindEvents() {
- document.querySelectorAll("nav button[data-section]").forEach((button) => {
- button.addEventListener("click", () => showSection(button.dataset.section));
- });
-
- document.getElementById("refreshAllBtn").addEventListener("click", async () => {
- await Promise.all([refreshStatus(), refreshConfig(), refreshNetwork()]);
- });
- document.getElementById("refreshConfigBtn").addEventListener("click", refreshConfig);
- document.getElementById("espnowRefreshBtn").addEventListener("click", refreshNetwork);
-
- document.getElementById("applyAudioConfigBtn").addEventListener("click", async () => {
- try {
- const payload = parseJsonInput("audioConfigInput");
- const result = await requestJson("/api/config/audio", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify(payload),
- });
- setJson("actionResult", result);
- await Promise.all([refreshConfig(), refreshStatus()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("applyPinsConfigBtn").addEventListener("click", async () => {
- try {
- const payload = parseJsonInput("pinsConfigInput");
- const result = await requestJson("/api/config/pins", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify(payload),
- });
- setJson("actionResult", result);
- await Promise.all([refreshConfig(), refreshStatus()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("wifiConnectForm").addEventListener("submit", async (event) => {
- event.preventDefault();
- const ssid = document.getElementById("wifiSsid").value.trim();
- const pass = document.getElementById("wifiPass").value;
- try {
- const result = await requestJson("/api/network/wifi/connect", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify({ ssid, pass }),
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("wifiDisconnectBtn").addEventListener("click", async () => {
- try {
- const result = await requestJson("/api/network/wifi/disconnect", {
- method: "POST",
- headers: jsonHeaders(),
- body: "{}",
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("wifiReconnectBtn").addEventListener("click", async () => {
- try {
- const result = await requestJson("/api/network/wifi/reconnect", {
- method: "POST",
- headers: jsonHeaders(),
- body: "{}",
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("wifiScanBtn").addEventListener("click", async () => {
- try {
- const result = await requestJson("/api/network/wifi/scan", {
- method: "POST",
- headers: jsonHeaders(),
- body: "{}",
- });
- setJson("actionResult", result);
- await refreshNetwork();
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("espnowOnBtn").addEventListener("click", async () => {
- try {
- const result = await requestJson("/api/network/espnow/on", {
- method: "POST",
- headers: jsonHeaders(),
- body: "{}",
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("espnowOffBtn").addEventListener("click", async () => {
- try {
- const result = await requestJson("/api/network/espnow/off", {
- method: "POST",
- headers: jsonHeaders(),
- body: "{}",
- });
- setJson("actionResult", result);
- await Promise.all([refreshStatus(), refreshNetwork()]);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("espnowPeerForm").addEventListener("submit", async (event) => {
- event.preventDefault();
- const mac = document.getElementById("espnowMac").value.trim();
- try {
- const result = await requestJson("/api/network/espnow/peer", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify({ mac }),
- });
- setJson("actionResult", result);
- await refreshNetwork();
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("espnowDelBtn").addEventListener("click", async () => {
- const mac = document.getElementById("espnowMac").value.trim();
- try {
- const result = await requestJson("/api/network/espnow/peer", {
- method: "DELETE",
- headers: jsonHeaders(),
- body: JSON.stringify({ mac }),
- });
- setJson("actionResult", result);
- await refreshNetwork();
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.getElementById("espnowSendForm").addEventListener("submit", async (event) => {
- event.preventDefault();
- const payloadRaw = document.getElementById("espnowPayload").value;
- const payload = parsePayloadValue(payloadRaw);
- try {
- const result = await requestJson("/api/network/espnow/send", {
- method: "POST",
- headers: jsonHeaders(),
- body: JSON.stringify({ payload }),
- });
- setJson("actionResult", result);
- await refreshNetwork();
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-
- document.querySelectorAll("#controlSection button[data-action]").forEach((button) => {
- button.addEventListener("click", async () => {
- try {
- await sendControl(button.dataset.action);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
- });
-
- document.getElementById("rawCommandForm").addEventListener("submit", async (event) => {
- event.preventDefault();
- const action = document.getElementById("rawCommandInput").value.trim();
- try {
- await sendControl(action);
- } catch (error) {
- setJson("actionResult", { error: error.message });
- }
- });
-}
-
-document.addEventListener("DOMContentLoaded", async () => {
- bindEvents();
- connectRealtime();
- ensureFallbackPolling();
- await Promise.all([refreshStatus(), refreshConfig(), refreshNetwork()]);
- showSection("dashboard");
-});
diff --git a/data/webui/style.css b/data/webui/style.css
deleted file mode 100644
index 8b71e44..0000000
--- a/data/webui/style.css
+++ /dev/null
@@ -1,116 +0,0 @@
-:root {
- --bg: #f4f6f8;
- --panel: #ffffff;
- --text: #1f2933;
- --accent: #0f6ab6;
- --accent-strong: #084a81;
- --ok: #19753d;
- --border: #d8dee4;
- --warn: #9a5f00;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
- padding: 24px;
- font-family: "Segoe UI", Tahoma, sans-serif;
- background: linear-gradient(135deg, #edf4fa, #f8f9fb);
- color: var(--text);
-}
-
-header {
- margin-bottom: 16px;
-}
-
-h1 {
- margin: 0 0 8px;
- color: var(--accent);
-}
-
-nav {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 16px;
-}
-
-button {
- background: var(--accent);
- color: #fff;
- border: none;
- border-radius: 6px;
- padding: 8px 12px;
- cursor: pointer;
-}
-
-button:hover {
- background: var(--accent-strong);
-}
-
-section {
- display: none;
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 16px;
-}
-
-section.active {
- display: block;
-}
-
-.grid2 {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 16px;
-}
-
-.inline-actions {
- margin: 8px 0;
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
-}
-
-input,
-select,
-textarea {
- padding: 8px;
- border: 1px solid var(--border);
- border-radius: 6px;
- font: inherit;
- color: inherit;
- background: #fff;
-}
-
-form {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 10px;
-}
-
-pre {
- background: #1f2933;
- color: #e8eef4;
- border-radius: 6px;
- padding: 12px;
- overflow-x: auto;
- white-space: pre-wrap;
- word-break: break-word;
-}
-
-@media (max-width: 760px) {
- body {
- padding: 12px;
- }
-
- button,
- input,
- textarea {
- width: 100%;
- }
-}
diff --git a/platformio.ini b/platformio.ini
deleted file mode 100644
index 5519698..0000000
--- a/platformio.ini
+++ /dev/null
@@ -1,108 +0,0 @@
-; PlatformIO Project Configuration File
-
-[platformio]
-default_envs = esp32-s3-devkitc-1
-build_dir = .pio/build
-
-[env]
-platform = espressif32
-framework = arduino
-monitor_speed = 115200
-test_build_src = yes
-build_flags =
- -DCORE_DEBUG_LEVEL=1
-lib_deps =
- bblanchon/ArduinoJson@^7.0.4
- throwtheswitch/Unity@^2.6.1
- ESP32Async/AsyncTCP@^3.3.2
- ESP32Async/ESPAsyncWebServer@^3.6.0
- https://github.com/pschatzmann/arduino-audio-tools.git
- https://github.com/bitluni/OsciDisplay.git
-lib_ignore =
- ESPAsyncTCP
- RPAsyncTCP
-
-
-; S3 scope only: ESP32-A252 build profile intentionally removed from active configs in this project phase.
-
-[env:esp32-s3-devkitc-1]
-board = esp32-s3-devkitc-1
-board_build.partitions = partitions/esp32s3_no_ota_spiffs_16MB.csv
-upload_port = /dev/cu.usbserial-0001
-monitor_port = /dev/cu.usbserial-0001
-build_flags =
- ${env.build_flags}
- -DBOARD_PROFILE_ESP32_S3
-build_src_filter =
- +
- +