Add full FM band scan for building a station/channel list
New TunerService::scanFullFmBand(): tunes to the band bottom then seeks up repeatedly (reusing the existing hardware-seek + RDS-name-poll machinery from scanForStation()) until the sweep wraps back around, collecting every station that clears the existing scan RSSI/SNR thresholds. Returns the list without touching saved presets or leaving the tuner in any particular place — callers decide what to do with the results. New POST /api/tuner/scan/full endpoint (core::TunerFmScannedStation DTO, serializeTunerFmBandScanJson). Blocks for the whole sweep like the existing /api/tuner/scan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
This commit is contained in:
@@ -95,6 +95,23 @@ struct TunerScanResult {
|
||||
std::optional<BroadcastLabel> stationName; ///< RDS PS or DAB label when known.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief TunerFmScannedStation — one hit from a full FM band scan.
|
||||
*
|
||||
* @dname TunerFmScannedStation
|
||||
* @return n/a (type)
|
||||
* @pubstate Plain DTO built by TunerService::scanFullFmBand().
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-18
|
||||
*/
|
||||
struct TunerFmScannedStation {
|
||||
FrequencyKHz frequency; ///< Centre frequency of the hit.
|
||||
std::int8_t rssiDbuV; ///< RSSI at the time of the hit.
|
||||
std::int8_t snrDb; ///< SNR at the time of the hit.
|
||||
std::optional<BroadcastLabel> stationName; ///< RDS PS name, if decoded in time.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief serializeTunerStatusJson — serialise a tuner snapshot for GET status.
|
||||
*
|
||||
@@ -207,4 +224,19 @@ struct TunerScanResult {
|
||||
*/
|
||||
[[nodiscard]] std::string serializeTunerScanJson(const TunerScanResult& result);
|
||||
|
||||
/**
|
||||
* @brief serializeTunerFmBandScanJson — serialise a full FM band scan.
|
||||
*
|
||||
* @dname serializeTunerFmBandScanJson
|
||||
* @param stations Hits from TunerService::scanFullFmBand(), in the
|
||||
* order the sweep found them (ascending frequency).
|
||||
* @return JSON object with a `stations` array for the HTTP response body.
|
||||
* @pubstate none
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-08-18
|
||||
*/
|
||||
[[nodiscard]] std::string serializeTunerFmBandScanJson(
|
||||
const std::vector<TunerFmScannedStation>& stations);
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -316,4 +316,24 @@ std::string serializeTunerScanJson(const TunerScanResult& result)
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string serializeTunerFmBandScanJson(
|
||||
const std::vector<TunerFmScannedStation>& stations)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "{\"stations\":[";
|
||||
for (std::size_t i = 0; i < stations.size(); ++i) {
|
||||
if (i > 0U) {
|
||||
out << ',';
|
||||
}
|
||||
const auto& s = stations[i];
|
||||
out << "{\"frequency_khz\":" << s.frequency.value()
|
||||
<< ",\"rssi_dbuv\":" << static_cast<int>(s.rssiDbuV)
|
||||
<< ",\"snr_db\":" << static_cast<int>(s.snrDb);
|
||||
appendOptionalLabel(out, "station_name", s.stationName);
|
||||
out << "}";
|
||||
}
|
||||
out << "]}";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
|
||||
@@ -139,6 +139,29 @@ namespace {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runTunerFmBandScanSerialiseTest()
|
||||
{
|
||||
core::TunerFmScannedStation withName{
|
||||
*core::FrequencyKHz::tryFromKhz(98300U), 12, 13,
|
||||
core::BroadcastLabel::tryFromChipBytes("RADIO 1")};
|
||||
core::TunerFmScannedStation withoutName{
|
||||
*core::FrequencyKHz::tryFromKhz(105500U), -4, -2, std::nullopt};
|
||||
|
||||
const std::string json =
|
||||
core::serializeTunerFmBandScanJson({withName, withoutName});
|
||||
if (!expectEqual(
|
||||
json,
|
||||
R"({"stations":[{"frequency_khz":98300,"rssi_dbuv":12,"snr_db":13,"station_name":"RADIO 1"},{"frequency_khz":105500,"rssi_dbuv":-4,"snr_db":-2}]})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const std::string emptyJson = core::serializeTunerFmBandScanJson({});
|
||||
if (!expectEqual(emptyJson, R"({"stations":[]})")) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] int runTunerStatusMetadataSerialiseTest()
|
||||
{
|
||||
core::TunerStatus status = {};
|
||||
@@ -240,5 +263,8 @@ int main()
|
||||
if (runTunerSeekParseTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (runTunerFmBandScanSerialiseTest() != EXIT_SUCCESS) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user