Add internet radio streaming with runtime API, modernize web UI, remove auto-tune/beep at boot
Streaming (main feature this session): - New WebRadioConfig/WebRadioJson core types, ISecureStore-backed persistence - New webradio::WebRadioService (thread-safe live config) + GET/POST /api/streaming - web_radio_stream task now runtime-toggleable (no reboot), no hardcoded URL - Content-Type diagnostic: warns clearly when a URL is a webpage, not an audio stream Boot cleanup: - Removed boot-time auto FM/DAB tune, auto-beep, and the (now-concluded) Si4684 crystal IBIAS/CTUN empirical sweep from main.cpp — tuning/beep are on-demand via the existing REST API only Web UI: - Modernized styling (cards, gradients, toggle switches, light/dark theme) - New Stream tab wired to /api/streaming Fixes found via real idf.py build (not just clangd): - Restored wrongly-removed si4684/Si4684Tuner.hpp include in main.cpp - Fixed MP3Decode() argument types in web_radio_stream.cpp (unsigned char**/int*) Quality-gate fixes: - Host-test stub headers (esp_log.h, freertos/*) so TunerService.cpp's scanForStation logging/pacing compiles for station_service_test / integration_service_test instead of running stale binaries - Added WifiScanner and WebRadioService manual sections; filled in missing Doxygen docs on BluetoothService, i2s_sdata_probe, test_firmware, Bt1035At - Ignore clangd's .cache/ index directory Also includes prior uncommitted work carried in the tree: Wi-Fi/Bluetooth device scan REST API and UI (WifiScanner, BT scan), SigmaStudio TCP bridge, and the current ADAU1701 SigmaStudio DSP program export. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "net/NetError.hpp"
|
||||
#include "net/NetState.hpp"
|
||||
#include "net/SetupWebServer.hpp"
|
||||
#include "net/SigmaStudioTcpServer.hpp"
|
||||
#include "net/SoftApHost.hpp"
|
||||
#include "net/StaClient.hpp"
|
||||
|
||||
@@ -53,6 +54,10 @@ namespace tuner {
|
||||
class TunerService;
|
||||
} // namespace tuner
|
||||
|
||||
namespace webradio {
|
||||
class WebRadioService;
|
||||
} // namespace webradio
|
||||
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
@@ -60,8 +65,9 @@ namespace net {
|
||||
*
|
||||
* @dname NetBootstrap
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns optional softAp_, optional sta_, and webServer_. Must
|
||||
* outlive app_main; keep one instance alive for process lifetime.
|
||||
* @pubstate Owns optional softAp_, optional sta_, webServer_, and
|
||||
* sigmaStudio_. Must outlive app_main; keep one instance alive
|
||||
* for process lifetime.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
@@ -79,6 +85,7 @@ public:
|
||||
* @param stations Station preset service for REST routes.
|
||||
* @param integration Application orchestration for preset recall.
|
||||
* @param ota Firmware OTA service for POST /api/system/ota.
|
||||
* @param webRadio Streaming config for GET/POST /api/streaming.
|
||||
* @param companionChips Boot flags exposed on GET /api/health.
|
||||
* @param deviceIdentity EEPROM-derived SSID, hostname, and serial.
|
||||
* @return NetBootstrap on success, or a NetError.
|
||||
@@ -93,6 +100,7 @@ public:
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
ota::OtaService& ota,
|
||||
webradio::WebRadioService& webRadio,
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity);
|
||||
|
||||
@@ -106,12 +114,14 @@ public:
|
||||
* @param softAp Optional SoftAP mode host.
|
||||
* @param sta Optional STA client instance.
|
||||
* @param webServer HTTP server instance.
|
||||
* @param sigmaStudio SigmaStudio TCP:8086 bridge instance.
|
||||
* @param state Initial network state.
|
||||
* @pubstate Transfers ownership of optional network resources.
|
||||
*/
|
||||
NetBootstrap(std::optional<SoftApHost> softAp,
|
||||
std::optional<StaClient> sta,
|
||||
SetupWebServer webServer,
|
||||
SigmaStudioTcpServer sigmaStudio,
|
||||
NetState state);
|
||||
|
||||
/**
|
||||
@@ -119,7 +129,7 @@ public:
|
||||
*
|
||||
* @dname NetBootstrap
|
||||
* @param other Source instance; left empty after the move.
|
||||
* @pubstate transfers softAp_, sta_, and webServer_ from other.
|
||||
* @pubstate transfers softAp_, sta_, webServer_, and sigmaStudio_ from other.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
@@ -132,7 +142,7 @@ public:
|
||||
* @dname operator=
|
||||
* @param other Source instance; left empty after the move.
|
||||
* @return Reference to this instance.
|
||||
* @pubstate transfers softAp_, sta_, and webServer_ from other.
|
||||
* @pubstate transfers softAp_, sta_, webServer_, and sigmaStudio_ from other.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
@@ -143,7 +153,7 @@ public:
|
||||
* @brief ~NetBootstrap — tear down network subsystems.
|
||||
*
|
||||
* @dname ~NetBootstrap
|
||||
* @pubstate destroys softAp_, sta_, and webServer_.
|
||||
* @pubstate destroys softAp_, sta_, webServer_, and sigmaStudio_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-06
|
||||
@@ -166,6 +176,7 @@ private:
|
||||
std::optional<SoftApHost> softAp_;
|
||||
std::optional<StaClient> sta_;
|
||||
SetupWebServer webServer_;
|
||||
SigmaStudioTcpServer sigmaStudio_;
|
||||
NetState state_;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,10 +37,12 @@ enum class NetError {
|
||||
WifiConfigFailed,
|
||||
WifiStartFailed,
|
||||
HttpServerStartFailed,
|
||||
TcpServerStartFailed,
|
||||
StaConnectTimeout,
|
||||
StaConnectFailed,
|
||||
StoreSaveFailed,
|
||||
CredentialsNotFound,
|
||||
WifiScanFailed,
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
@@ -50,6 +50,10 @@ namespace tuner {
|
||||
class TunerService;
|
||||
} // namespace tuner
|
||||
|
||||
namespace webradio {
|
||||
class WebRadioService;
|
||||
} // namespace webradio
|
||||
|
||||
struct httpd_handle;
|
||||
|
||||
namespace net {
|
||||
@@ -73,6 +77,7 @@ struct HttpRouteContext {
|
||||
station::StationService* stations; ///< Preset list REST routes.
|
||||
integration::IntegrationService* integration; ///< Preset recall orchestration.
|
||||
ota::OtaService* ota; ///< Firmware OTA streaming.
|
||||
webradio::WebRadioService* webRadio; ///< Streaming config REST routes.
|
||||
core::CompanionChipStatus companionChips; ///< Boot flags for /api/health.
|
||||
core::DeviceIdentity deviceIdentity; ///< EEPROM-derived unit identity.
|
||||
};
|
||||
@@ -152,6 +157,7 @@ public:
|
||||
* @param stations Station preset service for list REST routes.
|
||||
* @param integration Application orchestration for preset recall.
|
||||
* @param ota Firmware OTA service for POST /api/system/ota.
|
||||
* @param webRadio Streaming config for GET/POST /api/streaming.
|
||||
* @param companionChips Boot flags for GET /api/health.
|
||||
* @param deviceIdentity Unit identity for /api/health serialNumber.
|
||||
* @return Ok on success, or NetError::HttpServerStartFailed.
|
||||
@@ -167,6 +173,7 @@ public:
|
||||
station::StationService& stations,
|
||||
integration::IntegrationService& integration,
|
||||
ota::OtaService& ota,
|
||||
webradio::WebRadioService& webRadio,
|
||||
core::CompanionChipStatus companionChips,
|
||||
const core::DeviceIdentity& deviceIdentity);
|
||||
|
||||
@@ -179,7 +186,6 @@ private:
|
||||
bluetooth::BluetoothService* bluetooth_;
|
||||
station::StationService* stations_;
|
||||
integration::IntegrationService* integration_;
|
||||
HttpRouteContext routeContext_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @file SigmaStudioTcpServer.hpp
|
||||
* @brief TCP bridge exposing the ADAU1701 to SigmaStudio's Remote Connection.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Implements the subset of SigmaStudio's TCPi wire protocol needed for
|
||||
* Connect, Link Compile Download, register read/write, and runtime
|
||||
* safeload parameter updates, ported from the ADAU1701-TCPi-ESP32
|
||||
* reference project (https://github.com/rarranzb/ADAU1701-TCPi-ESP32,
|
||||
* MIT) onto DigiRadio's existing I2C plumbing
|
||||
* (components/drivers/adau1701/src/SigmaStudioFW.c). A completed Link
|
||||
* Compile Download is persisted to the `dsp` flash partition via
|
||||
* adau1701::FlashDspProgramSource::storeBlob(), so it becomes the
|
||||
* program DigiRadio boots with next time — same mechanism as
|
||||
* POST /api/dsp/program.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "net/NetError.hpp"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <expected>
|
||||
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief SigmaStudioTcpServer — port 8086 bridge to the ADAU1701.
|
||||
*
|
||||
* @dname SigmaStudioTcpServer
|
||||
* @return n/a (type)
|
||||
* @pubstate Owns a listening socket and a FreeRTOS task for the process
|
||||
* lifetime once started(); stop()/destructor tear both down.
|
||||
* Requires adau1701::Adau1701Driver::boot() to have already
|
||||
* bound the I2C device handle via sigma_studio_set_device().
|
||||
* Only one instance may be started at a time: the accept task
|
||||
* reads the listen fd from a process-lifetime singleton (see
|
||||
* activeListenFd() in the .cpp) rather than a captured `this`,
|
||||
* since instances are constructed as locals and move-relocated
|
||||
* into NetBootstrap — same rationale as SetupWebServer's
|
||||
* routeContextStorage().
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
class SigmaStudioTcpServer {
|
||||
public:
|
||||
/**
|
||||
* @brief SigmaStudioTcpServer — construct an unstarted server.
|
||||
*
|
||||
* @dname SigmaStudioTcpServer
|
||||
* @pubstate clears listenFd_ and task_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
SigmaStudioTcpServer();
|
||||
|
||||
/**
|
||||
* @brief ~SigmaStudioTcpServer — stop the server if running.
|
||||
*
|
||||
* @dname ~SigmaStudioTcpServer
|
||||
* @pubstate deletes task_ and closes listenFd_ when started.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
~SigmaStudioTcpServer();
|
||||
|
||||
SigmaStudioTcpServer(const SigmaStudioTcpServer&) = delete;
|
||||
SigmaStudioTcpServer& operator=(const SigmaStudioTcpServer&) = delete;
|
||||
|
||||
/**
|
||||
* @brief SigmaStudioTcpServer — move-construct, transferring ownership.
|
||||
*
|
||||
* @dname SigmaStudioTcpServer
|
||||
* @param other Source server; left stopped after the move.
|
||||
* @pubstate takes ownership of other.listenFd_ and other.task_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
SigmaStudioTcpServer(SigmaStudioTcpServer&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief operator= — move-assign, transferring ownership.
|
||||
*
|
||||
* @dname operator=
|
||||
* @param other Source server; left stopped after the move.
|
||||
* @return Reference to this instance.
|
||||
* @pubstate takes ownership of other.listenFd_ and other.task_.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
SigmaStudioTcpServer& operator=(SigmaStudioTcpServer&& other) noexcept;
|
||||
|
||||
/**
|
||||
* @brief start — open the listening socket and spawn the server task.
|
||||
*
|
||||
* @dname start
|
||||
* @return Ok on success, or NetError::TcpServerStartFailed.
|
||||
* @pubstate writes listenFd_ and task_ on success.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, NetError> start();
|
||||
|
||||
private:
|
||||
void stop() noexcept;
|
||||
|
||||
/** @brief FreeRTOS task entry: accepts and serves one client at a time. */
|
||||
static void acceptLoopTask(void* arg);
|
||||
|
||||
int listenFd_;
|
||||
TaskHandle_t task_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "core/WifiCredentials.hpp"
|
||||
#include "net/NetError.hpp"
|
||||
|
||||
#include "esp_event.h"
|
||||
|
||||
#include <expected>
|
||||
#include <string_view>
|
||||
|
||||
@@ -107,6 +109,8 @@ public:
|
||||
|
||||
private:
|
||||
bool connected_;
|
||||
esp_event_handler_instance_t wifiHandler_;
|
||||
esp_event_handler_instance_t ipHandler_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file WifiScanner.hpp
|
||||
* @brief Blocking Wi-Fi scan wrapper for the setup web UI.
|
||||
*
|
||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||
*
|
||||
* Copyright 2026 Michele Bigi
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-05
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/WifiScannedNetwork.hpp"
|
||||
#include "net/NetError.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <vector>
|
||||
|
||||
namespace net {
|
||||
|
||||
/**
|
||||
* @brief WifiScanner — runs esp_wifi scan and maps results to core DTOs.
|
||||
*
|
||||
* @dname WifiScanner
|
||||
* @return n/a (type)
|
||||
* @pubstate Stateless shell helper; assumes esp_wifi_init() already ran.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-05
|
||||
*/
|
||||
class WifiScanner {
|
||||
public:
|
||||
/**
|
||||
* @brief scanNearby — list visible access points sorted by RSSI.
|
||||
*
|
||||
* @dname scanNearby
|
||||
* @return Deduped networks on success, or NetError::WifiScanFailed.
|
||||
* @pubstate Temporarily switches AP-only mode to APSTA when required.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-05
|
||||
*/
|
||||
[[nodiscard]] static std::expected<std::vector<core::WifiScannedNetwork>,
|
||||
NetError>
|
||||
scanNearby();
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
Reference in New Issue
Block a user