Skip to content

Commit 52598e5

Browse files
committed
feat(modem): Add support for guessing mode
1 parent 72f4f7c commit 52598e5

File tree

6 files changed

+226
-7
lines changed

6 files changed

+226
-7
lines changed

components/esp_modem/include/cxx_include/esp_modem_dce.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ class DCE_Mode {
3030
~DCE_Mode() = default;
3131
bool set(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
3232
modem_mode get();
33+
modem_mode guess(DTE *dte, bool with_cmux = false);
3334

3435
private:
3536
bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
37+
modem_mode guess_unsafe(DTE *dte, bool with_cmux);
3638
modem_mode mode;
3739

3840
};
@@ -79,6 +81,11 @@ class DCE_T {
7981
return dte->command(command, std::move(got_line), time_ms);
8082
}
8183

84+
modem_mode guess_mode(bool with_cmux = false)
85+
{
86+
return mode.guess(dte.get(), with_cmux);
87+
}
88+
8289
bool set_mode(modem_mode m)
8390
{
8491
return mode.set(dte.get(), device.get(), netif, m);

components/esp_modem/include/cxx_include/esp_modem_dte.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ class DTE : public CommandableIf {
6565

6666
int write(DTE_Command command);
6767

68+
/**
69+
* @brief send data to the selected terminal, by default (without term_id argument)
70+
* this API works the same as write: sends data to the secondary terminal, which is
71+
* typically used as data terminal (for networking).
72+
*
73+
* @param data Data pointer to write
74+
* @param len Data len to write
75+
* @param term_id Terminal id: Primary if id==0, Secondary if id==1
76+
* @return number of bytes written
77+
*/
78+
int send(uint8_t *data, size_t len, int term_id = 1);
79+
6880
/**
6981
* @brief Reading from the underlying terminal
7082
* @param d Returning the data pointer of the received payload

components/esp_modem/include/cxx_include/esp_modem_types.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -37,6 +37,17 @@ enum class modem_mode {
3737
CMUX_MANUAL_DATA, /*!< Sets the primary terminal to DATA mode in manual CMUX */
3838
CMUX_MANUAL_COMMAND, /*!< Sets the primary terminal to COMMAND mode in manual CMUX */
3939
CMUX_MANUAL_SWAP, /*!< Swaps virtual terminals in manual CMUX mode (primary <-> secondary) */
40+
RESUME_DATA_MODE, /*!< This is used when the device is already in DATA mode and we need the modem lib to
41+
* enter the mode without switching. On success, we would end up in DATA-mode, UNDEF otherwise */
42+
RESUME_COMMAND_MODE, /*!< This is used when the device is already in COMMAND mode and we want to resume it
43+
* On success, we would end up in DATA-mode, UNDEF otherwise */
44+
RESUME_CMUX_MANUAL_MODE, /*!< This is used when the device is already in CMUX mode and we need the modem lib to
45+
* enter it without switching. On success, we would end up in CMUX_MANUAL-mode, UNDEF otherwise */
46+
RESUME_CMUX_MANUAL_DATA, /*!< This is used when the device is already in CMUX-DATA mode and we need the modem lib to
47+
* enter it without switching. On success, we would end up in CMUX_MANUAL-DATA mode, UNDEF otherwise */
48+
AUTODETECT, /*!< Auto-detection command: It tries to send a few packets in order to recognize which mode the
49+
* the device currently is and update the modem library mode. On success the modem is updated,
50+
* otherwise it's set to UNDEF */
4051
};
4152

4253
/**

components/esp_modem/src/esp_modem_dce.cpp

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -103,6 +103,51 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
103103
return true;
104104
case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
105105
break;
106+
case modem_mode::AUTODETECT: {
107+
auto guessed = guess_unsafe(dte, true);
108+
if (guessed == modem_mode::UNDEF) {
109+
return false;
110+
}
111+
// prepare the undefined mode before to allow all possible transitions
112+
if (!dte->set_mode(modem_mode::UNDEF)) {
113+
return false;
114+
}
115+
mode = modem_mode::UNDEF;
116+
ESP_LOGD("DCE mode", "Detected mode: %d", static_cast<int>(guessed));
117+
if (guessed == modem_mode::DATA_MODE) {
118+
return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_DATA_MODE);
119+
} else if (guessed == esp_modem::modem_mode::COMMAND_MODE) {
120+
return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_COMMAND_MODE);
121+
} else if (guessed == esp_modem::modem_mode::CMUX_MODE) {
122+
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_MODE)) {
123+
return false;
124+
}
125+
// now we guess the mode for each terminal
126+
guessed = guess_unsafe(dte, false);
127+
ESP_LOGD("DCE mode", "Detected mode on primary term: %d", static_cast<int>(guessed));
128+
// now we need to access the second terminal, so we could simply send a SWAP command
129+
// (switching to data mode does the swapping internally, so we only swap if we're in CMD mode)
130+
if (guessed == modem_mode::DATA_MODE) {
131+
// switch to DATA on the primary terminal and swap terminals
132+
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) {
133+
return false;
134+
}
135+
} else {
136+
// swap terminals
137+
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::CMUX_MANUAL_SWAP)) {
138+
return false;
139+
}
140+
}
141+
guessed = guess_unsafe(dte, false);
142+
ESP_LOGD("DCE mode", "Detected mode on secondary term: %d", static_cast<int>(guessed));
143+
if (guessed == modem_mode::DATA_MODE) {
144+
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) {
145+
return false;
146+
}
147+
}
148+
}
149+
return true;
150+
}
106151
case modem_mode::COMMAND_MODE:
107152
if (mode == modem_mode::COMMAND_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
108153
return false;
@@ -122,6 +167,32 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
122167
}
123168
mode = m;
124169
return true;
170+
case modem_mode::RESUME_DATA_MODE:
171+
if (!dte->set_mode(modem_mode::DATA_MODE)) {
172+
return false;
173+
}
174+
netif.start();
175+
mode = modem_mode::DATA_MODE;
176+
return true;
177+
case modem_mode::RESUME_COMMAND_MODE:
178+
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
179+
return false;
180+
}
181+
mode = modem_mode::COMMAND_MODE;
182+
return true;
183+
case modem_mode::RESUME_CMUX_MANUAL_MODE:
184+
if (!dte->set_mode(modem_mode::CMUX_MANUAL_MODE)) {
185+
return false;
186+
}
187+
mode = modem_mode::CMUX_MANUAL_MODE;
188+
return true;
189+
case modem_mode::RESUME_CMUX_MANUAL_DATA:
190+
if (!dte->set_mode(modem_mode::CMUX_MANUAL_SWAP)) {
191+
return false;
192+
}
193+
netif.start();
194+
mode = modem_mode::CMUX_MANUAL_MODE;
195+
return true;
125196
case modem_mode::DATA_MODE:
126197
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
127198
return false;
@@ -191,4 +262,114 @@ modem_mode DCE_Mode::get()
191262
return mode;
192263
}
193264

265+
modem_mode DCE_Mode::guess(DTE *dte, bool with_cmux)
266+
{
267+
Scoped<DTE> lock(*dte);
268+
return guess_unsafe(dte, with_cmux);
269+
}
270+
271+
/**
272+
* This namespace contains probe packets and expected replies on 3 different protocols,
273+
* the modem device could use (as well as timeouts and mode ids for synchronisation)
274+
*/
275+
namespace probe {
276+
277+
namespace ppp {
278+
// Test that we're in the PPP mode by sending an LCP protocol echo request and expecting LCP echo reply
279+
constexpr std::array<uint8_t, 16> lcp_echo_request = {0x7e, 0xff, 0x03, 0xc0, 0x21, 0x09, 0x01, 0x00, 0x08, 0x99, 0xd1, 0x35, 0xc1, 0x8e, 0x2c, 0x7e };
280+
constexpr std::array<uint8_t, 5> lcp_echo_reply_head = {0x7e, 0xff, 0x7d, 0x23, 0xc0};
281+
const size_t mode = 1 << 0;
282+
const int timeout = 200;
283+
}
284+
285+
namespace cmd {
286+
// For command mode, we just send a simple AT command
287+
const char at[] = "\r\nAT\r\n";
288+
const size_t max_at_reply = 16; // account for some whitespaces and/or CMUX encapsulation
289+
const char reply[] = { 'O', 'K' };
290+
const int mode = 1 << 1;
291+
const int timeout = 500;
292+
}
293+
294+
namespace cmux {
295+
// For CMUX mode, we send an SABM on control terminal (0)
296+
const uint8_t sabm0_reqest[] = {0xf9, 0x03, 0x3f, 0x01, 0x1c, 0xf9};
297+
const uint8_t sabm0_reply[] = {0xf9, 0x03, 0x73, 0x01};
298+
const int mode = 1 << 0;
299+
const int timeout = 200;
300+
}
301+
};
302+
303+
modem_mode DCE_Mode::guess_unsafe(DTE *dte, bool with_cmux)
304+
{
305+
// placeholder for reply and its size, since it could come in pieces, and we have to cache
306+
// this is captured by the lambda by reference.
307+
// must make sure the lambda is cleared before exiting this function (done by dte->on_read(nullptr))
308+
uint8_t reply[std::max(probe::cmd::max_at_reply, std::max(sizeof(probe::ppp::lcp_echo_request), sizeof(probe::cmux::sabm0_reply)))];
309+
size_t reply_pos = 0;
310+
auto signal = std::make_shared<SignalGroup>();
311+
std::weak_ptr<SignalGroup> weak_signal = signal;
312+
dte->on_read([weak_signal, with_cmux, &reply, &reply_pos](uint8_t *data, size_t len) {
313+
// storing the response in the `reply` array and de-fragmenting
314+
if (reply_pos >= sizeof(reply)) {
315+
return command_result::TIMEOUT;
316+
}
317+
auto reply_size = std::min((size_t)sizeof(reply) - reply_pos, len);
318+
::memcpy(reply + reply_pos, data, reply_size);
319+
reply_pos += reply_size;
320+
ESP_LOG_BUFFER_HEXDUMP("esp-modem: guess mode data:", reply, reply_pos, ESP_LOG_DEBUG);
321+
322+
// Check whether the response resembles the "golden" reply (for these 3 protocols)
323+
if (reply_pos >= sizeof(probe::ppp::lcp_echo_reply_head)) {
324+
// check for initial 2 bytes
325+
auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head.data(), 2));
326+
// and check the other two bytes for protocol ID: LCP
327+
if (ptr && ptr[3] == probe::ppp::lcp_echo_reply_head[3] && ptr[4] == probe::ppp::lcp_echo_reply_head[4]) {
328+
if (auto signal = weak_signal.lock()) {
329+
signal->set(probe::ppp::mode);
330+
}
331+
}
332+
}
333+
if (reply_pos >= 4 && memmem(reply, reply_pos, probe::cmd::reply, sizeof(probe::cmd::reply))) {
334+
if (reply[0] != 0xf9) { // double check that the reply is not wrapped in CMUX headers
335+
if (auto signal = weak_signal.lock()) {
336+
signal->set(probe::cmd::mode);
337+
}
338+
}
339+
}
340+
if (with_cmux && reply_pos >= sizeof(probe::cmux::sabm0_reply)) {
341+
// checking the initial 3 bytes
342+
auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::cmux::sabm0_reply, 3));
343+
// and checking that DLCI is 0 (control frame)
344+
if (ptr && (ptr[3] >> 2) == 0) {
345+
if (auto signal = weak_signal.lock()) {
346+
signal->set(probe::cmux::mode);
347+
}
348+
}
349+
}
350+
return command_result::TIMEOUT;
351+
});
352+
auto guessed = modem_mode::UNDEF;
353+
// Check the PPP mode fist by sending LCP echo request
354+
dte->send((uint8_t *)probe::ppp::lcp_echo_request.data(), sizeof(probe::ppp::lcp_echo_request), 0);
355+
if (signal->wait(probe::ppp::mode, probe::ppp::timeout)) {
356+
guessed = modem_mode::DATA_MODE;
357+
} else { // LCP echo timeout
358+
// now check for AT mode
359+
reply_pos = 0;
360+
dte->send((uint8_t *)probe::cmd::at, sizeof(probe::cmd::at), 0);
361+
if (signal->wait(probe::cmd::mode, probe::cmd::timeout)) {
362+
guessed = modem_mode::COMMAND_MODE;
363+
} else if (with_cmux) { // no AT reply, check for CMUX mode (if requested)
364+
reply_pos = 0;
365+
dte->send((uint8_t *) probe::cmux::sabm0_reqest, sizeof(probe::cmux::sabm0_reqest), 0);
366+
if (signal->wait(probe::cmux::mode, probe::cmux::timeout)) {
367+
guessed = modem_mode::CMUX_MODE;
368+
}
369+
}
370+
}
371+
dte->on_read(nullptr);
372+
return guessed;
373+
}
374+
194375
} // esp_modem

components/esp_modem/src/esp_modem_dte.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,13 @@ bool DTE::set_mode(modem_mode m)
223223
}
224224
}
225225
// transitions (COMMAND|DUAL|CMUX|UNDEF) -> DATA
226-
if (m == modem_mode::DATA_MODE) {
226+
if (m == modem_mode::DATA_MODE || m == modem_mode::RESUME_DATA_MODE) {
227227
if (mode == modem_mode::CMUX_MODE || mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::DUAL_MODE) {
228228
// mode stays the same, but need to swap terminals (as command has been switched)
229229
secondary_term.swap(primary_term);
230230
set_command_callbacks();
231231
} else {
232-
mode = m;
232+
mode = modem_mode::DATA_MODE;
233233
}
234234
return true;
235235
}
@@ -316,6 +316,12 @@ int DTE::write(uint8_t *data, size_t len)
316316
return secondary_term->write(data, len);
317317
}
318318

319+
int DTE::send(uint8_t *data, size_t len, int term_id)
320+
{
321+
Terminal *term = term_id == 0 ? primary_term.get() : secondary_term.get();
322+
return term->write(data, len);
323+
}
324+
319325
int DTE::write(DTE_Command command)
320326
{
321327
return primary_term->write(command.data, command.len);

components/esp_modem/src/esp_modem_netif.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -87,8 +87,10 @@ void Netif::start()
8787
receive(data, len);
8888
return true;
8989
});
90-
signal.set(PPP_STARTED);
91-
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
90+
if (!signal.is_any(PPP_STARTED)) {
91+
signal.set(PPP_STARTED);
92+
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
93+
}
9294
}
9395

9496
void Netif::stop()

0 commit comments

Comments
 (0)