1
1
/*
2
- * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
2
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
3
3
*
4
4
* SPDX-License-Identifier: Apache-2.0
5
5
*/
@@ -103,6 +103,51 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
103
103
return true ;
104
104
case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
105
105
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
+ }
106
151
case modem_mode::COMMAND_MODE:
107
152
if (mode == modem_mode::COMMAND_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
108
153
return false ;
@@ -122,6 +167,32 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
122
167
}
123
168
mode = m;
124
169
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 ;
125
196
case modem_mode::DATA_MODE:
126
197
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
127
198
return false ;
@@ -191,4 +262,114 @@ modem_mode DCE_Mode::get()
191
262
return mode;
192
263
}
193
264
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\n AT\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
+
194
375
} // esp_modem
0 commit comments