Release fw 0.8.4: doc sync, System UI, BT/FM polish.
Align README, manual, and backlog to 0.8.4; add Web UI System tab for OTA/DSP uploads, serial in health header, FM seek down, and BT1035 paired list plus auto-reconnect API. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,7 +17,9 @@
|
||||
#include "core/Bt1035At.hpp"
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace bt1035 {
|
||||
|
||||
@@ -183,11 +185,62 @@ public:
|
||||
[[nodiscard]] std::expected<void, Bt1035Error> setDeviceName(
|
||||
std::string_view name);
|
||||
|
||||
/**
|
||||
* @brief queryDeviceName — read GAP friendly name (AT+NAME).
|
||||
*
|
||||
* @dname queryDeviceName
|
||||
* @return Module name on success, or Bt1035Error.
|
||||
* @pubstate writes UART; parses +NAME= from the reply.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<std::string, Bt1035Error> queryDeviceName();
|
||||
|
||||
/**
|
||||
* @brief setAutoReconnect — configure power-on reconnect (AT+AUTOCONN).
|
||||
*
|
||||
* @dname setAutoReconnect
|
||||
* @param times 0 off, 1–15 attempts per Feasycom manual.
|
||||
* @return Ok on success, or Bt1035Error.
|
||||
* @pubstate writes UART.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Bt1035Error> setAutoReconnect(
|
||||
std::uint8_t times);
|
||||
|
||||
/**
|
||||
* @brief queryAutoReconnect — read AT+AUTOCONN setting.
|
||||
*
|
||||
* @dname queryAutoReconnect
|
||||
* @return Reconnect count 0–15, or Bt1035Error.
|
||||
* @pubstate writes UART.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<std::uint8_t, Bt1035Error> queryAutoReconnect();
|
||||
|
||||
/**
|
||||
* @brief queryPairedList — enumerate paired remotes (AT+PLIST).
|
||||
*
|
||||
* @dname queryPairedList
|
||||
* @return Parsed paired devices, or Bt1035Error.
|
||||
* @pubstate writes UART; may take longer than a single-line command.
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<std::vector<core::Bt1035PairedDevice>, Bt1035Error>
|
||||
queryPairedList();
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::expected<void, Bt1035Error> ensureBooted() const;
|
||||
[[nodiscard]] std::expected<void, Bt1035Error> runInitSequence();
|
||||
[[nodiscard]] std::expected<std::string, Bt1035Error> transmitAndCollect(
|
||||
std::string_view commandLine);
|
||||
std::string_view commandLine, int timeoutMs = kResponseTimeoutMs);
|
||||
[[nodiscard]] std::expected<void, Bt1035Error> transmitAndExpectOk(
|
||||
std::string_view commandLine);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace bt1035 {
|
||||
|
||||
@@ -66,7 +67,7 @@ std::expected<void, Bt1035Error> Bt1035Driver::ensureBooted() const
|
||||
}
|
||||
|
||||
std::expected<std::string, Bt1035Error> Bt1035Driver::transmitAndCollect(
|
||||
std::string_view commandLine)
|
||||
std::string_view commandLine, int timeoutMs)
|
||||
{
|
||||
const int written = uart_write_bytes(static_cast<uart_port_t>(uartPort_),
|
||||
commandLine.data(),
|
||||
@@ -79,7 +80,7 @@ std::expected<std::string, Bt1035Error> Bt1035Driver::transmitAndCollect(
|
||||
std::array<char, 128> buffer{};
|
||||
std::string accumulated;
|
||||
const TickType_t deadline =
|
||||
xTaskGetTickCount() + pdMS_TO_TICKS(kResponseTimeoutMs);
|
||||
xTaskGetTickCount() + pdMS_TO_TICKS(timeoutMs);
|
||||
|
||||
while (xTaskGetTickCount() < deadline) {
|
||||
const int received = uart_read_bytes(static_cast<uart_port_t>(uartPort_),
|
||||
@@ -175,6 +176,68 @@ std::expected<void, Bt1035Error> Bt1035Driver::setDeviceName(
|
||||
return transmitAndExpectOk(line);
|
||||
}
|
||||
|
||||
std::expected<std::string, Bt1035Error> Bt1035Driver::queryDeviceName()
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready) {
|
||||
return std::unexpected(ready.error());
|
||||
}
|
||||
auto response =
|
||||
transmitAndCollect(core::buildBt1035AtLine(core::Bt1035AtCommand::QueryName));
|
||||
if (!response) {
|
||||
return std::unexpected(response.error());
|
||||
}
|
||||
auto parsed = core::parseBt1035NameResponse(*response);
|
||||
if (!parsed) {
|
||||
return std::unexpected(Bt1035Error::UnexpectedResponse);
|
||||
}
|
||||
return *parsed;
|
||||
}
|
||||
|
||||
std::expected<void, Bt1035Error> Bt1035Driver::setAutoReconnect(
|
||||
std::uint8_t times)
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready) {
|
||||
return ready;
|
||||
}
|
||||
return transmitAndExpectOk(core::buildBt1035SetAutoConnLine(times));
|
||||
}
|
||||
|
||||
std::expected<std::uint8_t, Bt1035Error> Bt1035Driver::queryAutoReconnect()
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready) {
|
||||
return std::unexpected(ready.error());
|
||||
}
|
||||
auto response = transmitAndCollect(
|
||||
core::buildBt1035AtLine(core::Bt1035AtCommand::QueryAutoConn));
|
||||
if (!response) {
|
||||
return std::unexpected(response.error());
|
||||
}
|
||||
auto parsed = core::parseBt1035AutoConnResponse(*response);
|
||||
if (!parsed) {
|
||||
return std::unexpected(Bt1035Error::UnexpectedResponse);
|
||||
}
|
||||
return *parsed;
|
||||
}
|
||||
|
||||
std::expected<std::vector<core::Bt1035PairedDevice>, Bt1035Error>
|
||||
Bt1035Driver::queryPairedList()
|
||||
{
|
||||
if (auto ready = ensureBooted(); !ready) {
|
||||
return std::unexpected(ready.error());
|
||||
}
|
||||
auto response = transmitAndCollect(
|
||||
core::buildBt1035AtLine(core::Bt1035AtCommand::QueryPairedList),
|
||||
4000);
|
||||
if (!response) {
|
||||
return std::unexpected(response.error());
|
||||
}
|
||||
auto parsed = core::parseBt1035PairedListResponse(*response);
|
||||
if (!parsed) {
|
||||
return std::unexpected(Bt1035Error::UnexpectedResponse);
|
||||
}
|
||||
return *parsed;
|
||||
}
|
||||
|
||||
std::expected<void, Bt1035Error> Bt1035Driver::runInitSequence()
|
||||
{
|
||||
for (const core::Bt1035AtCommand command : core::bootInitSequence()) {
|
||||
|
||||
@@ -341,6 +341,24 @@ public:
|
||||
std::uint32_t componentId,
|
||||
Si4684DigitalServiceType type = Si4684DigitalServiceType::Audio);
|
||||
|
||||
/**
|
||||
* @brief stopDabService — stop active DAB audio (STOP_DIGITAL_SERVICE).
|
||||
*
|
||||
* @dname stopDabService
|
||||
* @param serviceId Selected service identifier.
|
||||
* @param componentId Audio component within the service.
|
||||
* @param type Digital service type (audio by default).
|
||||
* @return Ok on success, or Si4684Error.
|
||||
* @pubstate sends STOP_DIGITAL_SERVICE (AN649 opcode 0x82).
|
||||
*
|
||||
* @author Michele Bigi
|
||||
* @date 2026-07-07
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, Si4684Error> stopDabService(
|
||||
std::uint32_t serviceId,
|
||||
std::uint32_t componentId,
|
||||
Si4684DigitalServiceType type = Si4684DigitalServiceType::Audio);
|
||||
|
||||
private:
|
||||
enum class Command : std::uint8_t {
|
||||
PowerUp = 0x01,
|
||||
@@ -357,6 +375,7 @@ private:
|
||||
FmRdsStatus = 0x34,
|
||||
GetDigitalServiceList = 0x80,
|
||||
StartDigitalService = 0x81,
|
||||
StopDigitalService = 0x82,
|
||||
GetDigitalServiceData = 0x84,
|
||||
DabTuneFreq = 0xB0,
|
||||
DabDigRadStatus = 0xB2,
|
||||
|
||||
@@ -829,4 +829,33 @@ std::expected<void, Si4684Error> Si4684Driver::startDabService(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, Si4684Error> Si4684Driver::stopDabService(
|
||||
std::uint32_t serviceId,
|
||||
std::uint32_t componentId,
|
||||
Si4684DigitalServiceType type)
|
||||
{
|
||||
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||
return band;
|
||||
}
|
||||
const std::uint8_t args[] = {
|
||||
static_cast<std::uint8_t>(type),
|
||||
0x00U,
|
||||
0x00U,
|
||||
static_cast<std::uint8_t>(serviceId & 0xFFU),
|
||||
static_cast<std::uint8_t>((serviceId >> 8) & 0xFFU),
|
||||
static_cast<std::uint8_t>((serviceId >> 16) & 0xFFU),
|
||||
static_cast<std::uint8_t>(serviceId >> 24),
|
||||
static_cast<std::uint8_t>(componentId & 0xFFU),
|
||||
static_cast<std::uint8_t>((componentId >> 8) & 0xFFU),
|
||||
static_cast<std::uint8_t>((componentId >> 16) & 0xFFU),
|
||||
static_cast<std::uint8_t>(componentId >> 24),
|
||||
};
|
||||
if (auto cmd =
|
||||
writeCommand(Command::StopDigitalService, args, sizeof(args));
|
||||
!cmd) {
|
||||
return std::unexpected(Si4684Error::CommandFailed);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace si4684
|
||||
|
||||
@@ -153,6 +153,14 @@ std::expected<void, core::TunerError> Si4684Tuner::tuneDab(
|
||||
std::expected<void, core::TunerError> Si4684Tuner::tuneFm(
|
||||
core::FrequencyKHz frequency)
|
||||
{
|
||||
if (driver_.loadedBand() == Si4684Band::Dab && lastDabServiceId_
|
||||
&& lastDabComponentId_) {
|
||||
(void)driver_.stopDabService(*lastDabServiceId_, *lastDabComponentId_);
|
||||
lastDabServiceId_.reset();
|
||||
lastDabComponentId_.reset();
|
||||
dabDynamicLabel_.reset();
|
||||
}
|
||||
|
||||
if (auto result = driver_.tuneFm(frequency); !result) {
|
||||
return std::unexpected(mapError(result.error()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user