Add Slice 4 tuner stack with AGENTS-compliant core types and HTTP API.

Introduces Si4684/ADAU1701 drivers, TunerService, FrequencyKHz,
SeekDirection, /api/tuner routes without file-scope globals, host tests,
and firmware blob tooling (binaries remain gitignored).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 16:17:56 +02:00
co-authored by Cursor
parent c83c9bd765
commit 81d404a1df
68 changed files with 15411 additions and 139 deletions
+4 -2
View File
@@ -1,5 +1,7 @@
idf_component_register(
SRCS "main.cpp"
SRCS
"main.cpp"
"hardware_bootstrap.cpp"
INCLUDE_DIRS "."
REQUIRES core net secure_store
REQUIRES core net secure_store adau1701 si4684 tuner
)
+87
View File
@@ -0,0 +1,87 @@
/**
* @file hardware_bootstrap.cpp
* @brief HardwareBootstrap implementation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#include "hardware_bootstrap.hpp"
#include "adau1701/Adau1701Driver.hpp"
#include "board_pins.hpp"
#include "si4684/Si4684Band.hpp"
#include "si4684/Si4684Driver.hpp"
#include "si4684/Si4684EmbeddedImages.hpp"
#include "si4684/Si4684Tuner.hpp"
#include "driver/spi_master.h"
#include "esp_log.h"
namespace hardware {
namespace {
constexpr char kTag[] = "hw_boot";
si4684::Si4684EmbeddedImages gImages;
si4684::Si4684Driver gSi4684(
si4684::Si4684Pins{
.spiHost = SPI2_HOST,
.csGpio = board::pins::Si4684Cs,
.misoGpio = board::pins::Si4684Miso,
.mosiGpio = board::pins::Si4684Mosi,
.sclkGpio = board::pins::Si4684Sclk,
.rstbGpio = board::pins::Si4684Rstb,
.intbGpio = board::pins::Si4684Intb,
},
gImages.romPatch(),
gImages.dabFirmware(),
gImages.fmFirmware());
si4684::Si4684Tuner gSi4684Tuner(gSi4684);
adau1701::Adau1701Driver gAdau1701(
adau1701::Adau1701Pins{
.i2cSda = board::pins::Adau1701Sda,
.i2cScl = board::pins::Adau1701Scl,
.resetGpio = board::pins::Adau1701Reset,
.i2cAddr7 = board::pins::Adau1701Addr,
});
bool gReady = false;
} // namespace
std::expected<void, HardwareBootError> HardwareBootstrap::boot()
{
if (gReady) {
return {};
}
if (auto tunerResult = gSi4684.boot(si4684::Si4684Band::Dab); !tunerResult) {
ESP_LOGE(kTag, "Si4684 boot failed");
return std::unexpected(HardwareBootError::Si4684BootFailed);
}
if (!gAdau1701.isBooted()) {
auto dspResult = gAdau1701.boot();
if (!dspResult) {
ESP_LOGE(kTag, "ADAU1701 boot failed");
return std::unexpected(HardwareBootError::Adau1701BootFailed);
}
}
gReady = true;
ESP_LOGI(kTag, "companion chips ready");
return {};
}
si4684::Si4684Tuner& HardwareBootstrap::si4684Tuner()
{
return gSi4684Tuner;
}
} // namespace hardware
+80
View File
@@ -0,0 +1,80 @@
/**
* @file hardware_bootstrap.hpp
* @brief Boot Si4684 and ADAU1701 companion chips at application start.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#pragma once
#include <expected>
namespace si4684 {
class Si4684Tuner;
} // namespace si4684
namespace hardware {
/**
* @brief HardwareBootError — companion-chip boot failure causes.
*
* @dname HardwareBootError
* @return n/a (type)
* @pubstate n/a
*
* @author Michele Bigi
* @date 2026-07-06
*/
enum class HardwareBootError {
Si4684BootFailed,
Adau1701BootFailed,
};
/**
* @brief HardwareBootstrap — orchestrates Si4684 then ADAU1701 bring-up.
*
* @dname HardwareBootstrap
* @return n/a (type)
* @pubstate Owns static driver instances for the process lifetime. Must be
* called once from app_main before network start.
*
* @author Michele Bigi
* @date 2026-07-06
*/
class HardwareBootstrap {
public:
/**
* @brief boot — load Si4684 DAB image then replay ADAU1701 program.
*
* @dname boot
* @return Ok on success, or HardwareBootError.
* @pubstate constructs static Si4684 and ADAU1701 drivers on first call.
*
* Fail-closed: callers must not start Wi-Fi when this returns an error.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static std::expected<void, HardwareBootError> boot();
/**
* @brief si4684Tuner — borrow the Si4684 ITuner adapter after boot.
*
* @dname si4684Tuner
* @return Reference to the static Si4684Tuner instance.
* @pubstate reads static storage initialised by boot().
*
* Valid only after a successful boot() call in the same process.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static si4684::Si4684Tuner& si4684Tuner();
};
} // namespace hardware
+15 -22
View File
@@ -7,17 +7,14 @@
* 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
*
* @author Michele Bigi
* @date 2026-07-06
*/
#include "hardware_bootstrap.hpp"
#include "net/NetBootstrap.hpp"
#include "secure_store/NvsSecureStore.hpp"
#include "tuner/TunerService.hpp"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
@@ -28,16 +25,6 @@ namespace {
constexpr char kTag[] = "digiradio";
constexpr TickType_t kHeartbeatPeriod = pdMS_TO_TICKS(5000);
/**
* @brief heartbeatTask — periodic alive log for bring-up verification.
*
* @dname heartbeatTask
* @param arg Unused task parameter.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
void heartbeatTask(void* arg)
{
(void)arg;
@@ -50,24 +37,30 @@ void heartbeatTask(void* arg)
} // namespace
/**
* @brief app_main — ESP-IDF application entry point.
* @brief app_main — ESP-IDF entry point for DigiRadio firmware.
*
* @dname app_main
* @pubstate starts NvsSecureStore, NetBootstrap, and the heartbeat task.
*
* Slice 2: joins a stored Wi-Fi network when credentials exist, otherwise
* opens the DigiRadio-setup SoftAP for provisioning via the web UI.
* @pubstate boots companion chips, network stack, and heartbeat task.
*
* @author Michele Bigi
* @date 2026-07-06
*/
extern "C" void app_main()
{
ESP_LOGI(kTag, "DigiRadio firmware boot — Slice 2");
ESP_LOGI(kTag, "DigiRadio firmware boot — Slice 4");
auto hwResult = hardware::HardwareBootstrap::boot();
if (!hwResult) {
ESP_LOGE(kTag, "companion chip boot failed — halting");
return;
}
static tuner::TunerService tunerService(
hardware::HardwareBootstrap::si4684Tuner());
static secure_store::NvsSecureStore store;
auto netResult = net::NetBootstrap::start(store);
auto netResult = net::NetBootstrap::start(store, tunerService);
if (!netResult) {
ESP_LOGE(kTag, "network bootstrap failed");
return;