Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[modem]: Add support for guessing mode #612

Merged
merged 2 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
Expand Down Expand Up @@ -89,7 +89,7 @@ void wakeup_modem(void)
vTaskDelay(pdMS_TO_TICKS(2000));
}

#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
command_result handle_urc(uint8_t *data, size_t len)
{
ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO);
Expand Down Expand Up @@ -238,7 +238,9 @@ extern "C" void app_main(void)
if (c->get_count_of(&SetModeArgs::mode)) {
auto mode = c->get_string_of(&SetModeArgs::mode);
modem_mode dev_mode;
if (mode == "UNDEF") {
if (mode == "AUTO") {
dev_mode = esp_modem::modem_mode::AUTODETECT;
} else if (mode == "UNDEF") {
dev_mode = esp_modem::modem_mode::UNDEF;
} else if (mode == "CMUX1") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_MODE;
Expand Down Expand Up @@ -370,15 +372,15 @@ extern "C" void app_main(void)
ESP_LOGI(TAG, "Resetting the module...");
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
});
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) {
static int cnt = 0;
if (++cnt % 2) {
ESP_LOGI(TAG, "Adding URC handler");
dce->set_on_read(handle_urc);
dce->set_urc(handle_urc);
} else {
ESP_LOGI(TAG, "URC removed");
dce->set_on_read(nullptr);
dce->set_urc(nullptr);
}
return 0;
});
Expand Down
7 changes: 7 additions & 0 deletions components/esp_modem/include/cxx_include/esp_modem_dce.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

};
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions components/esp_modem/include/cxx_include/esp_modem_dte.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ class DTE : public CommandableIf {

int write(DTE_Command command);

/**
* @brief send data to the selected terminal, by default (without term_id argument)
* this API works the same as write: sends data to the secondary terminal, which is
* typically used as data terminal (for networking).
*
* @param data Data pointer to write
* @param len Data len to write
* @param term_id Terminal id: Primary if id==0, Secondary if id==1
* @return number of bytes written
*/
int send(uint8_t *data, size_t len, int term_id = 1);

/**
* @brief Reading from the underlying terminal
* @param d Returning the data pointer of the received payload
Expand Down
13 changes: 12 additions & 1 deletion components/esp_modem/include/cxx_include/esp_modem_types.hpp
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -37,6 +37,17 @@ 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, /*!< This is used when the device is already in DATA mode and we need the modem lib to
* enter the mode without switching. On success, we would end up in DATA-mode, UNDEF otherwise */
RESUME_COMMAND_MODE, /*!< This is used when the device is already in COMMAND mode and we want to resume it
* On success, we would end up in DATA-mode, UNDEF otherwise */
RESUME_CMUX_MANUAL_MODE, /*!< This is used when the device is already in CMUX mode and we need the modem lib to
* enter it without switching. On success, we would end up in CMUX_MANUAL-mode, UNDEF otherwise */
RESUME_CMUX_MANUAL_DATA, /*!< This is used when the device is already in CMUX-DATA mode and we need the modem lib to
* enter it without switching. On success, we would end up in CMUX_MANUAL-DATA mode, UNDEF otherwise */
AUTODETECT, /*!< Auto-detection command: It tries to send a few packets in order to recognize which mode the
* the device currently is and update the modem library mode. On success the modem is updated,
* otherwise it's set to UNDEF */
};

/**
Expand Down
183 changes: 182 additions & 1 deletion components/esp_modem/src/esp_modem_dce.cpp
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -103,6 +103,51 @@ 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_LOGD("DCE mode", "Detected mode: %d", static_cast<int>(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_LOGD("DCE mode", "Detected mode on primary term: %d", static_cast<int>(guessed));
// now we need to access the second terminal, so we could simply send a SWAP command
// (switching to data mode does the swapping internally, so we only swap if we're in CMD mode)
if (guessed == modem_mode::DATA_MODE) {
// switch to DATA on the primary terminal and swap terminals
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) {
return false;
}
} else {
// swap terminals
if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::CMUX_MANUAL_SWAP)) {
return false;
}
}
guessed = guess_unsafe(dte, false);
ESP_LOGD("DCE mode", "Detected mode on secondary term: %d", static_cast<int>(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;
Expand All @@ -122,6 +167,32 @@ 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();
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;
Expand Down Expand Up @@ -191,4 +262,114 @@ modem_mode DCE_Mode::get()
return mode;
}

modem_mode DCE_Mode::guess(DTE *dte, bool with_cmux)
{
Scoped<DTE> lock(*dte);
return guess_unsafe(dte, with_cmux);
}

/**
* This namespace contains probe packets and expected replies on 3 different protocols,
* the modem device could use (as well as timeouts and mode ids for synchronisation)
*/
namespace probe {

namespace ppp {
// Test that we're in the PPP mode by sending an LCP protocol echo request and expecting LCP echo reply
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 };
constexpr std::array<uint8_t, 5> lcp_echo_reply_head = {0x7e, 0xff, 0x7d, 0x23, 0xc0};
const size_t mode = 1 << 0;
const int timeout = 200;
}

namespace cmd {
// For command mode, we just send a simple AT command
const char at[] = "\r\nAT\r\n";
const 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 {
// For CMUX mode, we send an SABM on control terminal (0)
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 captured by the lambda by reference.
// 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)))];
Fixed Show fixed Hide fixed
size_t reply_pos = 0;
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> 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_DEBUG);

// 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
auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head.data(), 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
auto *ptr = static_cast<uint8_t *>(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.data(), 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
10 changes: 8 additions & 2 deletions components/esp_modem/src/esp_modem_dte.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 5 additions & 3 deletions components/esp_modem/src/esp_modem_netif.cpp
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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()
Expand Down
Loading