Skip to content

Commit 4889dd6

Browse files
committed
feat(modem): Add enhanced URC observer API
1 parent 134247d commit 4889dd6

File tree

7 files changed

+440
-40
lines changed

7 files changed

+440
-40
lines changed

components/esp_modem/examples/modem_console/main/modem_console_main.cpp

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Unlicense OR CC0-1.0
55
*/
@@ -97,10 +97,21 @@ void wakeup_modem(void)
9797
}
9898

9999
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
100-
command_result handle_urc(uint8_t *data, size_t len)
100+
esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info)
101101
{
102-
ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO);
103-
return command_result::TIMEOUT;
102+
// Log buffer information for debugging
103+
ESP_LOGI(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
104+
info.buffer_total_size, info.processed_offset, info.new_data_size,
105+
info.is_command_active ? "true" : "false");
106+
107+
// Log the new data content
108+
if (info.new_data_size > 0) {
109+
ESP_LOG_BUFFER_HEXDUMP("on_read", info.new_data_start, info.new_data_size, ESP_LOG_INFO);
110+
}
111+
112+
// For console example, we just log and don't consume anything
113+
// This allows the data to be processed by command handlers
114+
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
104115
}
105116
#endif
106117

@@ -381,14 +392,14 @@ extern "C" void app_main(void)
381392
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
382393
});
383394
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
384-
const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) {
395+
const ConsoleCommand HandleURC("urc", "toggle enhanced urc handling", no_args, [&](ConsoleCommand * c) {
385396
static int cnt = 0;
386397
if (++cnt % 2) {
387-
ESP_LOGI(TAG, "Adding URC handler");
388-
dce->set_urc(handle_urc);
398+
ESP_LOGI(TAG, "Adding enhanced URC handler");
399+
dce->set_enhanced_urc(handle_enhanced_urc);
389400
} else {
390-
ESP_LOGI(TAG, "URC removed");
391-
dce->set_urc(nullptr);
401+
ESP_LOGI(TAG, "Enhanced URC removed");
402+
dce->set_enhanced_urc(nullptr);
392403
}
393404
return 0;
394405
});

components/esp_modem/include/cxx_include/esp_modem_dce_template.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ class DCE_T {
103103
{
104104
dte->set_urc_cb(on_read_cb);
105105
}
106+
107+
void set_enhanced_urc(esp_modem::DTE::enhanced_urc_cb enhanced_cb)
108+
{
109+
dte->set_enhanced_urc_cb(enhanced_cb);
110+
}
106111
#endif
107112

108113
/**

components/esp_modem/include/cxx_include/esp_modem_dte.hpp

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -115,6 +115,42 @@ class DTE : public CommandableIf {
115115
{
116116
command_cb.urc_handler = std::move(line_cb);
117117
}
118+
119+
/**
120+
* @brief Enhanced URC handler with buffer consumption control
121+
* @param buffer_info Information about the current buffer state
122+
* @return Information about how much of the buffer to consume
123+
*/
124+
struct UrcBufferInfo {
125+
const uint8_t* buffer_start; // Start of entire buffer
126+
size_t buffer_total_size; // Total buffer size
127+
size_t processed_offset; // Offset of already processed data
128+
size_t new_data_size; // Size of new data since last call
129+
const uint8_t* new_data_start; // Pointer to start of new data
130+
bool is_command_active; // Whether a command is currently waiting for response
131+
};
132+
133+
enum class UrcConsumeResult {
134+
CONSUME_NONE, // Don't consume anything, continue waiting
135+
CONSUME_PARTIAL, // Consume only part of the buffer
136+
CONSUME_ALL // Consume entire buffer
137+
};
138+
139+
struct UrcConsumeInfo {
140+
UrcConsumeResult result;
141+
size_t consume_size; // How many bytes to consume (0 = none, SIZE_MAX = all)
142+
};
143+
144+
typedef std::function<UrcConsumeInfo(const UrcBufferInfo &)> enhanced_urc_cb;
145+
146+
/**
147+
* @brief Set enhanced URC callback with buffer consumption control
148+
* @param enhanced_cb Enhanced callback that can control buffer consumption
149+
*/
150+
void set_enhanced_urc_cb(enhanced_urc_cb enhanced_cb)
151+
{
152+
command_cb.enhanced_urc_handler = std::move(enhanced_cb);
153+
}
118154
#endif
119155

120156
/**
@@ -171,6 +207,33 @@ class DTE : public CommandableIf {
171207
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
172208
void exit_cmux_internal(); /*!< Cleanup CMUX */
173209

210+
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
211+
/**
212+
* @brief Buffer state tracking for enhanced URC processing
213+
*/
214+
struct BufferState {
215+
size_t total_processed = 0; /*!< Total bytes processed by URC handlers */
216+
size_t last_urc_processed = 0; /*!< Last offset processed by URC */
217+
bool command_waiting = false; /*!< Whether command is waiting for response */
218+
size_t command_start_offset = 0; /*!< Where current command response started */
219+
} buffer_state;
220+
221+
/**
222+
* @brief Update buffer state when new data arrives
223+
* @param new_data_size Size of new data added to buffer
224+
*/
225+
void update_buffer_state(size_t new_data_size);
226+
227+
/**
228+
* @brief Create URC buffer information for enhanced handlers
229+
* @param data Buffer data pointer
230+
* @param consumed Already consumed bytes
231+
* @param len New data length
232+
* @return UrcBufferInfo structure with complete buffer context
233+
*/
234+
UrcBufferInfo create_urc_info(uint8_t* data, size_t consumed, size_t len);
235+
#endif
236+
174237
Lock internal_lock{}; /*!< Locks DTE operations */
175238
unique_buffer buffer; /*!< DTE buffer */
176239
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
@@ -216,14 +279,15 @@ class DTE : public CommandableIf {
216279
struct command_cb {
217280
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
218281
got_line_cb urc_handler {}; /*!< URC callback if enabled */
282+
enhanced_urc_cb enhanced_urc_handler {}; /*!< Enhanced URC callback with consumption control */
219283
#endif
220284
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
221285
got_line_cb got_line; /*!< Supplied command callback */
222286
Lock line_lock{}; /*!< Command callback locking mechanism */
223287
char separator{}; /*!< Command reply separator (end of line/processing unit) */
224288
command_result result{}; /*!< Command return code */
225289
SignalGroup signal; /*!< Event group used to signal request-response operations */
226-
bool process_line(uint8_t *data, size_t consumed, size_t len); /*!< Lets the processing callback handle one line (processing unit) */
290+
bool process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte = nullptr); /*!< Lets the processing callback handle one line (processing unit) */
227291
bool wait_for_line(uint32_t time_ms) /*!< Waiting for command processing */
228292
{
229293
return signal.wait_any(command_cb::GOT_LINE, time_ms);

components/esp_modem/src/esp_modem_dte.cpp

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ void DTE::set_command_callbacks()
6565
{
6666
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
6767
Scoped<Lock> l(command_cb.line_lock);
68+
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
69+
// Update buffer state when new data arrives
70+
update_buffer_state(len);
71+
#endif
6872
#ifndef CONFIG_ESP_MODEM_URC_HANDLER
6973
if (command_cb.got_line == nullptr || command_cb.result != command_result::TIMEOUT) {
7074
return false; // this line has been processed already (got OK or FAIL previously)
@@ -80,7 +84,7 @@ void DTE::set_command_callbacks()
8084
std::memcpy(inflatable.current(), data, len);
8185
data = inflatable.begin();
8286
}
83-
if (command_cb.process_line(data, inflatable.consumed, len)) {
87+
if (command_cb.process_line(data, inflatable.consumed, len, this)) {
8488
return true;
8589
}
8690
// at this point we're sure that the data processing hasn't finished,
@@ -92,7 +96,7 @@ void DTE::set_command_callbacks()
9296
inflatable.consumed += len;
9397
return false;
9498
#else
95-
if (command_cb.process_line(data, 0, len)) {
99+
if (command_cb.process_line(data, 0, len, this)) {
96100
return true;
97101
}
98102
// cannot inflate and the processing hasn't finishes in the first iteration, but continue
@@ -105,7 +109,7 @@ void DTE::set_command_callbacks()
105109
if (buffer.size > buffer.consumed) {
106110
data = buffer.get();
107111
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
108-
if (command_cb.process_line(data, buffer.consumed, len)) {
112+
if (command_cb.process_line(data, buffer.consumed, len, this)) {
109113
return true;
110114
}
111115
buffer.consumed += len;
@@ -121,7 +125,7 @@ void DTE::set_command_callbacks()
121125
inflatable.grow(inflatable.consumed + len);
122126
}
123127
len = primary_term->read(inflatable.current(), len);
124-
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len)) {
128+
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len, this)) {
125129
return true;
126130
}
127131
inflatable.consumed += len;
@@ -150,10 +154,19 @@ void DTE::set_command_callbacks()
150154
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
151155
{
152156
Scoped<Lock> l1(internal_lock);
157+
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
158+
// Track command start
159+
buffer_state.command_waiting = true;
160+
buffer_state.command_start_offset = buffer_state.total_processed;
161+
#endif
153162
command_cb.set(got_line, separator);
154163
primary_term->write((uint8_t *)command.c_str(), command.length());
155164
command_cb.wait_for_line(time_ms);
156165
command_cb.set(nullptr);
166+
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
167+
// Track command end
168+
buffer_state.command_waiting = false;
169+
#endif
157170
buffer.consumed = 0;
158171
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
159172
inflatable.deflate();
@@ -365,18 +378,54 @@ void DTE::on_read(got_line_cb on_read_cb)
365378
});
366379
}
367380

368-
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len)
381+
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte)
369382
{
370383
// returning true indicates that the processing finished and lower layers can destroy the accumulated buffer
371384
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
372-
bool consume_buffer = false;
385+
// Call enhanced URC handler if registered
386+
if (enhanced_urc_handler && dte) {
387+
// Create buffer info for enhanced URC handler
388+
UrcBufferInfo buffer_info = dte->create_urc_info(data, consumed, len);
389+
390+
// Call enhanced URC handler
391+
UrcConsumeInfo consume_info = enhanced_urc_handler(buffer_info);
392+
393+
// Handle consumption control
394+
switch (consume_info.result) {
395+
case UrcConsumeResult::CONSUME_NONE:
396+
// Don't consume anything, continue with command processing
397+
break;
398+
399+
case UrcConsumeResult::CONSUME_PARTIAL:
400+
// Consume only specified amount
401+
dte->buffer_state.last_urc_processed += consume_info.consume_size;
402+
// Adjust data pointers for command processing
403+
data += consume_info.consume_size;
404+
consumed = (consumed + len) - consume_info.consume_size;
405+
len = 0;
406+
break;
407+
408+
case UrcConsumeResult::CONSUME_ALL:
409+
// Consume entire buffer
410+
dte->buffer_state.last_urc_processed = consumed + len;
411+
return true; // Signal buffer consumption
412+
}
413+
}
414+
415+
// Fallback to legacy URC handler if enhanced handler not set
373416
if (urc_handler) {
374-
consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
417+
bool consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
418+
if (result != command_result::TIMEOUT || got_line == nullptr) {
419+
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
420+
}
375421
}
422+
#endif
423+
424+
// Continue with normal command processing
376425
if (result != command_result::TIMEOUT || got_line == nullptr) {
377-
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
426+
return false; // Command processing continues
378427
}
379-
#endif
428+
380429
if (memchr(data + consumed, separator, len)) {
381430
result = got_line(data, consumed + len);
382431
if (result == command_result::OK || result == command_result::FAIL) {
@@ -423,3 +472,22 @@ void DTE::extra_buffer::grow(size_t need_size)
423472
*/
424473
unique_buffer::unique_buffer(size_t size):
425474
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}
475+
476+
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
477+
void DTE::update_buffer_state(size_t new_data_size)
478+
{
479+
buffer_state.total_processed += new_data_size;
480+
}
481+
482+
DTE::UrcBufferInfo DTE::create_urc_info(uint8_t* data, size_t consumed, size_t len)
483+
{
484+
return {
485+
.buffer_start = data,
486+
.buffer_total_size = consumed + len,
487+
.processed_offset = buffer_state.last_urc_processed,
488+
.new_data_size = (consumed + len) - buffer_state.last_urc_processed,
489+
.new_data_start = data + buffer_state.last_urc_processed,
490+
.is_command_active = buffer_state.command_waiting
491+
};
492+
}
493+
#endif

0 commit comments

Comments
 (0)