Add BT1035 pairing and station presets (fw 0.7.0).
Expose discoverable mode and A2DP control over REST, add persisted DAB/FM preset list with web UI, and document gaps in docs/TODO.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,12 @@ add_library(digiradio_core STATIC
|
||||
"${CORE_SRC_DIR}/AudioProfile.cpp"
|
||||
"${CORE_SRC_DIR}/AudioProfileJson.cpp"
|
||||
"${CORE_SRC_DIR}/Bt1035At.cpp"
|
||||
"${CORE_SRC_DIR}/StationName.cpp"
|
||||
"${CORE_SRC_DIR}/PresetSlot.cpp"
|
||||
"${CORE_SRC_DIR}/Station.cpp"
|
||||
"${CORE_SRC_DIR}/StationList.cpp"
|
||||
"${CORE_SRC_DIR}/StationListJson.cpp"
|
||||
"${CORE_SRC_DIR}/BluetoothJson.cpp"
|
||||
)
|
||||
target_include_directories(digiradio_core PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||
@@ -72,6 +78,10 @@ add_executable(enhancements_design_test enhancements_design_test.cpp)
|
||||
target_link_libraries(enhancements_design_test PRIVATE digiradio_core)
|
||||
add_test(NAME enhancements_design_test COMMAND enhancements_design_test)
|
||||
|
||||
add_executable(station_list_test station_list_test.cpp)
|
||||
target_link_libraries(station_list_test PRIVATE digiradio_core)
|
||||
add_test(NAME station_list_test COMMAND station_list_test)
|
||||
|
||||
add_executable(bt1035_at_test bt1035_at_test.cpp)
|
||||
target_link_libraries(bt1035_at_test PRIVATE digiradio_core)
|
||||
add_test(NAME bt1035_at_test COMMAND bt1035_at_test)
|
||||
|
||||
@@ -54,6 +54,23 @@ namespace {
|
||||
std::cerr << "unexpected parse failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (core::parseBt1035AtResponse("+A2DPSTAT=3\r\nOK\r\n")
|
||||
!= core::Bt1035AtResponseKind::Ok) {
|
||||
std::cerr << "expected multiline OK detection\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const std::string pairOn =
|
||||
core::buildBt1035AtLine(core::Bt1035AtCommand::PairDiscoverable);
|
||||
if (pairOn != "AT+PAIR=1\r\n") {
|
||||
std::cerr << "expected AT+PAIR=1 line\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const auto a2dp =
|
||||
core::parseBt1035A2dpStatResponse("+A2DPSTAT=4\r\nOK\r\n");
|
||||
if (!a2dp || *a2dp != core::Bt1035A2dpState::Streaming) {
|
||||
std::cerr << "expected streaming A2DP state\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @file station_list_test.cpp
|
||||
* @brief Host tests for station preset list domain and JSON.
|
||||
*
|
||||
* 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 "core/FrequencyKHz.hpp"
|
||||
#include "core/PresetSlot.hpp"
|
||||
#include "core/Station.hpp"
|
||||
#include "core/StationList.hpp"
|
||||
#include "core/StationListError.hpp"
|
||||
#include "core/StationListJson.hpp"
|
||||
#include "core/StationName.hpp"
|
||||
#include "core/TunerBand.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] core::Station makeFmStation(const char* name, std::uint32_t khz,
|
||||
unsigned slot)
|
||||
{
|
||||
auto label = core::StationName::tryFrom(name);
|
||||
auto frequency = core::FrequencyKHz::tryFromKhz(khz);
|
||||
auto preset = core::PresetSlot::tryFrom(slot);
|
||||
return core::Station(*label, core::TunerBand::Fm, 0U, std::nullopt,
|
||||
std::nullopt, *frequency, *preset);
|
||||
}
|
||||
|
||||
[[nodiscard]] int runDuplicateRejectionTest()
|
||||
{
|
||||
core::StationList list;
|
||||
if (auto added = list.add(makeFmStation("Radio 2", 88500U, 1U)); !added) {
|
||||
std::cerr << "first add failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (list.add(makeFmStation("Radio 2 dup", 88500U, 2U))) {
|
||||
std::cerr << "expected duplicate tune target rejection\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runJsonRoundTripTest()
|
||||
{
|
||||
core::StationList list;
|
||||
if (auto added = list.add(makeFmStation("Jazz FM", 101500U, 3U)); !added) {
|
||||
std::cerr << "add failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const std::string json = core::serializeStationListJson(list);
|
||||
auto parsed = core::parseStationListJson(json);
|
||||
if (!parsed || parsed->stations().size() != 1U) {
|
||||
std::cerr << "round-trip parse failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (parsed->stations()[0U].name().value() != "Jazz FM") {
|
||||
std::cerr << "name mismatch after round-trip\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runParseStationJsonTest()
|
||||
{
|
||||
const auto station = core::parseStationJson(
|
||||
R"({"name":"BBC","band":"dab","dab_freq_index":12,"dab_service_id":42,"dab_component_id":1})");
|
||||
if (!station || station->band() != core::TunerBand::Dab
|
||||
|| station->dabFreqIndex() != 12U) {
|
||||
std::cerr << "dab station parse failed\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
if (runDuplicateRejectionTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runJsonRoundTripTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runParseStationJsonTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user