Skip to content

Commit

Permalink
Merge pull request #416 from david-cermak/fix/modem_undef_mode
Browse files Browse the repository at this point in the history
fix(modem): Fixed mode transitions between any state and UNDEF
  • Loading branch information
david-cermak authored Nov 28, 2023
2 parents 3f49583 + 5396de4 commit afbca53
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,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 == "CMUX1") {
if (mode == "UNDEF") {
dev_mode = esp_modem::modem_mode::UNDEF;
} else if (mode == "CMUX1") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_MODE;
} else if (mode == "CMUX2") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_EXIT;
Expand Down
15 changes: 10 additions & 5 deletions 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-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -96,6 +96,11 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
{
switch (m) {
case modem_mode::UNDEF:
if (!dte->set_mode(m)) {
return false;
}
mode = m;
return true;
case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
break;
case modem_mode::COMMAND_MODE:
Expand Down Expand Up @@ -151,7 +156,7 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
mode = modem_mode::CMUX_MANUAL_MODE;
return true;
case modem_mode::CMUX_MANUAL_EXIT:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
if (!dte->set_mode(m)) {
Expand All @@ -160,20 +165,20 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
mode = modem_mode::COMMAND_MODE;
return true;
case modem_mode::CMUX_MANUAL_SWAP:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
if (!dte->set_mode(m)) {
return false;
}
return true;
case modem_mode::CMUX_MANUAL_DATA:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
return transitions::enter_data(*dte, *device, netif);
case modem_mode::CMUX_MANUAL_COMMAND:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
return transitions::exit_data(*dte, *device, netif);
Expand Down
17 changes: 15 additions & 2 deletions components/esp_modem/src/esp_modem_dte.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,14 @@ command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32

bool DTE::exit_cmux()
{
if (!cmux_term) {
return false;
}
if (!cmux_term->deinit()) {
return false;
}
exit_cmux_internal();
cmux_term.reset();
return true;
}

Expand All @@ -174,6 +178,10 @@ void DTE::exit_cmux_internal()

bool DTE::setup_cmux()
{
if (cmux_term) {
ESP_LOGE("esp_modem_dte", "Cannot setup_cmux(), cmux_term already exists");
return false;
}
cmux_term = std::make_shared<CMux>(primary_term, std::move(buffer));
if (cmux_term == nullptr) {
return false;
Expand All @@ -198,6 +206,11 @@ bool DTE::setup_cmux()

bool DTE::set_mode(modem_mode m)
{
// transitions (any) -> UNDEF
if (m == modem_mode::UNDEF) {
mode = m;
return true;
}
// transitions (COMMAND|UNDEF) -> CMUX
if (m == modem_mode::CMUX_MODE) {
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
Expand Down Expand Up @@ -246,7 +259,7 @@ bool DTE::set_mode(modem_mode m)
return false;
}
// manual CMUX transitions: Exit CMUX
if (m == modem_mode::CMUX_MANUAL_EXIT && mode == modem_mode::CMUX_MANUAL_MODE) {
if (m == modem_mode::CMUX_MANUAL_EXIT && (mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::UNDEF)) {
if (exit_cmux()) {
mode = modem_mode::COMMAND_MODE;
return true;
Expand All @@ -255,7 +268,7 @@ bool DTE::set_mode(modem_mode m)
return false;
}
// manual CMUX transitions: Swap terminals
if (m == modem_mode::CMUX_MANUAL_SWAP && mode == modem_mode::CMUX_MANUAL_MODE) {
if (m == modem_mode::CMUX_MANUAL_SWAP && (mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::UNDEF)) {
secondary_term.swap(primary_term);
set_command_callbacks();
return true;
Expand Down
87 changes: 86 additions & 1 deletion components/esp_modem/test/host_test/main/test_modem.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
Expand Down Expand Up @@ -247,3 +247,88 @@ TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
CHECK(ret == command_result::OK);
}
}

TEST_CASE("Command and Data mode transitions", "[esp_modem][transitions]")
{
auto term = std::make_unique<LoopbackTerm>();
auto loopback = term.get();
auto dte = std::make_shared<DTE>(std::move(term));
CHECK(term == nullptr);

esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
esp_netif_t netif{};
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
CHECK(dce != nullptr);

// UNDEF -> CMD (OK)
uint8_t resp[] = "DISCONNECTED\n";
loopback->inject(&resp[0], sizeof(resp), sizeof(resp), /* 10ms before injecting reply */100, 0);
loopback->write(nullptr, 0); /* this triggers sending the injected response */
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
loopback->inject(nullptr, 0, 0, 0, 0); /* reset injection, use synchronous replies now */
// CMD -> CMD (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == false);

// Forcing transition to CMD (via UNDEF)
// CMD -> UNDEF (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
// UNDEF -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);

// CMD -> DATA (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == true);
// DATA -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
}

TEST_CASE("CMUX mode transitions", "[esp_modem][transitions]")
{
auto term = std::make_unique<LoopbackTerm>();
auto dte = std::make_shared<DTE>(std::move(term));
CHECK(term == nullptr);

esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
esp_netif_t netif{};
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
CHECK(dce != nullptr);
// UNDEF -> CMUX (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MODE) == true);
// CMUX -> DATA (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == false);
// CMUX back -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
}

TEST_CASE("CMUX manual mode transitions", "[esp_modem][transitions]")
{
auto term = std::make_unique<LoopbackTerm>();
auto dte = std::make_shared<DTE>(std::move(term));
CHECK(term == nullptr);

esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
esp_netif_t netif{};
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
CHECK(dce != nullptr);

// Happy flow transitions of Manual CMUX transitions
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA) == true);
// Cannot test CMUX_MANUAL_DATA -> CMUX_MANUAL_COMMAND with our mocked terminal for now
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true);

// Check some out of order manual transitions, most of them are allowed,
// but some fail as modem layers report issues with specific steps
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == false); // cannot go directly to SWAP
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == true); // can go via UNDEF
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == false); // EXIT is allowed, but CMUX terms don't exist
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true); // Enter CMUX (via UNDEF)
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA) == true); // Go directly to DATA mode
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true); // Exit CMUX
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true); // Succeeds from any state

}
60 changes: 56 additions & 4 deletions docs/esp_modem/en/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,68 @@ Common use cases of the esp-modem are also listed as the examples:
- ``examples/modem_console`` is an example to exercise all possible module commands in a console application.
- ``examples/ap_to_pppos`` this example focuses on the network connectivity of the esp-modem and provides a WiFi AP that forwards packets (and uses NAT) to and from the PPPoS connection.

Working modes
~~~~~~~~~~~~~

Modem devices could work in multiple different modes, esp-modem library
uses these states to describe them:
- Standard modes:
- Command mode -- This mode is used for sending AT commands
- Data or PPP mode -- This mode is used for data communication (to create PPPoS tunnel between the device and the library)
- Multiplexing modes:
- CMUX mode -- This mode creates two virtual channels and uses one for sending AT commands and the other one for data communication.
- DUAL mode -- This mode uses two physical channels the same way as CMUX. This mode is supported only by certain devices, usually with USB interface.
- Manual CMUX modes -- These modes are designed for applications to have better control over CMUX mode transitions. It allows setting up the virtual channels,
switching between channels, transitioning between data and command modes for each channel separately, and exiting the CMUX.

Switching between common modes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The diagram below depicts allowed transitions between the most common modes

::

+---------+ +---------+
| COMMAND |<-->| DATA |
+---------+ +---------+
^
|
v
+-------+
| CMUX |
+-------+

Note that it is possible to switch from any mode to the "UNDEF" mode and vice-versa.

Switching between manual modes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The diagram below depicts allowed transitions between manual CMUX modes

::
+------------------------------------
| |
+----------+ +-------------+ +------------+ +----------+
| |<-->| MANUAL_DATA |<-->| MANUAL_CMD |<-->| COMMAND |
| CMUX | +-------------+ +------------+ | (CMUX |
| MANUAL | | | MANUAL |
| | +-------------+ | EXIT) |
| |<-->| MANUAL_SWAP |<-------------------->| |
+----------+ +-------------+ +----------+
| |
+-----------------------------------------------------+

Note that transitioning between "MANUAL_DATA" and "MANUAL_CMD" switches the secondary terminal (dedicated to PPP session) and could be used for recovering data communication if PPP session gets dropped.

Extensibility
-------------

CMUX
~~~~

Implementation of virtual terminals is an experimental feature, which
allows users to also issue commands in the data mode, after creating
multiple virtual terminals, designating some of them solely to data
mode, others solely to command mode.
Implements virtual terminals which allow users to also issue commands in the data mode;
after creating two virtual terminals, designating one of them solely to data mode, and
another one solely to command mode.

DTE
~~~
Expand Down

0 comments on commit afbca53

Please sign in to comment.