From 4889dd6fcbcc633de159e1485afcfed56cb52e39 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 4 Sep 2025 10:46:30 +0200 Subject: [PATCH] feat(modem): Add enhanced URC observer API --- .../modem_console/main/modem_console_main.cpp | 29 ++-- .../cxx_include/esp_modem_dce_template.hpp | 5 + .../include/cxx_include/esp_modem_dte.hpp | 68 +++++++- components/esp_modem/src/esp_modem_dte.cpp | 86 ++++++++-- .../esp_modem/test/target_urc/README.md | 152 ++++++++++++++++++ .../test/target_urc/main/urc_test.cpp | 131 ++++++++++++--- docs/esp_modem/en/advanced_api.rst | 9 ++ 7 files changed, 440 insertions(+), 40 deletions(-) create mode 100644 components/esp_modem/test/target_urc/README.md diff --git a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp index 16bff15b23..92f1b5d1b8 100644 --- a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp +++ b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -97,10 +97,21 @@ void wakeup_modem(void) } #ifdef CONFIG_ESP_MODEM_URC_HANDLER -command_result handle_urc(uint8_t *data, size_t len) +esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info) { - ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO); - return command_result::TIMEOUT; + // Log buffer information for debugging + ESP_LOGI(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s", + info.buffer_total_size, info.processed_offset, info.new_data_size, + info.is_command_active ? "true" : "false"); + + // Log the new data content + if (info.new_data_size > 0) { + ESP_LOG_BUFFER_HEXDUMP("on_read", info.new_data_start, info.new_data_size, ESP_LOG_INFO); + } + + // For console example, we just log and don't consume anything + // This allows the data to be processed by command handlers + return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0}; } #endif @@ -381,14 +392,14 @@ extern "C" void app_main(void) CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK")); }); #ifdef CONFIG_ESP_MODEM_URC_HANDLER - const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) { + const ConsoleCommand HandleURC("urc", "toggle enhanced urc handling", no_args, [&](ConsoleCommand * c) { static int cnt = 0; if (++cnt % 2) { - ESP_LOGI(TAG, "Adding URC handler"); - dce->set_urc(handle_urc); + ESP_LOGI(TAG, "Adding enhanced URC handler"); + dce->set_enhanced_urc(handle_enhanced_urc); } else { - ESP_LOGI(TAG, "URC removed"); - dce->set_urc(nullptr); + ESP_LOGI(TAG, "Enhanced URC removed"); + dce->set_enhanced_urc(nullptr); } return 0; }); diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce_template.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce_template.hpp index a3b3a0bf8b..3b722ef040 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce_template.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce_template.hpp @@ -103,6 +103,11 @@ class DCE_T { { dte->set_urc_cb(on_read_cb); } + + void set_enhanced_urc(esp_modem::DTE::enhanced_urc_cb enhanced_cb) + { + dte->set_enhanced_urc_cb(enhanced_cb); + } #endif /** diff --git a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp index 4e11515ff7..a78e486270 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -115,6 +115,42 @@ class DTE : public CommandableIf { { command_cb.urc_handler = std::move(line_cb); } + + /** + * @brief Enhanced URC handler with buffer consumption control + * @param buffer_info Information about the current buffer state + * @return Information about how much of the buffer to consume + */ + struct UrcBufferInfo { + const uint8_t* buffer_start; // Start of entire buffer + size_t buffer_total_size; // Total buffer size + size_t processed_offset; // Offset of already processed data + size_t new_data_size; // Size of new data since last call + const uint8_t* new_data_start; // Pointer to start of new data + bool is_command_active; // Whether a command is currently waiting for response + }; + + enum class UrcConsumeResult { + CONSUME_NONE, // Don't consume anything, continue waiting + CONSUME_PARTIAL, // Consume only part of the buffer + CONSUME_ALL // Consume entire buffer + }; + + struct UrcConsumeInfo { + UrcConsumeResult result; + size_t consume_size; // How many bytes to consume (0 = none, SIZE_MAX = all) + }; + + typedef std::function enhanced_urc_cb; + + /** + * @brief Set enhanced URC callback with buffer consumption control + * @param enhanced_cb Enhanced callback that can control buffer consumption + */ + void set_enhanced_urc_cb(enhanced_urc_cb enhanced_cb) + { + command_cb.enhanced_urc_handler = std::move(enhanced_cb); + } #endif /** @@ -171,6 +207,33 @@ class DTE : public CommandableIf { [[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */ void exit_cmux_internal(); /*!< Cleanup CMUX */ +#ifdef CONFIG_ESP_MODEM_URC_HANDLER + /** + * @brief Buffer state tracking for enhanced URC processing + */ + struct BufferState { + size_t total_processed = 0; /*!< Total bytes processed by URC handlers */ + size_t last_urc_processed = 0; /*!< Last offset processed by URC */ + bool command_waiting = false; /*!< Whether command is waiting for response */ + size_t command_start_offset = 0; /*!< Where current command response started */ + } buffer_state; + + /** + * @brief Update buffer state when new data arrives + * @param new_data_size Size of new data added to buffer + */ + void update_buffer_state(size_t new_data_size); + + /** + * @brief Create URC buffer information for enhanced handlers + * @param data Buffer data pointer + * @param consumed Already consumed bytes + * @param len New data length + * @return UrcBufferInfo structure with complete buffer context + */ + UrcBufferInfo create_urc_info(uint8_t* data, size_t consumed, size_t len); +#endif + Lock internal_lock{}; /*!< Locks DTE operations */ unique_buffer buffer; /*!< DTE buffer */ std::shared_ptr cmux_term; /*!< Primary terminal for this DTE */ @@ -216,6 +279,7 @@ class DTE : public CommandableIf { struct command_cb { #ifdef CONFIG_ESP_MODEM_URC_HANDLER got_line_cb urc_handler {}; /*!< URC callback if enabled */ + enhanced_urc_cb enhanced_urc_handler {}; /*!< Enhanced URC callback with consumption control */ #endif static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */ got_line_cb got_line; /*!< Supplied command callback */ @@ -223,7 +287,7 @@ class DTE : public CommandableIf { char separator{}; /*!< Command reply separator (end of line/processing unit) */ command_result result{}; /*!< Command return code */ SignalGroup signal; /*!< Event group used to signal request-response operations */ - bool process_line(uint8_t *data, size_t consumed, size_t len); /*!< Lets the processing callback handle one line (processing unit) */ + bool process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte = nullptr); /*!< Lets the processing callback handle one line (processing unit) */ bool wait_for_line(uint32_t time_ms) /*!< Waiting for command processing */ { return signal.wait_any(command_cb::GOT_LINE, time_ms); diff --git a/components/esp_modem/src/esp_modem_dte.cpp b/components/esp_modem/src/esp_modem_dte.cpp index 2c5385180c..9c6fdd14e5 100644 --- a/components/esp_modem/src/esp_modem_dte.cpp +++ b/components/esp_modem/src/esp_modem_dte.cpp @@ -65,6 +65,10 @@ void DTE::set_command_callbacks() { primary_term->set_read_cb([this](uint8_t *data, size_t len) { Scoped l(command_cb.line_lock); +#ifdef CONFIG_ESP_MODEM_URC_HANDLER + // Update buffer state when new data arrives + update_buffer_state(len); +#endif #ifndef CONFIG_ESP_MODEM_URC_HANDLER if (command_cb.got_line == nullptr || command_cb.result != command_result::TIMEOUT) { return false; // this line has been processed already (got OK or FAIL previously) @@ -80,7 +84,7 @@ void DTE::set_command_callbacks() std::memcpy(inflatable.current(), data, len); data = inflatable.begin(); } - if (command_cb.process_line(data, inflatable.consumed, len)) { + if (command_cb.process_line(data, inflatable.consumed, len, this)) { return true; } // at this point we're sure that the data processing hasn't finished, @@ -92,7 +96,7 @@ void DTE::set_command_callbacks() inflatable.consumed += len; return false; #else - if (command_cb.process_line(data, 0, len)) { + if (command_cb.process_line(data, 0, len, this)) { return true; } // cannot inflate and the processing hasn't finishes in the first iteration, but continue @@ -105,7 +109,7 @@ void DTE::set_command_callbacks() if (buffer.size > buffer.consumed) { data = buffer.get(); len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed); - if (command_cb.process_line(data, buffer.consumed, len)) { + if (command_cb.process_line(data, buffer.consumed, len, this)) { return true; } buffer.consumed += len; @@ -121,7 +125,7 @@ void DTE::set_command_callbacks() inflatable.grow(inflatable.consumed + len); } len = primary_term->read(inflatable.current(), len); - if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len)) { + if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len, this)) { return true; } inflatable.consumed += len; @@ -150,10 +154,19 @@ void DTE::set_command_callbacks() command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator) { Scoped l1(internal_lock); +#ifdef CONFIG_ESP_MODEM_URC_HANDLER + // Track command start + buffer_state.command_waiting = true; + buffer_state.command_start_offset = buffer_state.total_processed; +#endif command_cb.set(got_line, separator); primary_term->write((uint8_t *)command.c_str(), command.length()); command_cb.wait_for_line(time_ms); command_cb.set(nullptr); +#ifdef CONFIG_ESP_MODEM_URC_HANDLER + // Track command end + buffer_state.command_waiting = false; +#endif buffer.consumed = 0; #ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED inflatable.deflate(); @@ -365,18 +378,54 @@ void DTE::on_read(got_line_cb on_read_cb) }); } -bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len) +bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte) { // returning true indicates that the processing finished and lower layers can destroy the accumulated buffer #ifdef CONFIG_ESP_MODEM_URC_HANDLER - bool consume_buffer = false; + // Call enhanced URC handler if registered + if (enhanced_urc_handler && dte) { + // Create buffer info for enhanced URC handler + UrcBufferInfo buffer_info = dte->create_urc_info(data, consumed, len); + + // Call enhanced URC handler + UrcConsumeInfo consume_info = enhanced_urc_handler(buffer_info); + + // Handle consumption control + switch (consume_info.result) { + case UrcConsumeResult::CONSUME_NONE: + // Don't consume anything, continue with command processing + break; + + case UrcConsumeResult::CONSUME_PARTIAL: + // Consume only specified amount + dte->buffer_state.last_urc_processed += consume_info.consume_size; + // Adjust data pointers for command processing + data += consume_info.consume_size; + consumed = (consumed + len) - consume_info.consume_size; + len = 0; + break; + + case UrcConsumeResult::CONSUME_ALL: + // Consume entire buffer + dte->buffer_state.last_urc_processed = consumed + len; + return true; // Signal buffer consumption + } + } + + // Fallback to legacy URC handler if enhanced handler not set if (urc_handler) { - consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT; + bool consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT; + if (result != command_result::TIMEOUT || got_line == nullptr) { + return consume_buffer; // this line has been processed already (got OK or FAIL previously) + } } +#endif + + // Continue with normal command processing if (result != command_result::TIMEOUT || got_line == nullptr) { - return consume_buffer; // this line has been processed already (got OK or FAIL previously) + return false; // Command processing continues } -#endif + if (memchr(data + consumed, separator, len)) { result = got_line(data, consumed + len); if (result == command_result::OK || result == command_result::FAIL) { @@ -423,3 +472,22 @@ void DTE::extra_buffer::grow(size_t need_size) */ unique_buffer::unique_buffer(size_t size): data(std::make_unique(size)), size(size), consumed(0) {} + +#ifdef CONFIG_ESP_MODEM_URC_HANDLER +void DTE::update_buffer_state(size_t new_data_size) +{ + buffer_state.total_processed += new_data_size; +} + +DTE::UrcBufferInfo DTE::create_urc_info(uint8_t* data, size_t consumed, size_t len) +{ + return { + .buffer_start = data, + .buffer_total_size = consumed + len, + .processed_offset = buffer_state.last_urc_processed, + .new_data_size = (consumed + len) - buffer_state.last_urc_processed, + .new_data_start = data + buffer_state.last_urc_processed, + .is_command_active = buffer_state.command_waiting + }; +} +#endif diff --git a/components/esp_modem/test/target_urc/README.md b/components/esp_modem/test/target_urc/README.md new file mode 100644 index 0000000000..2dea2fa12c --- /dev/null +++ b/components/esp_modem/test/target_urc/README.md @@ -0,0 +1,152 @@ +# ESP Modem Enhanced URC Test + +## Overview + +This test validates the enhanced URC (Unsolicited Result Code) interface with buffer consumption control. It demonstrates the new `set_enhanced_urc()` API that provides granular control over buffer consumption and complete buffer visibility. + +## Test Configuration + +- **Target**: ESP-AT device with HTTP server +- **UART**: 115200 baud, 8N1, TX=17, RX=18 +- **Buffer Size**: 1024 bytes +- **Timeout**: 15 seconds + +## Test Cases + +### 1. Enhanced URC Handler Registration +- **Objective**: Verify enhanced URC handler can be registered +- **Method**: Call `set_enhanced_urc()` with custom handler +- **Expected**: Handler receives `UrcBufferInfo` with complete buffer context + +### 2. Buffer Visibility +- **Objective**: Verify URC handler receives complete buffer information +- **Method**: Log buffer state information in handler +- **Expected**: Handler receives `buffer_start`, `buffer_total_size`, `processed_offset`, `new_data_size`, `is_command_active` + +### 3. Granular Consumption Control +- **Objective**: Verify handler can consume partial buffer data +- **Method**: Process HTTP URCs line-by-line using `CONSUME_PARTIAL` +- **Expected**: Each complete line is consumed individually, remaining data preserved + +### 4. Multi-part Response Handling +- **Objective**: Verify handling of chunked HTTP responses +- **Method**: Process 9 HTTP chunks from ESP-AT server +- **Expected**: All chunks processed correctly with proper offset tracking + +### 5. Transfer Completion Detection +- **Objective**: Verify detection of transfer completion message +- **Method**: Search for "Transfer completed" in buffer +- **Expected**: Completion detected and event group signaled + +### 6. Command State Awareness +- **Objective**: Verify handler knows command state +- **Method**: Check `is_command_active` flag during processing +- **Expected**: Flag correctly reflects command state + +## Test Implementation + +The test uses an ESP-AT device running an HTTP server that sends chunked responses. The enhanced URC handler processes each HTTP URC line individually and detects completion. + +### Key Components + +```cpp +// Enhanced URC handler registration +set_enhanced_urc(handle_enhanced_urc); + +// Handler implementation +static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo& info) +{ + // Process HTTP URCs with granular consumption control + if (line.starts_with("+HTTPCGET:")) { + // Consume this line only + return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, line_end + 1}; + } + + // Check for completion + if (buffer.find("Transfer completed") != std::string_view::npos) { + xEventGroupSetBits(s_event_group, transfer_completed); + return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0}; + } + + return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0}; +} +``` + +## Example Output + +### Successful Test Run +``` +I (908) urc_test: Starting Enhanced URC Test +I (938) urc_test: Start HTTP server...(0) +I (948) urc_test: HTTP GET...(43) +I (1228) urc_test: HTTP URC: +HTTPCGET:27,=== Async Response #4 === +I (2778) urc_test: HTTP URC: +HTTPCGET:61,[1/9] [633135 ms] This is a simulated slow server response. +I (4288) urc_test: HTTP URC: +HTTPCGET:71,[2/9] [634639 ms] Chunk 1: The ESP-AT HTTP server is demonstrating... +I (5788) urc_test: HTTP URC: +HTTPCGET:73,[3/9] [636143 ms] Chunk 2: ...asynchronous chunked transfer encoding... +I (7288) urc_test: HTTP URC: +HTTPCGET:72,[4/9] [637647 ms] Chunk 3: ...with artificial delays between chunks... +I (8788) urc_test: HTTP URC: +HTTPCGET:74,[5/9] [639151 ms] Chunk 4: ...to simulate real-world network conditions. +I (10288) urc_test: HTTP URC: +HTTPCGET:62,[6/9] [640655 ms] Chunk 5: Processing data... please wait... +I (11788) urc_test: HTTP URC: +HTTPCGET:63,[7/9] [642159 ms] Chunk 6: Still processing... almost done... +I (13288) urc_test: HTTP URC: +HTTPCGET:61,[8/9] [643663 ms] Chunk 7: Final chunk - transfer complete! +I (14758) urc_test: HTTP URC: +HTTPCGET:43,[9/9] [645168 ms] === END OF RESPONSE === +I (15258) urc_test: Transfer completed detected in buffer! +I (15298) urc_test: Enhanced URC test completed successfully! +I (15308) urc_test: The enhanced URC handler successfully processed all HTTP chunks +I (15308) urc_test: with granular buffer consumption control +``` + +### Debug Output (with ESP_LOG_LEVEL_DEBUG) +``` +D (958) urc_test: URC Buffer Info: total_size=43, processed_offset=0, new_data_size=43, command_active=false +D (958) urc_test: Buffer content (first 43 chars): AT+HTTPCGET="http://127.0.0.1:8080/async" +D (968) urc_test: Other data: AT+HTTPCGET="http://127.0.0.1:8080/async" +D (1218) urc_test: URC Buffer Info: total_size=85, processed_offset=43, new_data_size=42, command_active=false +D (1228) urc_test: Consuming 40 bytes (line_end=82, processed_offset=43) +D (2778) urc_test: Consuming 76 bytes (line_end=158, processed_offset=83) +``` + +### Failed Test (Timeout) +``` +I (908) urc_test: Starting Enhanced URC Test +I (948) urc_test: HTTP GET...(43) +E (15385) urc_test: Enhanced URC test timed out +I (15385) urc_test: Enhanced URC test done +``` + +## Test Validation + +### Success Criteria +- All 9 HTTP chunks processed successfully +- Transfer completion detected within 15 seconds +- No buffer arithmetic errors (no negative `new_data_size`) +- Proper consumption control (line-by-line processing) + +### Failure Indicators +- Test timeout (15 seconds) +- Buffer arithmetic errors (negative `new_data_size`) +- Missing HTTP chunks +- Transfer completion not detected + +## Dependencies + +- ESP-AT device with HTTP server capability +- UART connection configured +- `CONFIG_ESP_MODEM_URC_HANDLER=y` +- FreeRTOS event groups + +## Build and Run + +```bash +idf.py build +idf.py flash monitor +``` + +## Comparison with Legacy URC + +| Feature | Legacy URC | Enhanced URC | +|---------|------------|--------------| +| Buffer Consumption | All or nothing | Granular (partial) | +| Buffer Visibility | None | Complete context | +| Command State | Unknown | Known (`is_command_active`) | +| Processing State | Unknown | Tracked (`processed_offset`) | +| Multi-part Support | Limited | Full support | diff --git a/components/esp_modem/test/target_urc/main/urc_test.cpp b/components/esp_modem/test/target_urc/main/urc_test.cpp index 9d9de29dd5..a8f3090e5e 100644 --- a/components/esp_modem/test/target_urc/main/urc_test.cpp +++ b/components/esp_modem/test/target_urc/main/urc_test.cpp @@ -3,7 +3,26 @@ * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ + +/** + * @file urc_test.cpp + * @brief Enhanced URC (Unsolicited Result Code) Test + * + * This test demonstrates the new enhanced URC interface with buffer consumption control. + * It tests the following features: + * + * 1. Enhanced URC Handler Registration: Uses set_enhanced_urc() instead of set_urc() + * 2. Buffer Visibility: URC handler receives complete buffer information + * 3. Granular Consumption Control: Handler can consume none, partial, or all buffer data + * 4. Processing State Awareness: Handler knows what data is new vs. already processed + * 5. Command State Awareness: Handler knows if a command is currently active + * + * The test works with ESP-AT HTTP server that sends chunked responses, demonstrating + * how the enhanced URC handler can process multi-part responses with precise control + * over buffer consumption. + */ #include +#include #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_netif.h" @@ -67,7 +86,7 @@ class DCE : public esp_modem::DCE_T { bool http_get(const std::string &url) { std::string command = "AT+HTTPCGET=\"" + url + "\"\r\n"; - set_urc(handle_urc); + set_enhanced_urc(handle_enhanced_urc); auto ret = dte->write(esp_modem::DTE_Command(command)); ESP_LOGI(TAG, "HTTP GET...(%d)", static_cast(ret)); return ret > 0; @@ -82,25 +101,88 @@ class DCE : public esp_modem::DCE_T { static constexpr int transfer_completed = 1; private: - static esp_modem::command_result handle_urc(uint8_t *data, size_t len) + static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info) { - static int start_chunk = 0; - static int end_chunk = 0; - std::string_view chunk((const char*)data + start_chunk, len - start_chunk); - int newline = chunk.find('\n'); - if (newline == std::string_view::npos) { - end_chunk = len; // careful, this grows buffer usage - printf("."); - return esp_modem::command_result::TIMEOUT; + // Log buffer information for debugging + ESP_LOGD(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s", + info.buffer_total_size, info.processed_offset, info.new_data_size, + info.is_command_active ? "true" : "false"); + + // Debug: Show buffer content (first 200 chars) + if (info.buffer_total_size > 0) { + size_t debug_len = std::min(info.buffer_total_size, (size_t)200); + ESP_LOGD(TAG, "Buffer content (first %zu chars): %.*s", + debug_len, (int)debug_len, (const char*)info.buffer_start); } - printf("%.*s\n", newline, (char*)data + start_chunk); - start_chunk = end_chunk; - // check for the last one - constexpr char last_chunk[] = "Transfer completed"; - if (memmem(data, len, last_chunk, sizeof(last_chunk) - 1) != nullptr) { + + // Create string view of the entire buffer for processing + std::string_view buffer((const char*)info.buffer_start, info.buffer_total_size); + + // First, check if we have the completion message anywhere in the buffer + if (buffer.find("Transfer completed") != std::string_view::npos) { + ESP_LOGI(TAG, "Transfer completed detected in buffer!"); xEventGroupSetBits(s_event_group, transfer_completed); + // Consume everything + return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0}; } - return esp_modem::command_result::OK; + + // Process from the last processed offset + size_t search_start = info.processed_offset; + + // Look for complete lines starting from the processed offset + while (search_start < info.buffer_total_size) { + size_t line_end = buffer.find('\n', search_start); + + if (line_end == std::string_view::npos) { + // No complete line found, wait for more data + ESP_LOGD(TAG, "Waiting for more data... (search_start=%zu, total_size=%zu)", + search_start, info.buffer_total_size); + return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0}; + } + + // Found a complete line, process it + std::string_view line = buffer.substr(search_start, line_end - search_start); + + // Remove carriage return if present + if (!line.empty() && line.back() == '\r') { + line.remove_suffix(1); + } + + // Check if this is an HTTP URC + if (line.starts_with("+HTTPCGET:")) { + ESP_LOGI(TAG, "HTTP URC: %.*s", (int)line.length(), line.data()); + + // Check for transfer completion - look for "Transfer completed" anywhere in the line + if (line.find("Transfer completed") != std::string_view::npos) { + ESP_LOGI(TAG, "Transfer completed detected!"); + xEventGroupSetBits(s_event_group, transfer_completed); + } + + // Consume this line (including the newline) + size_t consume_size = line_end + 1 - info.processed_offset; + ESP_LOGD(TAG, "Consuming %zu bytes (line_end=%zu, processed_offset=%zu)", + consume_size, line_end, info.processed_offset); + return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size}; + + } else if (line.starts_with("+HTTPCGET")) { + // Partial HTTP URC, don't consume yet + ESP_LOGD(TAG, "Partial HTTP URC: %.*s", (int)line.length(), line.data()); + return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0}; + + } else if (!line.empty()) { + // Other data, log and consume + ESP_LOGD(TAG, "Other data: %.*s", (int)line.length(), line.data()); + size_t consume_size = line_end + 1 - info.processed_offset; + return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size}; + } + + // Move to next line + search_start = line_end + 1; + } + + // Processed all available data + ESP_LOGD(TAG, "Processed all available data"); + return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0}; } }; @@ -131,8 +213,8 @@ extern "C" void app_main(void) esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); dte_config.dte_buffer_size = 1024; - dte_config.uart_config.tx_io_num = 18; - dte_config.uart_config.rx_io_num = 17; + dte_config.uart_config.tx_io_num = 17; + dte_config.uart_config.rx_io_num = 18; auto uart_dte = esp_modem::create_uart_dte(&dte_config); if (uart_dte == nullptr) { ESP_LOGE(TAG, "Failed to create UART DTE"); @@ -144,15 +226,24 @@ extern "C" void app_main(void) return; } + ESP_LOGI(TAG, "Starting Enhanced URC Test"); + ESP_LOGI(TAG, "This test demonstrates the new enhanced URC interface with buffer consumption control"); + dce->start_http_server(); + ESP_LOGI(TAG, "Sending HTTP GET request with enhanced URC handler"); dce->http_get("http://127.0.0.1:8080/async"); EventBits_t bits = xEventGroupWaitBits(s_event_group, 1, pdTRUE, pdFALSE, pdMS_TO_TICKS(15000)); if (bits & DCE::transfer_completed) { - ESP_LOGI(TAG, "Request finished!"); + ESP_LOGI(TAG, "Enhanced URC test completed successfully!"); + ESP_LOGI(TAG, "The enhanced URC handler successfully processed all HTTP chunks"); + ESP_LOGI(TAG, "with granular buffer consumption control"); + } else { + ESP_LOGE(TAG, "Enhanced URC test timed out"); } + dce->sync(); vEventGroupDelete(s_event_group); - ESP_LOGI(TAG, "Done"); + ESP_LOGI(TAG, "Enhanced URC test done"); } diff --git a/docs/esp_modem/en/advanced_api.rst b/docs/esp_modem/en/advanced_api.rst index 4df349f8f7..699fc15cc3 100644 --- a/docs/esp_modem/en/advanced_api.rst +++ b/docs/esp_modem/en/advanced_api.rst @@ -38,6 +38,15 @@ work with commands differently. This might be useful to add some custom preproce Please check the ``modem_console`` example with ``CONFIG_EXAMPLE_MODEM_DEVICE_SHINY=y`` configuration which demonstrates overriding default ``command()`` method to implement URC processing in user space. +Enhanced URC (Unsolicited Result Code) Handling +------------------------------------------------ + +The ESP modem library provides two interfaces for handling URCs: a legacy callback-based interface and an enhanced interface with granular buffer consumption control. The enhanced interface, available through :cpp:func:`esp_modem::DCE_T::set_enhanced_urc`, provides complete buffer visibility and allows URC handlers to make precise decisions about buffer consumption. This is particularly useful for processing multi-part responses or when you need to consume only specific portions of the buffer while preserving other data for command processing. + +The enhanced URC handler receives a :cpp:struct:`esp_modem::DTE::UrcBufferInfo` structure containing the complete buffer context, including what data has been processed, what's new, and whether a command is currently active. The handler returns a :cpp:struct:`esp_modem::DTE::UrcConsumeInfo` structure specifying how much of the buffer to consume: none (wait for more data), partial (consume specific amount), or all (consume entire buffer). This granular control enables sophisticated URC processing scenarios such as line-by-line parsing of chunked HTTP responses or selective processing based on command state. + +For applications migrating from the legacy URC interface, the enhanced interface maintains backward compatibility while providing significantly more control over buffer management. The legacy :cpp:func:`esp_modem::DCE_T::set_urc` method continues to work as before, but new applications should consider using the enhanced interface for better buffer control and processing flexibility. + Create new communication interface ----------------------------------