Add generic ADAU1701 parameter access (GET/PUT /api/dsp/param)

New core::IDsp::writeRawParam(address, value) safeloads any named
Parameter RAM cell in the compiled SigmaStudio program (74 cells: mixer/
Beep1/PEQ coefficients/limiter thresholds/etc — the same set SigmaStudio's
own Remote Connection, components/net/SigmaStudioTcpServer, already has
full access to). Value is a plain SigmaStudio floating coefficient,
converted to ADAU 8.23 fixpoint via the existing core::floatToFixpoint823.

Adau1701ParamTable.hpp holds the name->address table extracted from
Firmware/ADAU1701-Firmware/DigiRadio_IC_1_PARAM.h; core::DspParamJson
handles the wire format without core/ depending on the adau1701 driver
(net/SetupWebServer.cpp bridges the two, consistent with how other
JSON DTOs are supplied their data by the net layer).

GET /api/dsp/params lists every cell (name + address) for discovery.
PUT /api/dsp/param {"name":...,"value":...} writes one. Deliberately no
domain validation, matching the trust level already implied by the
existing SigmaStudio TCP bridge being reachable on the same network.
HTTP routes were registered in the SetupWebServer.cpp change committed
alongside the FM band scan feature (3a10ed7).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
This commit is contained in:
2026-08-18 07:51:09 +02:00
co-authored by Claude Sonnet 5
parent 7e65394100
commit 39984a97d1
14 changed files with 563 additions and 0 deletions
@@ -24,6 +24,7 @@ add_library(digiradio_core STATIC
"${CORE_SRC_DIR}/WifiScanJson.cpp"
"${CORE_SRC_DIR}/EmbeddedBlobReader.cpp"
"${CORE_SRC_DIR}/TunerJson.cpp"
"${CORE_SRC_DIR}/DspParamJson.cpp"
"${CORE_SRC_DIR}/FrequencyKHz.cpp"
"${CORE_SRC_DIR}/GainDb.cpp"
"${CORE_SRC_DIR}/FrequencyHz.cpp"
@@ -89,6 +90,10 @@ add_executable(tuner_json_test tuner_json_test.cpp)
target_link_libraries(tuner_json_test PRIVATE digiradio_core)
add_test(NAME tuner_json_test COMMAND tuner_json_test)
add_executable(dsp_param_json_test dsp_param_json_test.cpp)
target_link_libraries(dsp_param_json_test PRIVATE digiradio_core)
add_test(NAME dsp_param_json_test COMMAND dsp_param_json_test)
add_executable(frequency_khz_test frequency_khz_test.cpp)
target_link_libraries(frequency_khz_test PRIVATE digiradio_core)
add_test(NAME frequency_khz_test COMMAND frequency_khz_test)
@@ -0,0 +1,129 @@
/**
* @file dsp_param_json_test.cpp
* @brief Host tests for DspParamJson parse/serialise.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-08-18
*/
#include "core/DspParamJson.hpp"
#include <cstdlib>
#include <iostream>
namespace {
bool expectEqual(const std::string& actual, const std::string& expected)
{
if (actual != expected) {
std::cerr << "expected: " << expected << "\n actual: " << actual
<< "\n";
return false;
}
return true;
}
[[nodiscard]] int runParseWriteTest()
{
const auto parsed = core::parseDspParamWriteJson(
R"({"name":"BEEP1_KICK","value":1.0})");
if (!parsed) {
std::cerr << "expected parse success\n";
return EXIT_FAILURE;
}
if (parsed->name != "BEEP1_KICK" || parsed->value != 1.0F) {
std::cerr << "unexpected parsed fields\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runParseNegativeValueTest()
{
const auto parsed = core::parseDspParamWriteJson(
R"({"name":"LIMITER1_THRESHOLD","value":-0.5})");
if (!parsed || parsed->value != -0.5F) {
std::cerr << "expected negative value to parse\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runParseMissingFieldTest()
{
const auto parsed = core::parseDspParamWriteJson(R"({"name":"X"})");
if (parsed) {
std::cerr << "expected missing value to fail\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runParseInvalidJsonTest()
{
const auto parsed = core::parseDspParamWriteJson("not json");
if (parsed) {
std::cerr << "expected invalid json to fail\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runSerialiseListTest()
{
const std::vector<core::DspParamInfo> params = {
{"BEEP1_ENABLE", 0},
{"BEEP1_KICK", 1},
};
const std::string json = core::serializeDspParamListJson(params);
if (!expectEqual(
json,
R"({"params":[{"name":"BEEP1_ENABLE","address":0},{"name":"BEEP1_KICK","address":1}]})")) {
return EXIT_FAILURE;
}
const std::string emptyJson = core::serializeDspParamListJson({});
if (!expectEqual(emptyJson, R"({"params":[]})")) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runSerialiseErrorTest()
{
const std::string json = core::serializeDspParamErrorJson("not_found");
if (!expectEqual(json, R"({"status":"error","reason":"not_found"})")) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
{
if (runParseWriteTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runParseNegativeValueTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runParseMissingFieldTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runParseInvalidJsonTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runSerialiseListTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runSerialiseErrorTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -75,6 +75,12 @@ public:
{
return {};
}
[[nodiscard]] std::expected<void, core::DspError> writeRawParam(
unsigned, float) override
{
return {};
}
};
class TrackingTuner final : public core::ITuner {