mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-19 21:42:25 +02:00
Merge pull request #416 from david-cermak/fix/modem_undef_mode
fix(modem): Fixed mode transitions between any state and UNDEF
This commit is contained in:
@ -235,7 +235,9 @@ extern "C" void app_main(void)
|
|||||||
if (c->get_count_of(&SetModeArgs::mode)) {
|
if (c->get_count_of(&SetModeArgs::mode)) {
|
||||||
auto mode = c->get_string_of(&SetModeArgs::mode);
|
auto mode = c->get_string_of(&SetModeArgs::mode);
|
||||||
modem_mode dev_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;
|
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_MODE;
|
||||||
} else if (mode == "CMUX2") {
|
} else if (mode == "CMUX2") {
|
||||||
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_EXIT;
|
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_EXIT;
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -96,6 +96,11 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
|
|||||||
{
|
{
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case modem_mode::UNDEF:
|
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
|
case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
|
||||||
break;
|
break;
|
||||||
case modem_mode::COMMAND_MODE:
|
case modem_mode::COMMAND_MODE:
|
||||||
@ -151,7 +156,7 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
|
|||||||
mode = modem_mode::CMUX_MANUAL_MODE;
|
mode = modem_mode::CMUX_MANUAL_MODE;
|
||||||
return true;
|
return true;
|
||||||
case modem_mode::CMUX_MANUAL_EXIT:
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!dte->set_mode(m)) {
|
if (!dte->set_mode(m)) {
|
||||||
@ -160,7 +165,7 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
|
|||||||
mode = modem_mode::COMMAND_MODE;
|
mode = modem_mode::COMMAND_MODE;
|
||||||
return true;
|
return true;
|
||||||
case modem_mode::CMUX_MANUAL_SWAP:
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!dte->set_mode(m)) {
|
if (!dte->set_mode(m)) {
|
||||||
@ -168,12 +173,12 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case modem_mode::CMUX_MANUAL_DATA:
|
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 false;
|
||||||
}
|
}
|
||||||
return transitions::enter_data(*dte, *device, netif);
|
return transitions::enter_data(*dte, *device, netif);
|
||||||
case modem_mode::CMUX_MANUAL_COMMAND:
|
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 false;
|
||||||
}
|
}
|
||||||
return transitions::exit_data(*dte, *device, netif);
|
return transitions::exit_data(*dte, *device, netif);
|
||||||
|
@ -151,10 +151,14 @@ command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32
|
|||||||
|
|
||||||
bool DTE::exit_cmux()
|
bool DTE::exit_cmux()
|
||||||
{
|
{
|
||||||
|
if (!cmux_term) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!cmux_term->deinit()) {
|
if (!cmux_term->deinit()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
exit_cmux_internal();
|
exit_cmux_internal();
|
||||||
|
cmux_term.reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +178,10 @@ void DTE::exit_cmux_internal()
|
|||||||
|
|
||||||
bool DTE::setup_cmux()
|
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));
|
cmux_term = std::make_shared<CMux>(primary_term, std::move(buffer));
|
||||||
if (cmux_term == nullptr) {
|
if (cmux_term == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
@ -198,6 +206,11 @@ bool DTE::setup_cmux()
|
|||||||
|
|
||||||
bool DTE::set_mode(modem_mode m)
|
bool DTE::set_mode(modem_mode m)
|
||||||
{
|
{
|
||||||
|
// transitions (any) -> UNDEF
|
||||||
|
if (m == modem_mode::UNDEF) {
|
||||||
|
mode = m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// transitions (COMMAND|UNDEF) -> CMUX
|
// transitions (COMMAND|UNDEF) -> CMUX
|
||||||
if (m == modem_mode::CMUX_MODE) {
|
if (m == modem_mode::CMUX_MODE) {
|
||||||
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
|
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
|
||||||
@ -246,7 +259,7 @@ bool DTE::set_mode(modem_mode m)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// manual CMUX transitions: Exit CMUX
|
// 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()) {
|
if (exit_cmux()) {
|
||||||
mode = modem_mode::COMMAND_MODE;
|
mode = modem_mode::COMMAND_MODE;
|
||||||
return true;
|
return true;
|
||||||
@ -255,7 +268,7 @@ bool DTE::set_mode(modem_mode m)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// manual CMUX transitions: Swap terminals
|
// 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);
|
secondary_term.swap(primary_term);
|
||||||
set_command_callbacks();
|
set_command_callbacks();
|
||||||
return true;
|
return true;
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
*/
|
*/
|
||||||
@ -247,3 +247,88 @@ TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
|
|||||||
CHECK(ret == command_result::OK);
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -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/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.
|
- ``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
|
Extensibility
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
CMUX
|
CMUX
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
Implementation of virtual terminals is an experimental feature, which
|
Implements virtual terminals which allow users to also issue commands in the data mode;
|
||||||
allows users to also issue commands in the data mode, after creating
|
after creating two virtual terminals, designating one of them solely to data mode, and
|
||||||
multiple virtual terminals, designating some of them solely to data
|
another one solely to command mode.
|
||||||
mode, others solely to command mode.
|
|
||||||
|
|
||||||
DTE
|
DTE
|
||||||
~~~
|
~~~
|
||||||
|
Reference in New Issue
Block a user