mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-11-15 23:09:27 +01:00
feat(modem): Add enhanced URC observer API
This commit is contained in:
@@ -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
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
*/
|
*/
|
||||||
@@ -97,10 +97,21 @@ void wakeup_modem(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
#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);
|
// Log buffer information for debugging
|
||||||
return command_result::TIMEOUT;
|
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
|
#endif
|
||||||
|
|
||||||
@@ -381,14 +392,14 @@ extern "C" void app_main(void)
|
|||||||
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
|
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
|
||||||
});
|
});
|
||||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
#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;
|
static int cnt = 0;
|
||||||
if (++cnt % 2) {
|
if (++cnt % 2) {
|
||||||
ESP_LOGI(TAG, "Adding URC handler");
|
ESP_LOGI(TAG, "Adding enhanced URC handler");
|
||||||
dce->set_urc(handle_urc);
|
dce->set_enhanced_urc(handle_enhanced_urc);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "URC removed");
|
ESP_LOGI(TAG, "Enhanced URC removed");
|
||||||
dce->set_urc(nullptr);
|
dce->set_enhanced_urc(nullptr);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ public:
|
|||||||
{
|
{
|
||||||
dte->set_urc_cb(on_read_cb);
|
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
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -115,6 +115,42 @@ public:
|
|||||||
{
|
{
|
||||||
command_cb.urc_handler = std::move(line_cb);
|
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<UrcConsumeInfo(const UrcBufferInfo &)> 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
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,6 +207,33 @@ private:
|
|||||||
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
|
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
|
||||||
void exit_cmux_internal(); /*!< Cleanup CMUX */
|
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 */
|
Lock internal_lock{}; /*!< Locks DTE operations */
|
||||||
unique_buffer buffer; /*!< DTE buffer */
|
unique_buffer buffer; /*!< DTE buffer */
|
||||||
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
|
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
|
||||||
@@ -216,6 +279,7 @@ private:
|
|||||||
struct command_cb {
|
struct command_cb {
|
||||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||||
got_line_cb urc_handler {}; /*!< URC callback if enabled */
|
got_line_cb urc_handler {}; /*!< URC callback if enabled */
|
||||||
|
enhanced_urc_cb enhanced_urc_handler {}; /*!< Enhanced URC callback with consumption control */
|
||||||
#endif
|
#endif
|
||||||
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
|
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
|
||||||
got_line_cb got_line; /*!< Supplied command callback */
|
got_line_cb got_line; /*!< Supplied command callback */
|
||||||
@@ -223,7 +287,7 @@ private:
|
|||||||
char separator{}; /*!< Command reply separator (end of line/processing unit) */
|
char separator{}; /*!< Command reply separator (end of line/processing unit) */
|
||||||
command_result result{}; /*!< Command return code */
|
command_result result{}; /*!< Command return code */
|
||||||
SignalGroup signal; /*!< Event group used to signal request-response operations */
|
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 */
|
bool wait_for_line(uint32_t time_ms) /*!< Waiting for command processing */
|
||||||
{
|
{
|
||||||
return signal.wait_any(command_cb::GOT_LINE, time_ms);
|
return signal.wait_any(command_cb::GOT_LINE, time_ms);
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ void DTE::set_command_callbacks()
|
|||||||
{
|
{
|
||||||
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
|
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||||
Scoped<Lock> l(command_cb.line_lock);
|
Scoped<Lock> 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
|
#ifndef CONFIG_ESP_MODEM_URC_HANDLER
|
||||||
if (command_cb.got_line == nullptr || command_cb.result != command_result::TIMEOUT) {
|
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)
|
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);
|
std::memcpy(inflatable.current(), data, len);
|
||||||
data = inflatable.begin();
|
data = inflatable.begin();
|
||||||
}
|
}
|
||||||
if (command_cb.process_line(data, inflatable.consumed, len)) {
|
if (command_cb.process_line(data, inflatable.consumed, len, this)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// at this point we're sure that the data processing hasn't finished,
|
// 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;
|
inflatable.consumed += len;
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
if (command_cb.process_line(data, 0, len)) {
|
if (command_cb.process_line(data, 0, len, this)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// cannot inflate and the processing hasn't finishes in the first iteration, but continue
|
// 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) {
|
if (buffer.size > buffer.consumed) {
|
||||||
data = buffer.get();
|
data = buffer.get();
|
||||||
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
buffer.consumed += len;
|
buffer.consumed += len;
|
||||||
@@ -121,7 +125,7 @@ void DTE::set_command_callbacks()
|
|||||||
inflatable.grow(inflatable.consumed + len);
|
inflatable.grow(inflatable.consumed + len);
|
||||||
}
|
}
|
||||||
len = primary_term->read(inflatable.current(), 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;
|
return true;
|
||||||
}
|
}
|
||||||
inflatable.consumed += len;
|
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)
|
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
|
||||||
{
|
{
|
||||||
Scoped<Lock> l1(internal_lock);
|
Scoped<Lock> 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);
|
command_cb.set(got_line, separator);
|
||||||
primary_term->write((uint8_t *)command.c_str(), command.length());
|
primary_term->write((uint8_t *)command.c_str(), command.length());
|
||||||
command_cb.wait_for_line(time_ms);
|
command_cb.wait_for_line(time_ms);
|
||||||
command_cb.set(nullptr);
|
command_cb.set(nullptr);
|
||||||
|
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||||
|
// Track command end
|
||||||
|
buffer_state.command_waiting = false;
|
||||||
|
#endif
|
||||||
buffer.consumed = 0;
|
buffer.consumed = 0;
|
||||||
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
|
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
|
||||||
inflatable.deflate();
|
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
|
// returning true indicates that the processing finished and lower layers can destroy the accumulated buffer
|
||||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||||
bool consume_buffer = false;
|
// Call enhanced URC handler if registered
|
||||||
if (urc_handler) {
|
if (enhanced_urc_handler && dte) {
|
||||||
consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
|
||||||
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
|
// Fallback to legacy URC handler if enhanced handler not set
|
||||||
|
if (urc_handler) {
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
|
// Continue with normal command processing
|
||||||
|
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||||
|
return false; // Command processing continues
|
||||||
|
}
|
||||||
|
|
||||||
if (memchr(data + consumed, separator, len)) {
|
if (memchr(data + consumed, separator, len)) {
|
||||||
result = got_line(data, consumed + len);
|
result = got_line(data, consumed + len);
|
||||||
if (result == command_result::OK || result == command_result::FAIL) {
|
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):
|
unique_buffer::unique_buffer(size_t size):
|
||||||
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}
|
data(std::make_unique<uint8_t[]>(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
|
||||||
|
|||||||
152
components/esp_modem/test/target_urc/README.md
Normal file
152
components/esp_modem/test/target_urc/README.md
Normal file
@@ -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 |
|
||||||
@@ -3,7 +3,26 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
* 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 <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/event_groups.h"
|
#include "freertos/event_groups.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
@@ -67,7 +86,7 @@ public:
|
|||||||
bool http_get(const std::string &url)
|
bool http_get(const std::string &url)
|
||||||
{
|
{
|
||||||
std::string command = "AT+HTTPCGET=\"" + url + "\"\r\n";
|
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));
|
auto ret = dte->write(esp_modem::DTE_Command(command));
|
||||||
ESP_LOGI(TAG, "HTTP GET...(%d)", static_cast<int>(ret));
|
ESP_LOGI(TAG, "HTTP GET...(%d)", static_cast<int>(ret));
|
||||||
return ret > 0;
|
return ret > 0;
|
||||||
@@ -82,25 +101,88 @@ public:
|
|||||||
|
|
||||||
static constexpr int transfer_completed = 1;
|
static constexpr int transfer_completed = 1;
|
||||||
private:
|
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;
|
// Log buffer information for debugging
|
||||||
static int end_chunk = 0;
|
ESP_LOGD(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
|
||||||
std::string_view chunk((const char*)data + start_chunk, len - start_chunk);
|
info.buffer_total_size, info.processed_offset, info.new_data_size,
|
||||||
int newline = chunk.find('\n');
|
info.is_command_active ? "true" : "false");
|
||||||
if (newline == std::string_view::npos) {
|
|
||||||
end_chunk = len; // careful, this grows buffer usage
|
// Debug: Show buffer content (first 200 chars)
|
||||||
printf(".");
|
if (info.buffer_total_size > 0) {
|
||||||
return esp_modem::command_result::TIMEOUT;
|
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;
|
// Create string view of the entire buffer for processing
|
||||||
// check for the last one
|
std::string_view buffer((const char*)info.buffer_start, info.buffer_total_size);
|
||||||
constexpr char last_chunk[] = "Transfer completed";
|
|
||||||
if (memmem(data, len, last_chunk, sizeof(last_chunk) - 1) != nullptr) {
|
// 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);
|
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();
|
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||||
dte_config.dte_buffer_size = 1024;
|
dte_config.dte_buffer_size = 1024;
|
||||||
dte_config.uart_config.tx_io_num = 18;
|
dte_config.uart_config.tx_io_num = 17;
|
||||||
dte_config.uart_config.rx_io_num = 17;
|
dte_config.uart_config.rx_io_num = 18;
|
||||||
auto uart_dte = esp_modem::create_uart_dte(&dte_config);
|
auto uart_dte = esp_modem::create_uart_dte(&dte_config);
|
||||||
if (uart_dte == nullptr) {
|
if (uart_dte == nullptr) {
|
||||||
ESP_LOGE(TAG, "Failed to create UART DTE");
|
ESP_LOGE(TAG, "Failed to create UART DTE");
|
||||||
@@ -144,15 +226,24 @@ extern "C" void app_main(void)
|
|||||||
return;
|
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();
|
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");
|
dce->http_get("http://127.0.0.1:8080/async");
|
||||||
|
|
||||||
EventBits_t bits = xEventGroupWaitBits(s_event_group, 1, pdTRUE, pdFALSE, pdMS_TO_TICKS(15000));
|
EventBits_t bits = xEventGroupWaitBits(s_event_group, 1, pdTRUE, pdFALSE, pdMS_TO_TICKS(15000));
|
||||||
if (bits & DCE::transfer_completed) {
|
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();
|
dce->sync();
|
||||||
vEventGroupDelete(s_event_group);
|
vEventGroupDelete(s_event_group);
|
||||||
ESP_LOGI(TAG, "Done");
|
ESP_LOGI(TAG, "Enhanced URC test done");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
Create new communication interface
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user