diff --git a/data/webui/index.html b/data/webui/index.html
index 27030f8..bef7f97 100644
--- a/data/webui/index.html
+++ b/data/webui/index.html
@@ -32,6 +32,29 @@
+
+
+
Audio config (JSON)
+
+
+
+
+
+
+
MQTT config (JSON)
+
+
+
+
+
+
+
Pins config (JSON)
+
+
+
+
+
+
@@ -86,6 +109,21 @@
+
+
+
Bluetooth
+
+
+
+
+
+
+
+
+
diff --git a/data/webui/script.js b/data/webui/script.js
index f664b66..5fc4943 100644
--- a/data/webui/script.js
+++ b/data/webui/script.js
@@ -51,6 +51,14 @@ 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("[")) {
@@ -90,6 +98,18 @@ async function refreshConfig() {
requestJson("/api/config/mqtt"),
]);
setJson("configJson", { pins, audio, mqtt });
+ const audioInput = document.getElementById("audioConfigInput");
+ const mqttInput = document.getElementById("mqttConfigInput");
+ const pinsInput = document.getElementById("pinsConfigInput");
+ if (audioInput) {
+ audioInput.value = JSON.stringify(audio, null, 2);
+ }
+ if (mqttInput) {
+ mqttInput.value = JSON.stringify(mqtt.config || mqtt, null, 2);
+ }
+ if (pinsInput) {
+ pinsInput.value = JSON.stringify(pins, null, 2);
+ }
} catch (error) {
setJson("configJson", { error: error.message });
}
@@ -116,6 +136,15 @@ async function refreshNetwork() {
}
}
+async function refreshBluetooth() {
+ try {
+ const bt = await requestJson("/api/bluetooth");
+ setJson("btJson", bt);
+ } catch (error) {
+ setJson("btJson", { error: error.message });
+ }
+}
+
async function sendControl(action) {
const result = await requestJson("/api/control", {
method: "POST",
@@ -137,6 +166,52 @@ function bindEvents() {
});
document.getElementById("refreshConfigBtn").addEventListener("click", refreshConfig);
document.getElementById("espnowRefreshBtn").addEventListener("click", refreshNetwork);
+ document.getElementById("btRefreshBtn").addEventListener("click", refreshBluetooth);
+
+ 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("applyMqttConfigBtn").addEventListener("click", async () => {
+ try {
+ const payload = parseJsonInput("mqttConfigInput");
+ const result = await requestJson("/api/config/mqtt", {
+ method: "POST",
+ headers: jsonHeaders(),
+ body: JSON.stringify(payload),
+ });
+ setJson("actionResult", result);
+ await Promise.all([refreshConfig(), refreshNetwork(), 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();
@@ -319,6 +394,64 @@ function bindEvents() {
}
});
+ document.getElementById("btHfpConnectForm").addEventListener("submit", async (event) => {
+ event.preventDefault();
+ const addr = document.getElementById("btHfpAddr").value.trim();
+ try {
+ const result = await requestJson("/api/bluetooth/hfp/connect", {
+ method: "POST",
+ headers: jsonHeaders(),
+ body: JSON.stringify({ addr }),
+ });
+ setJson("actionResult", result);
+ await Promise.all([refreshBluetooth(), refreshStatus()]);
+ } catch (error) {
+ setJson("actionResult", { error: error.message });
+ }
+ });
+
+ document.getElementById("btHfpDisconnectBtn").addEventListener("click", async () => {
+ try {
+ const result = await requestJson("/api/bluetooth/hfp/disconnect", {
+ 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", {
+ method: "POST",
+ headers: jsonHeaders(),
+ body: "{}",
+ });
+ setJson("actionResult", result);
+ await Promise.all([refreshBluetooth(), refreshStatus()]);
+ } catch (error) {
+ setJson("actionResult", { error: error.message });
+ }
+ });
+
+ document.getElementById("btBleStopBtn").addEventListener("click", async () => {
+ try {
+ const result = await requestJson("/api/bluetooth/ble/stop", {
+ method: "POST",
+ headers: jsonHeaders(),
+ body: "{}",
+ });
+ setJson("actionResult", result);
+ await Promise.all([refreshBluetooth(), refreshStatus()]);
+ } catch (error) {
+ setJson("actionResult", { error: error.message });
+ }
+ });
+
document.querySelectorAll("#controlSection button[data-action]").forEach((button) => {
button.addEventListener("click", async () => {
try {
@@ -342,6 +475,6 @@ function bindEvents() {
document.addEventListener("DOMContentLoaded", async () => {
bindEvents();
- await Promise.all([refreshStatus(), refreshConfig(), refreshNetwork()]);
+ await Promise.all([refreshStatus(), refreshConfig(), refreshNetwork(), refreshBluetooth()]);
showSection("dashboard");
});