Add updatable ADAU1701 program via flash partition and HTTP API.

Decouple the SigmaStudio RAM download from compiled-in adau1701_program.c:
DRAD v1 blobs in the dsp partition with embedded fallback, POST /api/dsp/program,
and host-tested blob parse/serialize helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 09:21:49 +02:00
co-authored by Cursor
parent cd94e36a9d
commit cc4a03168e
31 changed files with 1302 additions and 50 deletions
@@ -46,11 +46,18 @@ add_library(digiradio_core STATIC
"${CORE_SRC_DIR}/BroadcastLabel.cpp"
"${CORE_SRC_DIR}/RdsMetadataAccumulator.cpp"
"${CORE_SRC_DIR}/DabDynamicLabelAccumulator.cpp"
"${CORE_SRC_DIR}/DspProgram.cpp"
"${CORE_SRC_DIR}/RegisterWrite.cpp"
"${CORE_SRC_DIR}/DspProgramBlob.cpp"
)
target_include_directories(digiradio_core PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
)
add_executable(dsp_program_blob_test dsp_program_blob_test.cpp)
target_link_libraries(dsp_program_blob_test PRIVATE digiradio_core)
add_test(NAME dsp_program_blob_test COMMAND dsp_program_blob_test)
add_executable(device_identity_test device_identity_test.cpp)
target_link_libraries(device_identity_test PRIVATE digiradio_core)
add_test(NAME device_identity_test COMMAND device_identity_test)
@@ -0,0 +1,116 @@
/**
* @file dsp_program_blob_test.cpp
* @brief Host tests for ADAU1701 program blob parse/serialise.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-07
*/
#include "core/DspProgram.hpp"
#include "core/DspProgramBlob.hpp"
#include "core/DspProgramError.hpp"
#include "core/RegisterWrite.hpp"
#include <cstdlib>
#include <iostream>
#include <vector>
namespace {
[[nodiscard]] core::DspProgram sampleProgram()
{
return core::DspProgram(std::vector<core::RegisterWrite>{
core::RegisterWrite(0x081CU, {0x00U, 0x1CU}),
core::RegisterWrite(0x0400U, std::vector<std::uint8_t>(16U, 0xABU)),
});
}
[[nodiscard]] int runRoundTripTest()
{
const core::DspProgram original = sampleProgram();
const std::vector<std::uint8_t> blob =
core::serializeDspProgramBlob(original);
const auto parsed = core::parseDspProgramBlob(blob);
if (!parsed) {
std::cerr << "round-trip parse failed\n";
return EXIT_FAILURE;
}
if (parsed->writes().size() != original.writes().size()) {
std::cerr << "write count mismatch\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runEmptyTest()
{
const std::vector<std::uint8_t> empty;
const auto parsed = core::parseDspProgramBlob(empty);
if (parsed || parsed.error() != core::DspProgramError::Empty) {
std::cerr << "expected empty error\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runTruncatedTest()
{
const std::vector<std::uint8_t> blob = {'D', 'R', 'A', 'D', 1, 0, 1, 0};
const auto parsed = core::parseDspProgramBlob(blob);
if (parsed || parsed.error() != core::DspProgramError::Truncated) {
std::cerr << "expected truncated error\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runBadCrcTest()
{
std::vector<std::uint8_t> blob = core::serializeDspProgramBlob(sampleProgram());
blob.back() ^= 0xFFU;
const auto parsed = core::parseDspProgramBlob(blob);
if (parsed || parsed.error() != core::DspProgramError::BadCrc) {
std::cerr << "expected bad_crc error\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runBadMagicTest()
{
std::vector<std::uint8_t> blob = core::serializeDspProgramBlob(sampleProgram());
blob[0] = 'X';
const auto parsed = core::parseDspProgramBlob(blob);
if (parsed || parsed.error() != core::DspProgramError::InvalidMagic) {
std::cerr << "expected invalid_magic error\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
{
if (runRoundTripTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runEmptyTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runTruncatedTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runBadCrcTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runBadMagicTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}