From 85abac4af5633b4dc426549305b9f3c62226ffee Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 10 Jul 2024 20:13:34 +0200 Subject: [PATCH] feat(modem): Add support for guessing mode --- .../include/cxx_include/esp_modem_dce.hpp | 9 +- .../include/cxx_include/esp_modem_dte.hpp | 1 + .../include/cxx_include/esp_modem_types.hpp | 8 +- components/esp_modem/src/esp_modem_dce.cpp | 177 +++++++++++++++++- components/esp_modem/src/esp_modem_dte.cpp | 12 +- components/esp_modem/src/esp_modem_netif.cpp | 8 +- 6 files changed, 206 insertions(+), 9 deletions(-) diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp index 1b04bbfdd74..da54a946952 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,9 +30,11 @@ class DCE_Mode { ~DCE_Mode() = default; bool set(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m); modem_mode get(); + modem_mode guess(DTE *dte, bool with_cmux = false); private: bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m); + modem_mode guess_unsafe(DTE *dte, bool with_cmux); modem_mode mode; }; @@ -79,6 +81,11 @@ class DCE_T { return dte->command(command, std::move(got_line), time_ms); } + modem_mode guess_mode(bool with_cmux = false) + { + return mode.guess(dte.get(), with_cmux); + } + bool set_mode(modem_mode m) { return mode.set(dte.get(), device.get(), netif, m); 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 b865180c5fe..149ec6b3f4e 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp @@ -64,6 +64,7 @@ class DTE : public CommandableIf { int write(uint8_t *data, size_t len) override; int write(DTE_Command command); + int send(uint8_t *data, size_t len, int term_id = 1); /** * @brief Reading from the underlying terminal diff --git a/components/esp_modem/include/cxx_include/esp_modem_types.hpp b/components/esp_modem/include/cxx_include/esp_modem_types.hpp index 944427156d8..5c0c2d4c735 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_types.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_types.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -37,6 +37,11 @@ enum class modem_mode { CMUX_MANUAL_DATA, /*!< Sets the primary terminal to DATA mode in manual CMUX */ CMUX_MANUAL_COMMAND, /*!< Sets the primary terminal to COMMAND mode in manual CMUX */ CMUX_MANUAL_SWAP, /*!< Swaps virtual terminals in manual CMUX mode (primary <-> secondary) */ + RESUME_DATA_MODE, + RESUME_COMMAND_MODE, + RESUME_CMUX_MANUAL_MODE, + RESUME_CMUX_MANUAL_DATA, + AUTODETECT, }; /** @@ -57,6 +62,7 @@ struct PdpContext { explicit PdpContext(std::string apn) : apn(std::move(apn)) {} size_t context_id = 1; std::string protocol_type = "IP"; +// std::string protocol_type = "IPV6"; std::string apn; }; diff --git a/components/esp_modem/src/esp_modem_dce.cpp b/components/esp_modem/src/esp_modem_dce.cpp index 55c51f0cdc4..054730be17c 100644 --- a/components/esp_modem/src/esp_modem_dce.cpp +++ b/components/esp_modem/src/esp_modem_dce.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -103,6 +103,49 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m return true; case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode break; + case modem_mode::AUTODETECT: { + auto guessed = guess_unsafe(dte, true); + if (guessed == modem_mode::UNDEF) { + return false; + } + // prepare the undefined mode before to allow all possible transitions + if (!dte->set_mode(modem_mode::UNDEF)) { + return false; + } + mode = modem_mode::UNDEF; + ESP_LOGI("DCE mode", "Detected mode: %d", static_cast(guessed)); + if (guessed == modem_mode::DATA_MODE) { + return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_DATA_MODE); + } else if (guessed == esp_modem::modem_mode::COMMAND_MODE) { + return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_COMMAND_MODE); + } else if (guessed == esp_modem::modem_mode::CMUX_MODE) { + if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_MODE)) { + return false; + } + // now we guess the mode for each terminal + guessed = guess_unsafe(dte, false); + ESP_LOGI("DCE mode", "Detected mode on primary term: %d", static_cast(guessed)); + if (guessed == modem_mode::DATA_MODE) { + if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) { + return false; + } +// vTaskDelay(pdMS_TO_TICKS(1000)); + } else { + // swap terminals, and do the same for the secondary terminal + if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::CMUX_MANUAL_SWAP)) { + return false; + } + } + guessed = guess_unsafe(dte, false); + ESP_LOGI("DCE mode", "Detected mode on secondary term: %d", static_cast(guessed)); + if (guessed == modem_mode::DATA_MODE) { + if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) { + return false; + } + } + } + return true; + } case modem_mode::COMMAND_MODE: if (mode == modem_mode::COMMAND_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) { return false; @@ -122,6 +165,35 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m } mode = m; return true; + case modem_mode::RESUME_DATA_MODE: + if (!dte->set_mode(modem_mode::DATA_MODE)) { + return false; + } + netif.start(); + mode = modem_mode::DATA_MODE; + return true; + case modem_mode::RESUME_COMMAND_MODE: + if (!dte->set_mode(modem_mode::COMMAND_MODE)) { + return false; + } + mode = modem_mode::COMMAND_MODE; + return true; + case modem_mode::RESUME_CMUX_MANUAL_MODE: + if (!dte->set_mode(modem_mode::CMUX_MANUAL_MODE)) { + return false; + } + mode = modem_mode::CMUX_MANUAL_MODE; + return true; + case modem_mode::RESUME_CMUX_MANUAL_DATA: + if (!dte->set_mode(modem_mode::CMUX_MANUAL_SWAP)) { + return false; + } + netif.start(); +// if (!dte->set_mode(modem_mode::CMUX_MANUAL_SWAP)) { +// return false; +// } + mode = modem_mode::CMUX_MANUAL_MODE; + return true; case modem_mode::DATA_MODE: if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) { return false; @@ -191,4 +263,107 @@ modem_mode DCE_Mode::get() return mode; } +modem_mode DCE_Mode::guess(DTE *dte, bool with_cmux) +{ + Scoped lock(*dte); + return guess_unsafe(dte, with_cmux); +} + +namespace probe { + +namespace ppp { +const uint8_t lcp_echo_request[] = {0x7e, 0xff, 0x03, 0xc0, 0x21, 0x09, 0x01, 0x00, 0x08, 0x99, 0xd1, 0x35, 0xc1, 0x8e, 0x2c, 0x7e }; +const uint8_t lcp_echo_reply_head[] = {0x7e, 0xff, 0x7d, 0x23, 0xc0}; +const size_t mode = 1 << 0; +const int timeout = 200; +} + +namespace cmd { +const char at[] = "\r\nAT\r\n"; +size_t max_at_reply = 16; // account for some whitespaces and/or CMUX encapsulation +const char reply[] = { 'O', 'K' }; +const int mode = 1 << 1; +const int timeout = 500; +} + +namespace cmux { +const uint8_t sabm0_reqest[] = {0xf9, 0x03, 0x3f, 0x01, 0x1c, 0xf9}; +const uint8_t sabm0_reply[] = {0xf9, 0x03, 0x73, 0x01}; +const int mode = 1 << 0; +const int timeout = 200; +} +}; + +modem_mode DCE_Mode::guess_unsafe(DTE *dte, bool with_cmux) +{ + // placeholder for reply and its size, since it could come in pieces, and we have to cache + // this is passed to the lambda as references. + // must make sure the lambda is cleared before exiting this function (done by dte->on_read(nullptr)) + uint8_t reply[std::max(probe::cmd::max_at_reply, std::max(sizeof(probe::ppp::lcp_echo_request), sizeof(probe::cmux::sabm0_reply)))]; + size_t reply_pos = 0; + auto signal = std::make_shared(); + std::weak_ptr weak_signal = signal; + dte->on_read([weak_signal, with_cmux, &reply, &reply_pos](uint8_t *data, size_t len) { + // storing the response in the `reply` array and de-fragmenting + if (reply_pos >= sizeof(reply)) { + return command_result::TIMEOUT; + } + auto reply_size = std::min((size_t)sizeof(reply) - reply_pos, len); + ::memcpy(reply + reply_pos, data, reply_size); + reply_pos += reply_size; + ESP_LOG_BUFFER_HEXDUMP("esp-modem: guess mode data:", reply, reply_pos, ESP_LOG_ERROR); + + // Check whether the response resembles the "golden" reply (for these 3 protocols) + if (reply_pos >= sizeof(probe::ppp::lcp_echo_reply_head)) { + // check for initial 2 bytes + uint8_t *ptr = static_cast(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head, 2)); + // and check the other two bytes for protocol ID: LCP + if (ptr && ptr[3] == probe::ppp::lcp_echo_reply_head[3] && ptr[4] == probe::ppp::lcp_echo_reply_head[4]) { + if (auto signal = weak_signal.lock()) { + signal->set(probe::ppp::mode); + } + } + } + if (reply_pos >= 4 && memmem(reply, reply_pos, probe::cmd::reply, sizeof(probe::cmd::reply))) { + if (reply[0] != 0xf9) { // double check that the reply is not wrapped in CMUX headers + if (auto signal = weak_signal.lock()) { + signal->set(probe::cmd::mode); + } + } + } + if (with_cmux && reply_pos >= sizeof(probe::cmux::sabm0_reply)) { + // checking the initial 3 bytes + uint8_t *ptr = static_cast(memmem(reply, reply_pos, probe::cmux::sabm0_reply, 3)); + // and checking that DLCI is 0 (control frame) + if (ptr && (ptr[3] >> 2) == 0) { + if (auto signal = weak_signal.lock()) { + signal->set(probe::cmux::mode); + } + } + } + return command_result::TIMEOUT; + }); + auto guessed = modem_mode::UNDEF; + // Check the PPP mode fist by sending LCP echo request + dte->send((uint8_t *)probe::ppp::lcp_echo_request, sizeof(probe::ppp::lcp_echo_request), 0); + if (signal->wait(probe::ppp::mode, probe::ppp::timeout)) { + guessed = modem_mode::DATA_MODE; + } else { // LCP echo timeout + // now check for AT mode + reply_pos = 0; + dte->send((uint8_t *)probe::cmd::at, sizeof(probe::cmd::at), 0); + if (signal->wait(probe::cmd::mode, probe::cmd::timeout)) { + guessed = modem_mode::COMMAND_MODE; + } else if (with_cmux) { // no AT reply, check for CMUX mode (if requested) + reply_pos = 0; + dte->send((uint8_t *) probe::cmux::sabm0_reqest, sizeof(probe::cmux::sabm0_reqest), 0); + if (signal->wait(probe::cmux::mode, probe::cmux::timeout)) { + guessed = modem_mode::CMUX_MODE; + } + } + } + dte->on_read(nullptr); + return guessed; +} + } // esp_modem diff --git a/components/esp_modem/src/esp_modem_dte.cpp b/components/esp_modem/src/esp_modem_dte.cpp index 5517ac6067a..dd5004e5ba8 100644 --- a/components/esp_modem/src/esp_modem_dte.cpp +++ b/components/esp_modem/src/esp_modem_dte.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -223,13 +223,13 @@ bool DTE::set_mode(modem_mode m) } } // transitions (COMMAND|DUAL|CMUX|UNDEF) -> DATA - if (m == modem_mode::DATA_MODE) { + if (m == modem_mode::DATA_MODE || m == modem_mode::RESUME_DATA_MODE) { if (mode == modem_mode::CMUX_MODE || mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::DUAL_MODE) { // mode stays the same, but need to swap terminals (as command has been switched) secondary_term.swap(primary_term); set_command_callbacks(); } else { - mode = m; + mode = modem_mode::DATA_MODE; } return true; } @@ -316,6 +316,12 @@ int DTE::write(uint8_t *data, size_t len) return secondary_term->write(data, len); } +int DTE::send(uint8_t *data, size_t len, int term_id) +{ + Terminal *term = term_id == 0 ? primary_term.get() : secondary_term.get(); + return term->write(data, len); +} + int DTE::write(DTE_Command command) { return primary_term->write(command.data, command.len); diff --git a/components/esp_modem/src/esp_modem_netif.cpp b/components/esp_modem/src/esp_modem_netif.cpp index d808db07486..fbb75c37c5a 100644 --- a/components/esp_modem/src/esp_modem_netif.cpp +++ b/components/esp_modem/src/esp_modem_netif.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -87,8 +87,10 @@ void Netif::start() receive(data, len); return true; }); - signal.set(PPP_STARTED); - esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr); + if (!signal.is_any(PPP_STARTED)) { + signal.set(PPP_STARTED); + esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr); + } } void Netif::stop()