From e52f39d1e4bd1be2e5e5d0c1999e5de8f260ac2c Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:32:46 +0800 Subject: [PATCH 1/9] feat: multiple transport support --- components/acoustics/api/context.hpp | 24 +++++++++++++++++------- components/acoustics/api/server.hpp | 2 +- components/acoustics/api/v0/context.cpp | 7 +++++-- components/acoustics/api/v1/context.cpp | 7 +++++-- components/acoustics/hal/transport.hpp | 5 +++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/components/acoustics/api/context.hpp b/components/acoustics/api/context.hpp index 16fdb90..1b91f80 100644 --- a/components/acoustics/api/context.hpp +++ b/components/acoustics/api/context.hpp @@ -52,7 +52,7 @@ class Context final return _user_context; } - virtual core::Status report(core::Status status) noexcept; + virtual core::Status report(core::Status status, hal::Transport *transport = nullptr) noexcept; static Context *create() noexcept; @@ -102,16 +102,26 @@ class Context final bridge::__REGISTER_TRANSPORTS__(); { - auto transport = hal::TransportRegistry::getTransport(console); - if (!transport) [[unlikely]] + const auto &transports = hal::TransportRegistry::getTransportMap(); + for (const auto &[id, transport]: transports) { - return STATUS(ENODEV, "Transport with ID " + std::to_string(console) + " is not registered"); + if (transport && !transport->initialized()) [[likely]] + { + status = transport->init(); + if (!status) [[unlikely]] + { + LOG(ERROR, "Failed to initialize transport ID=%d: %s", id, status.message().c_str()); + } + } + if (!console_ptr && id == console) + { + console_ptr = transport; + } } - if (!transport->initialized()) [[likely]] + if (!console_ptr || !console_ptr->initialized()) [[unlikely]] { - transport->init(); + return STATUS(ENODEV, "Transport with ID " + std::to_string(console) + " is not registered"); } - console_ptr = transport; } bridge::__REGISTER_PREDEFINED_MODULE_NODE_BUILDER__(); diff --git a/components/acoustics/api/server.hpp b/components/acoustics/api/server.hpp index 379a36a..a935ba2 100644 --- a/components/acoustics/api/server.hpp +++ b/components/acoustics/api/server.hpp @@ -165,7 +165,7 @@ class Server final _buffer[read - 1] = '\0'; LOG(DEBUG, "Failed to parse command '%s': %s", reinterpret_cast(_buffer), _read_status.message().c_str()); - _context.report(_read_status); + _context.report(_read_status, &transport); return; } LOG(DEBUG, "Parsed command: '%s'", command_ptr->name().data()); diff --git a/components/acoustics/api/v0/context.cpp b/components/acoustics/api/v0/context.cpp index 94e1751..d076b79 100644 --- a/components/acoustics/api/v0/context.cpp +++ b/components/acoustics/api/v0/context.cpp @@ -58,9 +58,12 @@ static bool registerCommands() namespace api { -core::Status Context::report(core::Status status) noexcept +core::Status Context::report(core::Status status, hal::Transport *transport) noexcept { - auto transport = _console_ptr; + if (!transport) + { + transport = _console_ptr; + } if (!transport) [[unlikely]] { LOG(ERROR, "Console transport is not initialized"); diff --git a/components/acoustics/api/v1/context.cpp b/components/acoustics/api/v1/context.cpp index fa50046..677e7e6 100644 --- a/components/acoustics/api/v1/context.cpp +++ b/components/acoustics/api/v1/context.cpp @@ -57,9 +57,12 @@ static bool registerCommands() namespace api { -core::Status Context::report(core::Status status) noexcept +core::Status Context::report(core::Status status, hal::Transport *transport) noexcept { - auto transport = _console_ptr; + if (!transport) + { + transport = _console_ptr; + } if (!transport) [[unlikely]] { LOG(ERROR, "Console transport is not initialized"); diff --git a/components/acoustics/hal/transport.hpp b/components/acoustics/hal/transport.hpp index 9f71402..6fa743f 100644 --- a/components/acoustics/hal/transport.hpp +++ b/components/acoustics/hal/transport.hpp @@ -38,6 +38,11 @@ class TransportRegistry final return nullptr; } + const TransportMap& getTransports() const noexcept + { + return _transports; + } + static const TransportMap &getTransportMap() noexcept { return _transports; From b4b37b8dd7d55bb76fe9a83d0a8d91b62c414d15 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:33:26 +0800 Subject: [PATCH 2/9] feat: add uart 1 --- .../porting/transport/uart_1.hpp | 231 ++++++++++++++++++ .../porting/transport_esp32s3.cpp | 2 + 2 files changed, 233 insertions(+) create mode 100644 components/acoustics-porting/porting/transport/uart_1.hpp diff --git a/components/acoustics-porting/porting/transport/uart_1.hpp b/components/acoustics-porting/porting/transport/uart_1.hpp new file mode 100644 index 0000000..7074d7b --- /dev/null +++ b/components/acoustics-porting/porting/transport/uart_1.hpp @@ -0,0 +1,231 @@ +#pragma once +#ifndef UART_2_HPP +#define UART_2_HPP + +#include "core/ring_buffer.hpp" +#include "hal/transport.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace porting { + +class TransportUART1 final: public hal::Transport +{ +public: + static inline core::ConfigObjectMap DEFAULT_CONFIGS() noexcept + { + return {}; + } + + TransportUART1() noexcept : Transport(Info(2, "UART 1", Type::UART, { DEFAULT_CONFIGS() })) { } + + core::Status init() noexcept override + { + if (_info.status != Status::Uninitialized) + { + return STATUS(ENXIO, "Transport is already initialized or in an invalid state"); + } + + auto ret = uart_driver_install(UART_NUM_1, _rx_buffer_size, _tx_buffer_size, 0, NULL, 0); + if (ret != ESP_OK) + { + LOG(ERROR, "Failed to install UART driver: %s", esp_err_to_name(ret)); + return STATUS(EIO, "Failed to install UART driver"); + } + + ret = uart_param_config(UART_NUM_1, &_config); + if (ret != ESP_OK) + { + LOG(ERROR, "Failed to configure UART parameters: %s", esp_err_to_name(ret)); + uart_driver_delete(UART_NUM_1); + return STATUS(EIO, "Failed to configure UART parameters"); + } + + ret = uart_set_pin(UART_NUM_1, GPIO_NUM_43, GPIO_NUM_44, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + if (ret != ESP_OK) + { + LOG(ERROR, "Failed to set UART pins: %s", esp_err_to_name(ret)); + uart_driver_delete(UART_NUM_1); + return STATUS(EIO, "Failed to set UART pins"); + } + + if (!_rx_buffer) + { + _rx_buffer = core::RingBuffer::create(_rx_buffer_size); + if (!_rx_buffer) + { + LOG(ERROR, "Failed to create RX buffer"); + return STATUS(ENOMEM, "Failed to create RX buffer"); + } + } + + _info.status = Status::Idle; + + return STATUS_OK(); + } + + core::Status deinit() noexcept override + { + if (_info.status == Status::Locked) + { + return STATUS(EBUSY, "Transport is currently locked"); + } + + auto ret = uart_driver_delete(UART_NUM_1); + if (ret != ESP_OK) + { + LOG(ERROR, "Failed to uninstall UART driver: %s", esp_err_to_name(ret)); + return STATUS(EIO, "Failed to uninstall UART driver"); + } + + if (_rx_buffer) + { + _rx_buffer.reset(); + } + + _info.status = Status::Uninitialized; + + return STATUS_OK(); + } + + core::Status updateConfig(const core::ConfigMap &configs) noexcept override + { + return STATUS(ENOTSUP, "Update config is not supported for UART transport"); + } + + inline size_t available() const noexcept override + { + if (!initialized() || !_rx_buffer) [[unlikely]] + { + return 0; + } + + int ret = syncReadBuffer(); + if (ret != 0) [[unlikely]] + { + LOG(ERROR, "Failed to sync read buffer: %s", std::strerror(ret)); + } + return _rx_buffer->size(); + } + + inline size_t read(void *data, size_t size) noexcept override + { + if (!_rx_buffer) [[unlikely]] + { + return 0; + } + + int ret = syncReadBuffer(); + if (ret != 0) [[unlikely]] + { + LOG(ERROR, "Failed to sync read buffer: %s", std::strerror(ret)); + return 0; + } + + return _rx_buffer->read(reinterpret_cast(data), size); + } + + inline size_t write(const void *data, size_t size) noexcept override + { + if (!initialized()) [[unlikely]] + { + return size; // Not initialized, return size as if all data was written + } + if (!data || size == 0) [[unlikely]] + { + return 0; + } + + size_t written = 0; + while (written < size) + { + size_t to_write = std::min(size - written, static_cast(_tx_buffer_size)); + int ret = uart_write_bytes(UART_NUM_1, static_cast(data) + written, to_write); + if (ret < 0) [[unlikely]] + { + LOG(ERROR, "Failed to write data to UART: %s", esp_err_to_name(ret)); + return -EIO; + } + written += ret; + } + + return written; + } + + inline int flush() noexcept override + { + if (!initialized()) [[unlikely]] + { + return 0; // Not initialized, nothing to flush + } + return uart_wait_tx_done(UART_NUM_1, portMAX_DELAY); + } + +protected: + inline int syncReadBuffer() const noexcept override + { + std::byte r_buf[32]; + for (int r_len = 0;;) + { + r_len = uart_read_bytes(UART_NUM_1, r_buf, sizeof(r_buf), pdMS_TO_TICKS(10)); + if (r_len > 0) [[likely]] + { + auto written = _rx_buffer->write(r_buf, r_len); + if (written < r_len) [[unlikely]] + { + LOG(ERROR, "Failed to write all read bytes to RX buffer, written: %zu, expected: %d", written, + r_len); + } + } + else if (r_len == 0) + { + break; + } + else + { + return -EIO; + } + } + + return 0; + } + + inline core::RingBuffer &getReadBuffer() noexcept override + { + return *_rx_buffer; + } + +private: + static const inline size_t _tx_buffer_size = 32 * 1024; + static const inline size_t _rx_buffer_size = 4096; + static const inline uart_config_t _config = { .baud_rate = 921600, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0, + .source_clk = UART_SCLK_DEFAULT, + .flags = { .allow_pd = 0, .backup_before_sleep = 0 } }; + + std::unique_ptr> _rx_buffer = nullptr; +}; + +} // namespace porting + +#endif // UART_HPP diff --git a/components/acoustics-porting/porting/transport_esp32s3.cpp b/components/acoustics-porting/porting/transport_esp32s3.cpp index e9fe29a..a9159d3 100644 --- a/components/acoustics-porting/porting/transport_esp32s3.cpp +++ b/components/acoustics-porting/porting/transport_esp32s3.cpp @@ -1,5 +1,6 @@ #include "hal/transport.hpp" +#include "transport/uart_1.hpp" #include "transport/uart_console.hpp" namespace bridge { @@ -7,6 +8,7 @@ namespace bridge { void __REGISTER_TRANSPORTS__() { [[maybe_unused]] static porting::TransportUARTConsole transport_uart_console; + [[maybe_unused]] static porting::TransportUART1 transport_uart_1; } } // namespace bridge From 6db63140eac446471edbd60cbdf2c47b336161b1 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:33:52 +0800 Subject: [PATCH 3/9] chore: ignore clangd caches --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5cfda67..681833b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ __pycache__ # Logs *.log + +# Cache (clangd) +.cache +compile_commands.json From 0a697f6fe244c0fa833e3fa3f57dc1c7e6fcbaf7 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:53:23 +0800 Subject: [PATCH 4/9] feat: support sample without encode --- components/acoustics/api/v1/cmd_start.hpp | 10 ++++++---- components/acoustics/api/v1/task_sc.hpp | 14 +++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/components/acoustics/api/v1/cmd_start.hpp b/components/acoustics/api/v1/cmd_start.hpp index 5af4d21..51671fb 100644 --- a/components/acoustics/api/v1/cmd_start.hpp +++ b/components/acoustics/api/v1/cmd_start.hpp @@ -66,6 +66,7 @@ class CmdStart final: public api::Command } bool sample_enabled = false; + bool sample_no_encode = false; bool invoke_enabled = false; auto status = STATUS_OK(); @@ -80,9 +81,10 @@ class CmdStart final: public api::Command { if (auto action = std::get_if(&it->second); action != nullptr) { - if (*action == "sample") + if (*action == "sample" || *action == "sample_no_encode") { sample_enabled = true; + sample_no_encode = (action->size() > sizeof("sample")); status = makeSensorReady(); if (!status) [[unlikely]] { @@ -187,8 +189,8 @@ class CmdStart final: public api::Command if (sample_enabled) { _internal_sample_task_id += 1; - return std::shared_ptr( - new v1::TaskSC::Sample(context, transport, id, _sensor, cmd_tag, _internal_sample_task_id)); + return std::shared_ptr(new v1::TaskSC::Sample(context, transport, id, _sensor, cmd_tag, + sample_no_encode, _internal_sample_task_id)); } if (invoke_enabled) { @@ -265,7 +267,7 @@ class CmdStart final: public api::Command } status = executor.submit(std::shared_ptr( - new v1::TaskSC::Sample(context, *console, id, _sensor, "RC", _internal_sample_task_id))); + new v1::TaskSC::Sample(context, *console, id, _sensor, "RC", false, _internal_sample_task_id))); if (!status) [[unlikely]] { return status; diff --git a/components/acoustics/api/v1/task_sc.hpp b/components/acoustics/api/v1/task_sc.hpp index 11cfaee..eef5c25 100644 --- a/components/acoustics/api/v1/task_sc.hpp +++ b/components/acoustics/api/v1/task_sc.hpp @@ -70,9 +70,9 @@ struct TaskSC final { public: Sample(api::Context &context, hal::Transport &transport, size_t id, hal::Sensor *sensor, std::string tag, - const volatile size_t &external_task_id) noexcept + bool no_encode, const volatile size_t &external_task_id) noexcept : api::Task(context, transport, id, v1::defaults::task_priority), _sensor(sensor), _tag(std::move(tag)), - _external_task_id(external_task_id), _internal_task_id(_external_task_id), + _no_encode(no_encode), _external_task_id(external_task_id), _internal_task_id(_external_task_id), _start_time(std::chrono::steady_clock::now()), _current_id(0), _current_id_next(0), _sr(0), _fs(0) { { @@ -109,6 +109,7 @@ struct TaskSC final } } + if (!_no_encode) { auto adpcm_size = core::EncoderADPCMIMA::estimate(_fs); auto opus_size = core::EncoderLIBOPUS::estimate(_fs, _sr); @@ -207,6 +208,12 @@ struct TaskSC final } _current_id_next = _current_id + 1; + + if (_no_encode) + { + return executor.submit(getptr(), getNextDataDelay(_sensor->dataAvailable())); + } + if (shared::is_invoking) { status = encodeADPCM(data_frame.data->data(), size); @@ -333,6 +340,7 @@ struct TaskSC final hal::Sensor *_sensor; const std::string _tag; + const bool _no_encode; const volatile size_t &_external_task_id; const size_t _internal_task_id; const std::chrono::steady_clock::time_point _start_time; @@ -626,4 +634,4 @@ struct TaskSC final } // namespace v1 -#endif \ No newline at end of file +#endif From 8608f5c2b7f12d0a1f2a60099e7d0ef844263916 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:54:55 +0800 Subject: [PATCH 5/9] chore: bump version --- components/acoustics-porting/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/acoustics-porting/CMakeLists.txt b/components/acoustics-porting/CMakeLists.txt index c1287fc..a8f85a7 100644 --- a/components/acoustics-porting/CMakeLists.txt +++ b/components/acoustics-porting/CMakeLists.txt @@ -6,9 +6,9 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(ACOUSTICS_SDK_VERSION_MAJOR 1) -set(ACOUSTICS_SDK_VERSION_MINOR 12) -set(ACOUSTICS_SDK_VERSION_PATCH 30) +set(ACOUSTICS_SDK_VERSION_MAJOR 2) +set(ACOUSTICS_SDK_VERSION_MINOR 1) +set(ACOUSTICS_SDK_VERSION_PATCH 28) set(ACOUSTICS_PORTING_INCLUDES_DIR ${CMAKE_CURRENT_LIST_DIR}/porting From 87569a337f9822d899f65b613aa5d58bb4fa14fb Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Wed, 28 Jan 2026 18:00:20 +0800 Subject: [PATCH 6/9] fix: header guard typo --- components/acoustics-porting/porting/transport/uart_1.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/acoustics-porting/porting/transport/uart_1.hpp b/components/acoustics-porting/porting/transport/uart_1.hpp index 7074d7b..e9fcb14 100644 --- a/components/acoustics-porting/porting/transport/uart_1.hpp +++ b/components/acoustics-porting/porting/transport/uart_1.hpp @@ -1,6 +1,6 @@ #pragma once -#ifndef UART_2_HPP -#define UART_2_HPP +#ifndef UART_1_HPP +#define UART_1_HPP #include "core/ring_buffer.hpp" #include "hal/transport.hpp" From cc7e40f97050cafd5d7818cad2f5ac5ef40bae58 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:29:18 +0800 Subject: [PATCH 7/9] chore: sync experimental itoa --- components/acoustics/core/serializer.hpp | 352 ++++++++++++++++++++++- 1 file changed, 345 insertions(+), 7 deletions(-) diff --git a/components/acoustics/core/serializer.hpp b/components/acoustics/core/serializer.hpp index d2d8856..5a9824b 100644 --- a/components/acoustics/core/serializer.hpp +++ b/components/acoustics/core/serializer.hpp @@ -24,6 +24,350 @@ namespace core { +namespace charconv { + + namespace digits2 { + + inline constexpr decltype(auto) make_digits() noexcept + { + alignas(2) std::array data {}; + for (size_t i = 0; i < 100; ++i) + { + data[i * 2] = static_cast('0' + i / 10); + data[i * 2 + 1] = static_cast('0' + i % 10); + } + return data; + } + + alignas(2) inline constexpr auto table = make_digits(); + + inline constexpr const char *at(size_t index) + { + return &table[index]; + } + + } // namespace digits2 + + template>, + std::enable_if_t && sizeof(U) == sizeof(uint8_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + if (val < 100U) + { + unsigned leading_zero = val < 10U; + std::memcpy(buf, digits2::at((val << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + return buf + 2 - leading_zero; +#pragma GCC diagnostic pop + } + else + { + uint16_t l0_0 = (41U * val) >> 12; + uint16_t l1_2 = val - (100U * l0_0); + *buf = static_cast('0' | l0_0); + std::memcpy(buf + 1, digits2::at(l1_2 << 1), 2); + return buf + 3; + } +#pragma GCC diagnostic pop + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(int8_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + unsigned sign = val < 0; + uint8_t uval = static_cast(val); + *buf = '-'; + return itoa(sign ? static_cast(~uval + 1) : uval, buf + sign); + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(uint16_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + if (val < 100U) + { + unsigned leading_zero = val < 10U; + std::memcpy(buf, digits2::at((val << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + return buf + 2 - leading_zero; +#pragma GCC diagnostic pop + } + else if (val < 10'000U) + { + uint32_t l0_1 = (static_cast(5243U) * val) >> 19; + uint32_t l2_3 = val - (static_cast(100U) * l0_1); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + return buf + 4; + } + else + { + uint32_t l0_1 = static_cast((static_cast(107375UL) * val) >> 30); + uint32_t l2_5 = val - (static_cast(10'000U) * l0_1); + uint32_t l2_3 = (static_cast(5243U) * l2_5) >> 19; + uint32_t l4_5 = l2_5 - (static_cast(100U) * l2_3); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); + return buf + 6; + } +#pragma GCC diagnostic pop + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(int16_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + unsigned sign = val < 0; + uint16_t uval = static_cast(val); + *buf = '-'; + return itoa(sign ? static_cast(~uval + 1) : uval, buf + sign); + } + + inline char *itoa_lt_1e2(uint32_t val, char *buf) noexcept + { + unsigned leading_zero = val < 10U; + std::memcpy(buf, digits2::at((val << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + return buf + 2 - leading_zero; +#pragma GCC diagnostic pop + } + + inline char *itoa_ge_1e2_lt_1e4(uint32_t val, char *buf) noexcept + { + uint32_t l0_1 = (static_cast(5243U) * val) >> 19; + uint32_t l2_3 = val - (static_cast(100U) * l0_1); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + return buf + 4; + } + + inline char *itoa_ge_1e2_lt_1e4_lz(uint32_t val, char *buf) noexcept + { + uint32_t l0_1 = (static_cast(5243U) * val) >> 19; + uint32_t l2_3 = val - (static_cast(100U) * l0_1); + std::memcpy(buf, digits2::at(l0_1 << 1), 2); + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + return buf + 4; + } + + inline char *itoa_ge_1e4_lt_1e6(uint32_t val, char *buf) noexcept + { + uint32_t l0_1 = static_cast((static_cast(429497UL) * val) >> 32); + uint32_t l2_5 = val - (static_cast(10'000U) * l0_1); + uint32_t l2_3 = (static_cast(5243U) * l2_5) >> 19; + uint32_t l4_5 = l2_5 - (static_cast(100U) * l2_3); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); + return buf + 6; + } + + inline char *itoa_ge_1e6_lt_1e8(uint32_t val, char *buf) noexcept + { + uint32_t l0_3 = static_cast((static_cast(109951163UL) * val) >> 40); + uint32_t l4_7 = val - (static_cast(10'000U) * l0_3); + uint32_t l0_1 = (static_cast(5243U) * l0_3) >> 19; + uint32_t l2_3 = l0_3 - (static_cast(100U) * l0_1); + uint32_t l4_5 = (static_cast(5243U) * l4_7) >> 19; + uint32_t l6_7 = l4_7 - (static_cast(100U) * l4_5); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); + std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2); + return buf + 8; + } + + inline char *itoa_ge_1e6_lt_1e8_lz(uint32_t val, char *buf) noexcept + { + uint32_t l0_3 = static_cast((static_cast(109951163UL) * val) >> 40); + uint32_t l4_7 = val - (static_cast(10'000U) * l0_3); + uint32_t l0_1 = (static_cast(5243U) * l0_3) >> 19; + uint32_t l2_3 = l0_3 - (static_cast(100U) * l0_1); + uint32_t l4_5 = (static_cast(5243U) * l4_7) >> 19; + uint32_t l6_7 = l4_7 - (static_cast(100U) * l4_5); + std::memcpy(buf, digits2::at((l0_1 << 1)), 2); + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); + std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2); + return buf + 8; + } + + inline char *itoa_ge_1e8_lt_1ea(uint32_t val, char *buf) noexcept + { + uint32_t l0_5 = static_cast((static_cast(3518437209UL) * val) >> 45); + uint32_t l0_1 = static_cast((static_cast(429497UL) * l0_5) >> 32); + uint32_t l2_5 = l0_5 - (static_cast(10'000U) * l0_1); + uint32_t l2_3 = (static_cast(5243U) * l2_5) >> 19; + uint32_t l4_5 = l2_5 - (static_cast(100U) * l2_3); + uint32_t l6_9 = val - (static_cast(10'000U) * l0_5); + uint32_t l6_7 = (static_cast(5243U) * l6_9) >> 19; + uint32_t l8_9 = l6_9 - (static_cast(100U) * l6_7); + unsigned leading_zero = l0_1 < 10U; + std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + buf -= leading_zero; +#pragma GCC diagnostic pop + std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); + std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); + std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2); + std::memcpy(buf + 8, digits2::at(l8_9 << 1), 2); + return buf + 10; + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(uint32_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + if (val < 100U) + { + return itoa_lt_1e2(val, buf); + } + else if (val < 10'000U) + { + return itoa_ge_1e2_lt_1e4(val, buf); + } + else if (val < static_cast(1'000'000UL)) + { + return itoa_ge_1e4_lt_1e6(val, buf); + } + else if (val < static_cast(100'000'000UL)) + { + return itoa_ge_1e6_lt_1e8(val, buf); + } + else + { + return itoa_ge_1e8_lt_1ea(val, buf); + } + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(int32_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + unsigned sign = val < 0; + uint32_t uval = static_cast(val); + *buf = '-'; + return itoa(sign ? static_cast(~uval + 1) : uval, buf + sign); + } + + inline char *itoa_lt_1e8(uint32_t val, char *buf) noexcept + { + if (val < 100U) + { + return itoa_lt_1e2(val, buf); + } + else if (val < 10'000U) + { + return itoa_ge_1e2_lt_1e4(val, buf); + } + else if (val < static_cast(1'000'000UL)) + { + return itoa_ge_1e4_lt_1e6(val, buf); + } + else + { + return itoa_ge_1e6_lt_1e8(val, buf); + } + } + + inline char *itoa_ge_1e4_lt_1e8(uint32_t val, char *buf) noexcept + { + if (val < static_cast(1'000'000UL)) + { + return itoa_ge_1e4_lt_1e6(val, buf); + } + else + { + return itoa_ge_1e6_lt_1e8(val, buf); + } + } + + inline char *itoa_ge_1e8_lt_1e16(uint64_t val, char *buf) noexcept + { + uint64_t l0_7 = val / 100'000'000UL; + uint32_t l8_f = static_cast(val - (static_cast(100'000'000UL) * l0_7)); + buf = itoa_lt_1e8(static_cast(l0_7), buf); + buf = itoa_ge_1e6_lt_1e8_lz(l8_f, buf); + return buf; + } + + inline char *itoa_ge_1e16_lt_1e20(uint64_t val, char *buf) noexcept + { + uint64_t l0_b = val / 100'000'000UL; + uint32_t r0_7 = static_cast(val - (static_cast(100'000'000UL) * l0_b)); + uint32_t l0_3 = static_cast(l0_b / 10'000UL); + uint32_t l4_7 = static_cast(l0_b - (static_cast(10'000UL) * l0_3)); + buf = itoa_ge_1e4_lt_1e8(l0_3, buf); + buf = itoa_ge_1e2_lt_1e4_lz(l4_7, buf); + buf = itoa_ge_1e6_lt_1e8_lz(r0_7, buf); + return buf; + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(uint64_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + if (val < static_cast(100'000'000UL)) + { + return itoa_lt_1e8(static_cast(val), buf); + } + else if (val < static_cast(10'000'000'000'000'000ULL)) + { + return itoa_ge_1e8_lt_1e16(val, buf); + } + else + { + return itoa_ge_1e16_lt_1e20(val, buf); + } + } + + template>, + std::enable_if_t && sizeof(U) == sizeof(int64_t), bool> = true> + inline constexpr char *itoa(T &&val, char *buf) noexcept + { + unsigned sign = val < 0; + uint64_t uval = static_cast(val); + *buf = '-'; + return itoa(sign ? static_cast(~uval + 1) : uval, buf + sign); + } + +} // namespace charconv + class Serializer final { public: @@ -872,13 +1216,7 @@ class Serializer final } char *buffer = reinterpret_cast(_buffer); - char *buffer_e = reinterpret_cast(_buffer_e); - const auto [ptr, ec] = std::to_chars(buffer, buffer_e, value); - if (ec != std::errc()) [[unlikely]] - { - error(static_cast(ec)); - return false; - } + char* ptr = charconv::itoa(std::forward(value), buffer); _buffer = reinterpret_cast(ptr); return true; From 1ddaa8a8d2e74566fa7f9e04e86d6d317ce66592 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:50:55 +0800 Subject: [PATCH 8/9] refactor: board detector --- .../porting/board/board_detector.h | 129 ++++++++++-------- .../porting/device_esp32s3.cpp | 3 +- .../porting/sensor/i2smic.hpp | 6 +- .../porting/transport_esp32s3.cpp | 7 +- 4 files changed, 83 insertions(+), 62 deletions(-) diff --git a/components/acoustics-porting/porting/board/board_detector.h b/components/acoustics-porting/porting/board/board_detector.h index 9da6381..e329667 100644 --- a/components/acoustics-porting/porting/board/board_detector.h +++ b/components/acoustics-porting/porting/board/board_detector.h @@ -2,27 +2,25 @@ #ifndef BOARD_DETECTOR_H #define BOARD_DETECTOR_H -#include "core/logger.hpp" +#include + #include #include #include #include #include #include -#include + +#include "core/logger.hpp" namespace porting { -enum class BoardType { - UNKNOWN = 0, - XIAO_S3, - RESPEAKER_LITE, - RESPEAKER_XVF3800 -}; +enum class BoardType { UNKNOWN = 0, XIAO_S3, RESPEAKER_LITE, RESPEAKER_XVF3800 }; -struct BoardConfig { +struct BoardConfig +{ BoardType type; - const char* name; + const char *name; bool use_pdm; gpio_num_t pdm_clk; gpio_num_t pdm_din; @@ -32,68 +30,87 @@ struct BoardConfig { gpio_num_t i2s_dout; i2s_role_t i2s_role; uint32_t sample_rate; - const int* gpio_pins; + const int *gpio_pins; size_t gpio_pins_count; }; -inline BoardType detectBoard() noexcept { - LOG(INFO, "Detecting board type via I2C..."); - - i2c_master_bus_config_t i2c_cfg = { - .i2c_port = I2C_NUM_0, - .sda_io_num = GPIO_NUM_5, - .scl_io_num = GPIO_NUM_6, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .flags = {.enable_internal_pullup = true}, - }; - - i2c_master_bus_handle_t bus_handle; - if (i2c_new_master_bus(&i2c_cfg, &bus_handle) != ESP_OK) { - LOG(WARNING, "Failed to init I2C, defaulting to XIAO S3"); - return BoardType::XIAO_S3; - } +#ifdef DYN_BOARD_TYPE_FROM_I2C_ONCE +#error "DYN_BOARD_TYPE_FROM_I2C is already defined" +#endif +#define DYN_BOARD_TYPE_FROM_I2C_ONCE porting::__DynamicBoardTypeFromI2COnce() + +inline BoardType __DynamicBoardTypeFromI2COnce() noexcept +{ + static auto type = []() noexcept -> BoardType { + LOG(INFO, "Detecting board type via I2C..."); + + i2c_master_bus_config_t i2c_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = GPIO_NUM_5, + .scl_io_num = GPIO_NUM_6, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 1, + .trans_queue_depth = 0, + .flags = { .enable_internal_pullup = true, .allow_pd = 0 }, + }; + + i2c_master_bus_handle_t bus_handle; + if (i2c_new_master_bus(&i2c_cfg, &bus_handle) != ESP_OK) + { + LOG(WARNING, "Failed to init I2C, defaulting to XIAO S3"); + return BoardType::XIAO_S3; + } + + // Check for ReSpeaker Lite (I2C address 0x42) + if (i2c_master_probe(bus_handle, 0x42, 100) == ESP_OK) + { + i2c_del_master_bus(bus_handle); + LOG(INFO, "Detected: ReSpeaker Lite (I2C 0x42)"); + return BoardType::RESPEAKER_LITE; + } + + // Check for 0x2C device + if (i2c_master_probe(bus_handle, 0x2C, 100) == ESP_OK) + { + i2c_del_master_bus(bus_handle); + LOG(INFO, "Detected: ReSpeaker XVF3800 (I2C 0x2C)"); + return BoardType::RESPEAKER_XVF3800; + } - // Check for ReSpeaker Lite (I2C address 0x42) - if (i2c_master_probe(bus_handle, 0x42, 100) == ESP_OK) { i2c_del_master_bus(bus_handle); - LOG(INFO, "Detected: ReSpeaker Lite (I2C 0x42)"); - return BoardType::RESPEAKER_LITE; - } - // Check for 0x2C device - if (i2c_master_probe(bus_handle, 0x2C, 100) == ESP_OK) { - i2c_del_master_bus(bus_handle); - LOG(INFO, "Detected: ReSpeaker XVF3800 (I2C 0x2C)"); - return BoardType::RESPEAKER_XVF3800; - } - - i2c_del_master_bus(bus_handle); + LOG(INFO, "Default: XIAO ESP32-S3"); + return BoardType::XIAO_S3; + }(); - // No I2C device found, assume XIAO S3 - LOG(INFO, "Detected: XIAO ESP32-S3 (default)"); - return BoardType::XIAO_S3; + return type; } -inline const BoardConfig& getBoardConfig(BoardType type) noexcept { +#ifdef DYN_BOARD_CONFIG_FORM_TYPE +#error "DYN_BOARD_CONFIG_FORM_TYPE is already defined" +#endif +#define DYN_BOARD_CONFIG_FORM_TYPE(type) porting::__DynamicBoardConfigFromType(type) + +inline const BoardConfig &__DynamicBoardConfigFromType(BoardType type) noexcept +{ static const int xiao_s3_gpios[] = { 1, 2, 3, 21, 41, 42 }; static const int respeaker_lite_gpios[] = { 1, 2, 3, 21, 41, 42 }; static const int respeaker_xvf3800_gpios[] = { 1, 3, 4, 43, 44 }; static const BoardConfig configs[] = { - { BoardType::XIAO_S3, "XIAO ESP32-S3", true, GPIO_NUM_42, GPIO_NUM_41, - GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, I2S_ROLE_MASTER, 44100, - xiao_s3_gpios, 6 }, - { BoardType::RESPEAKER_LITE, "ReSpeaker Lite", false, GPIO_NUM_NC, GPIO_NUM_NC, - GPIO_NUM_8, GPIO_NUM_7, GPIO_NUM_44, GPIO_NUM_43, I2S_ROLE_SLAVE, 16000, - respeaker_lite_gpios, 6 }, - { BoardType::RESPEAKER_XVF3800, "ReSpeaker XVF3800", false, GPIO_NUM_NC, GPIO_NUM_NC, - GPIO_NUM_8, GPIO_NUM_7, GPIO_NUM_43, GPIO_NUM_44, I2S_ROLE_MASTER, 16000, - respeaker_xvf3800_gpios, 5 }, + { BoardType::XIAO_S3, "XIAO ESP32-S3", true, GPIO_NUM_42, GPIO_NUM_41, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_NC, + GPIO_NUM_NC, I2S_ROLE_MASTER, 44100, xiao_s3_gpios, 6 }, + { BoardType::RESPEAKER_LITE, "ReSpeaker Lite", false, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_8, GPIO_NUM_7, + GPIO_NUM_44, GPIO_NUM_43, I2S_ROLE_SLAVE, 16000, respeaker_lite_gpios, 6 }, + { BoardType::RESPEAKER_XVF3800, "ReSpeaker XVF3800", false, GPIO_NUM_NC, GPIO_NUM_NC, GPIO_NUM_8, GPIO_NUM_7, + GPIO_NUM_43, GPIO_NUM_44, I2S_ROLE_MASTER, 16000, respeaker_xvf3800_gpios, 5 }, }; - for (const auto& cfg : configs) { - if (cfg.type == type) return cfg; + for (const auto &cfg: configs) + { + if (cfg.type == type) + return cfg; } return configs[0]; // Default to ESP32-S3 } diff --git a/components/acoustics-porting/porting/device_esp32s3.cpp b/components/acoustics-porting/porting/device_esp32s3.cpp index bca1e7a..869f413 100644 --- a/components/acoustics-porting/porting/device_esp32s3.cpp +++ b/components/acoustics-porting/porting/device_esp32s3.cpp @@ -88,8 +88,7 @@ class DeviceESP32S3 final: public hal::Device return STATUS_OK(); } - auto board_type = porting::detectBoard(); - _board_config = porting::getBoardConfig(board_type); + _board_config = DYN_BOARD_CONFIG_FORM_TYPE(DYN_BOARD_TYPE_FROM_I2C_ONCE); _info.name = _board_config.name; for (size_t i = 0; i < _board_config.gpio_pins_count; ++i) { diff --git a/components/acoustics-porting/porting/sensor/i2smic.hpp b/components/acoustics-porting/porting/sensor/i2smic.hpp index 6d2d2de..69adf6d 100644 --- a/components/acoustics-porting/porting/sensor/i2smic.hpp +++ b/components/acoustics-porting/porting/sensor/i2smic.hpp @@ -60,8 +60,8 @@ class SensorI2SMic final: public Sensor return STATUS(ENXIO, "Sensor is already initialized or in an invalid state"); } - _board_type = detectBoard(); - _board_config = getBoardConfig(_board_type); + _board_type = DYN_BOARD_TYPE_FROM_I2C_ONCE; + _board_config = DYN_BOARD_CONFIG_FORM_TYPE(_board_type); const size_t sr = _board_config.sample_rate; _channels = _info.configs["channels"].getValue(); _buffered_duration = _info.configs["buffered_duration"].getValue(); @@ -426,4 +426,4 @@ class SensorI2SMic final: public Sensor } // namespace porting -#endif \ No newline at end of file +#endif diff --git a/components/acoustics-porting/porting/transport_esp32s3.cpp b/components/acoustics-porting/porting/transport_esp32s3.cpp index a9159d3..b0a8aa7 100644 --- a/components/acoustics-porting/porting/transport_esp32s3.cpp +++ b/components/acoustics-porting/porting/transport_esp32s3.cpp @@ -1,5 +1,6 @@ #include "hal/transport.hpp" +#include "board/board_detector.h" #include "transport/uart_1.hpp" #include "transport/uart_console.hpp" @@ -8,7 +9,11 @@ namespace bridge { void __REGISTER_TRANSPORTS__() { [[maybe_unused]] static porting::TransportUARTConsole transport_uart_console; - [[maybe_unused]] static porting::TransportUART1 transport_uart_1; + + if (DYN_BOARD_TYPE_FROM_I2C_ONCE == porting::BoardType::XIAO_S3) + { + [[maybe_unused]] static porting::TransportUART1 transport_uart_1; + } } } // namespace bridge From 1a19c272b2a1fea82bfbe761b979fb455558cc46 Mon Sep 17 00:00:00 2001 From: iChizer0 <62390647+iChizer0@users.noreply.github.com> Date: Thu, 29 Jan 2026 09:54:27 +0800 Subject: [PATCH 9/9] chore: bump version --- components/acoustics-porting/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/acoustics-porting/CMakeLists.txt b/components/acoustics-porting/CMakeLists.txt index a8f85a7..6fe1267 100644 --- a/components/acoustics-porting/CMakeLists.txt +++ b/components/acoustics-porting/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(ACOUSTICS_SDK_VERSION_MAJOR 2) set(ACOUSTICS_SDK_VERSION_MINOR 1) -set(ACOUSTICS_SDK_VERSION_PATCH 28) +set(ACOUSTICS_SDK_VERSION_PATCH 29) set(ACOUSTICS_PORTING_INCLUDES_DIR ${CMAKE_CURRENT_LIST_DIR}/porting