Release fw 0.8.4: doc sync, System UI, BT/FM polish.

Align README, manual, and backlog to 0.8.4; add Web UI System tab for OTA/DSP uploads, serial in health header, FM seek down, and BT1035 paired list plus auto-reconnect API.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 09:56:44 +02:00
co-authored by Cursor
parent b9ed8a2dcd
commit 176cdd9319
28 changed files with 988 additions and 66 deletions
+83 -4
View File
@@ -60,7 +60,7 @@ namespace net {
namespace {
constexpr char kTag[] = "SetupWebServer";
constexpr char kFirmwareVersion[] = "0.8.3";
constexpr char kFirmwareVersion[] = "0.8.4";
constexpr unsigned kRebootDelaySec = 3;
extern const uint8_t www_index_html_gz_start[] asm(
@@ -402,12 +402,12 @@ esp_err_t tunerPlayPostHandler(httpd_req_t* req)
}
/**
* @brief tunerSeekPostHandler — accept POST /api/tuner/seek (FM up).
* @brief tunerSeekPostHandler — accept POST /api/tuner/seek (FM up/down).
*
* @dname tunerSeekPostHandler
* @param req HTTP request handle from esp_http_server.
* @return ESP_OK on success, or an esp_err_t error code.
* @pubstate uses route context tuner service with SeekDirection::Up.
* @pubstate uses route context tuner service with parsed SeekDirection.
*
* @author Michele Bigi
* @date 2026-07-06
@@ -420,7 +420,18 @@ esp_err_t tunerSeekPostHandler(httpd_req_t* req)
return httpd_resp_send(req, nullptr, 0);
}
auto freq = ctx->tuner->seekFm(core::SeekDirection::Up);
std::array<char, 128> body{};
readRequestBody(req, body);
const auto direction = core::parseTunerSeekJson(std::string_view(body.data()));
if (!direction) {
const std::string json =
core::serializeTunerErrorJson(parseErrorToken(direction.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
auto freq = ctx->tuner->seekFm(*direction);
if (freq) {
const std::string json = std::string("{\"frequency_khz\":")
+ std::to_string(freq->value()) + "}";
@@ -895,6 +906,58 @@ esp_err_t bluetoothDisconnectPostHandler(httpd_req_t* req)
return httpd_resp_send(req, "{\"status\":\"disconnected\"}", 24);
}
esp_err_t bluetoothPairedGetHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
auto devices = ctx->bluetooth->listPaired();
if (!devices) {
const std::string json =
core::serializeBluetoothErrorJson(bt1035ErrorToken(devices.error()));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
const std::string json = core::serializeBluetoothPairedJson(*devices);
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
esp_err_t bluetoothAutoReconnectPostHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
if (ctx == nullptr || ctx->bluetooth == nullptr) {
httpd_resp_set_status(req, "503 Service Unavailable");
return httpd_resp_send(req, nullptr, 0);
}
std::array<char, 128> body{};
readRequestBody(req, body);
const auto times =
core::parseBluetoothAutoReconnectJson(std::string_view(body.data()));
if (!times) {
const std::string json =
core::serializeBluetoothErrorJson(parseErrorToken(times.error()));
httpd_resp_set_status(req, "400 Bad Request");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
if (auto result = ctx->bluetooth->setAutoReconnect(*times); !result) {
const std::string json =
core::serializeBluetoothErrorJson(bt1035ErrorToken(result.error()));
httpd_resp_set_status(req, "500 Internal Server Error");
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, json.c_str(), json.size());
}
httpd_resp_set_type(req, "application/json");
return httpd_resp_send(req, "{\"status\":\"saved\"}", 18);
}
esp_err_t stationsGetHandler(httpd_req_t* req)
{
auto* ctx = routeContextFrom(req);
@@ -1313,6 +1376,22 @@ std::expected<void, NetError> SetupWebServer::start(
};
httpd_register_uri_handler(server_, &bluetoothDisconnectUri);
const httpd_uri_t bluetoothPairedUri = {
.uri = "/api/bluetooth/paired",
.method = HTTP_GET,
.handler = bluetoothPairedGetHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothPairedUri);
const httpd_uri_t bluetoothAutoReconnectUri = {
.uri = "/api/bluetooth/auto-reconnect",
.method = HTTP_POST,
.handler = bluetoothAutoReconnectPostHandler,
.user_ctx = routeCtx,
};
httpd_register_uri_handler(server_, &bluetoothAutoReconnectUri);
const httpd_uri_t stationsGetUri = {
.uri = "/api/stations",
.method = HTTP_GET,