Merge pull request #42 from david-cermak/feature/modem_cmux_ext

esp_modem: CMUX mode improvements
This commit is contained in:
david-cermak
2022-06-21 18:08:34 +02:00
committed by GitHub
16 changed files with 360 additions and 90 deletions

View File

@ -10,6 +10,7 @@
#pragma once
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <esp_console.h>

View File

@ -87,10 +87,10 @@ extern "C" void app_main(void)
}
#endif
if (dce->set_mode(esp_modem::modem_mode::CMUX_MODE) && dce->set_mode(esp_modem::modem_mode::DATA_MODE)) {
if (dce->set_mode(esp_modem::modem_mode::CMUX_MODE)) {
std::cout << "Modem has correctly entered multiplexed command/data mode" << std::endl;
} else {
ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
return;
}

View File

@ -0,0 +1,50 @@
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
namespace esp_modem {
/**
* Common unique buffer, which is transferable between DTE and CMUX
*
*/
struct unique_buffer {
explicit unique_buffer(size_t size);
unique_buffer (unique_buffer const&) = delete;
unique_buffer& operator=(unique_buffer const&) = delete;
unique_buffer(unique_buffer&& other) noexcept
{
data = std::move(other.data);
size = other.size;
consumed = 0;
}
unique_buffer& operator=(unique_buffer&& other) noexcept
{
if (&other == this) {
return *this;
}
data = std::move(other.data);
size = other.size;
consumed = 0;
return *this;
}
[[nodiscard]] uint8_t *get() const { return data.get(); }
std::unique_ptr<uint8_t[]> data;
size_t size{};
size_t consumed{};
};
}

View File

@ -15,6 +15,7 @@
#pragma once
#include "esp_modem_terminal.hpp"
#include "cxx_include/esp_modem_buffer.hpp"
namespace esp_modem {
@ -54,8 +55,8 @@ class CMuxInstance;
*/
class CMux {
public:
explicit CMux(std::unique_ptr<Terminal> t, std::unique_ptr<uint8_t[]> b, size_t buff_size):
term(std::move(t)), payload_start(nullptr), total_payload_size(0), buffer_size(buff_size), buffer(std::move(b)) {}
explicit CMux(std::shared_ptr<Terminal> t, unique_buffer&& b):
term(std::move(t)), payload_start(nullptr), total_payload_size(0), buffer(std::move(b)) {}
~CMux() = default;
/**
@ -64,6 +65,19 @@ public:
*/
[[nodiscard]] bool init();
/**
* @brief Closes and deinits CMux protocol
* @return true on success
*/
[[nodiscard]] bool deinit();
/**
* @brief Ejects the attached terminal and buffer,
* so they could be used as traditional command/data DTE's
* @return pair of the original terminal and buffer
*/
std::pair<std::shared_ptr<Terminal>, unique_buffer> detach();
/**
* @brief Sets read callback for the appropriate terminal
* @param inst Index of the terminal
@ -84,7 +98,8 @@ private:
static uint8_t fcs_crc(const uint8_t frame[6]); /*!< Utility to calculate FCS CRC */
void data_available(uint8_t *data, size_t len); /*!< Called when valid data available */
void send_sabm(size_t i); /*!< Sending initial SABM */
bool on_cmux(uint8_t *data, size_t len); /*!< Called from terminal layer when raw CMUX protocol data available */
void send_disconnect(size_t i); /*!< Sending closing request for each virtual or control terminal */
bool on_cmux_data(uint8_t *data, size_t len); /*!< Called from terminal layer when raw CMUX protocol data available */
struct CMuxFrame; /*!< Forward declare the Frame struct, used in protocol decoders */
/**
@ -100,7 +115,7 @@ private:
bool on_footer(CMuxFrame &frame);
std::function<bool(uint8_t *data, size_t len)> read_cb[MAX_TERMINALS_NUM]; /*!< Function pointers to read callbacks */
std::unique_ptr<Terminal> term; /*!< The original terminal */
std::shared_ptr<Terminal> term; /*!< The original terminal */
cmux_state state; /*!< CMux protocol state */
/**
@ -117,10 +132,9 @@ private:
int sabm_ack;
/**
* Processing buffer size and pointer
* Processing unique buffer (reused and transferred from it's parent DTE)
*/
size_t buffer_size;
std::unique_ptr<uint8_t[]> buffer;
unique_buffer buffer;
Lock lock;
};

View File

@ -40,6 +40,7 @@ public:
modem_mode get();
private:
bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
modem_mode mode;
};

View File

@ -79,8 +79,17 @@ public:
}
return true;
} else if (mode == modem_mode::COMMAND_MODE) {
Task::Delay(1000); // Mandatory 1s pause
return set_command_mode() == command_result::OK;
Task::Delay(1000); // Mandatory 1s pause before
int retry = 0;
while (retry++ < 3) {
if (set_command_mode() == command_result::OK)
return true;
Task::Delay(1000); // Mandatory 1s pause after escape
if (sync() == command_result::OK)
return true;
Task::Delay(1000); // Mandatory 1s pause before escape
}
return false;
} else if (mode == modem_mode::CMUX_MODE) {
return set_cmux() == command_result::OK;
}

View File

@ -15,18 +15,20 @@
#pragma once
#include <memory>
#include <utility>
#include <cstddef>
#include <cstdint>
#include <utility>
#include "cxx_include/esp_modem_primitives.hpp"
#include "cxx_include/esp_modem_terminal.hpp"
#include "cxx_include/esp_modem_cmux.hpp"
#include "cxx_include/esp_modem_types.hpp"
#include "cxx_include/esp_modem_buffer.hpp"
struct esp_modem_dte_config;
namespace esp_modem {
class CMux;
/**
* @defgroup ESP_MODEM_DTE
* @brief Definition of DTE and related classes
@ -94,21 +96,27 @@ public:
*/
command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms, char separator) override;
protected:
/**
* @brief Allows for locking the DTE
*/
void lock() { internal_lock.lock(); }
void unlock() { internal_lock.unlock(); }
friend class Scoped<DTE>; /*!< Declaring "Scoped<DTE> lock(dte)" locks this instance */
private:
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
[[nodiscard]] bool setup_cmux(); /*!< Internal setup of CMUX mode */
[[nodiscard]] bool setup_cmux(); /*!< Internal setup of CMUX mode */
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode */
Lock lock{}; /*!< Locks DTE operations */
size_t buffer_size; /*!< Size of available DTE buffer */
size_t consumed; /*!< Indication of already processed portion in DTE buffer */
std::unique_ptr<uint8_t[]> buffer; /*!< DTE buffer */
std::unique_ptr<Terminal> term; /*!< Primary terminal for this DTE */
Terminal *command_term; /*!< Reference to the terminal used for sending commands */
std::unique_ptr<Terminal> other_term; /*!< Secondary terminal for this DTE */
modem_mode mode; /*!< DTE operation mode */
Lock internal_lock{}; /*!< Locks DTE operations */
unique_buffer buffer; /*!< DTE buffer */
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
std::shared_ptr<Terminal> command_term; /*!< Reference to the terminal used for sending commands */
std::shared_ptr<Terminal> data_term; /*!< Secondary terminal for this DTE */
modem_mode mode; /*!< DTE operation mode */
SignalGroup signal; /*!< Event group used to signal request-response operations */
std::function<bool(uint8_t *data, size_t len)> on_data; /*!< on data callback for current terminal */
std::function<bool(uint8_t *data, size_t len)> on_data; /*!< on data callback for current terminal */
};
/**

View File

@ -46,7 +46,7 @@ private:
using TaskT = TaskHandle_t;
using SignalT = EventGroupHandle_t;
#else
using Lock = std::mutex;
using Lock = std::recursive_mutex;
struct SignalGroupInternal;
using SignalT = std::unique_ptr<SignalGroupInternal>;
using TaskT = std::thread;

View File

@ -39,6 +39,7 @@ typedef enum esp_modem_dce_mode
{
ESP_MODEM_MODE_COMMAND, /**< Default mode after modem startup, used for sending AT commands */
ESP_MODEM_MODE_DATA, /**< Used for switching to PPP mode for the modem to connect to a network */
ESP_MODEM_MODE_CMUX, /**< Multiplexed terminal mode */
} esp_modem_dce_mode_t;
/**

View File

@ -125,6 +125,13 @@ extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce
dce_wrap->dce->set_data();
} else if (mode == ESP_MODEM_MODE_COMMAND) {
dce_wrap->dce->exit_data();
} else if (mode == ESP_MODEM_MODE_CMUX) {
if (dce_wrap->dce->set_mode(modem_mode::CMUX_MODE) &&
// automatically switch to data mode for the primary terminal
dce_wrap->dce->set_mode(modem_mode::DATA_MODE)) {
return ESP_OK;
}
return ESP_FAIL;
} else {
return ESP_ERR_NOT_SUPPORTED;
}

View File

@ -78,6 +78,21 @@ uint8_t CMux::fcs_crc(const uint8_t frame[6])
return crc;
}
void CMux::send_disconnect(size_t i)
{
if (i == 0) { // control terminal
uint8_t frame[] = {
SOF_MARKER, 0x3, 0xFF, 0x5, 0xC3, 0x1, 0xE7, SOF_MARKER };
term->write(frame, 8);
} else { // separate virtual terminal
uint8_t frame[] = {
SOF_MARKER, 0x3, FT_DISC | PF, 0x1, 0, SOF_MARKER };
frame[1] |= i << 2;
frame[4] = 0xFF - fcs_crc(frame);
term->write(frame, sizeof(frame));
}
}
void CMux::send_sabm(size_t i)
{
uint8_t frame[6];
@ -127,6 +142,9 @@ void CMux::data_available(uint8_t *data, size_t len)
read_cb[virtual_term](payload_start, total_payload_size);
#endif
}
} else if (type == 0xFF && dlci == 0) { // notify the internal DISC command
Scoped<Lock> l(lock);
sabm_ack = dlci;
}
}
@ -254,11 +272,11 @@ bool CMux::on_footer(CMuxFrame &frame)
return true;
}
bool CMux::on_cmux(uint8_t *data, size_t actual_len)
bool CMux::on_cmux_data(uint8_t *data, size_t actual_len)
{
if (!data) {
#ifdef DEFRAGMENT_CMUX_PAYLOAD
auto data_to_read = buffer_size - 128; // keep 128 (max CMUX payload) backup buffer)
auto data_to_read = buffer.size - 128; // keep 128 (max CMUX payload) backup buffer)
if (payload_start) {
data = payload_start + total_payload_size;
data_to_read = payload_len + 2;
@ -305,12 +323,48 @@ bool CMux::on_cmux(uint8_t *data, size_t actual_len)
return true;
}
bool CMux::deinit()
{
int timeout = 0;
sabm_ack = -1;
// First disconnect all (2) virtual terminals
for (size_t i = 1; i < 3; i++) {
send_disconnect(i);
while (true) {
usleep(10'000);
Scoped<Lock> l(lock);
if (sabm_ack == i) {
sabm_ack = -1;
break;
}
if (timeout++ > 100) {
return false;
}
}
}
sabm_ack = -1;
// Then disconnect the control terminal
send_disconnect(0);
while (true) {
usleep(10'000);
Scoped<Lock> l(lock);
if (sabm_ack == 0) {
break;
}
if (timeout++ > 100) {
return false;
}
}
term->set_read_cb(nullptr);
return true;
}
bool CMux::init()
{
frame_header_offset = 0;
state = cmux_state::INIT;
term->set_read_cb([this](uint8_t *data, size_t len) {
this->on_cmux(data, len);
this->on_cmux_data(data, len);
return false;
});
@ -370,3 +424,8 @@ void CMux::set_read_cb(int inst, std::function<bool(uint8_t *, size_t)> f)
read_cb[inst] = std::move(f);
}
}
std::pair<std::shared_ptr<Terminal>, unique_buffer> CMux::detach()
{
return std::make_pair(std::move(term), std::move(buffer));
}

View File

@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <list>
#include <unistd.h>
#include <cstring>
#include "cxx_include/esp_modem_dte.hpp"
#include "cxx_include/esp_modem_dce.hpp"
@ -20,33 +22,77 @@
namespace esp_modem {
/**
* Set mode while the entire DTE is locked
*/
bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
{
Scoped<DTE> lock(*dte);
return set_unsafe(dte, device, netif, m);
}
/**
* state machine:
*
* COMMAND_MODE <----> DATA_MODE
* COMMAND_MODE <----> CMUX_MODE
*
* UNDEF <----> any
*/
bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
{
switch (m) {
case modem_mode::UNDEF:
break;
case modem_mode::COMMAND_MODE:
if (mode == modem_mode::COMMAND_MODE) {
return false;
{
if (mode == modem_mode::COMMAND_MODE) {
return false;
}
if (mode == modem_mode::CMUX_MODE) {
netif.stop();
netif.wait_until_ppp_exits();
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
return false;
}
mode = m;
return true;
}
netif.stop();
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> weak_signal = signal;
dte->set_read_cb([weak_signal](uint8_t *data, size_t len) -> bool {
if (memchr(data, '\n', len)) {
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_DEBUG);
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
std::string_view response((char *) data, len);
for (auto &it : pass)
if (response.find(it) != std::string::npos) {
if (auto signal = weak_signal.lock())
signal->set(1);
return true;
}
}
return false;
});
netif.wait_until_ppp_exits();
if (!signal->wait(1, 2000)) {
if (!device->set_mode(modem_mode::COMMAND_MODE)) {
mode = modem_mode::UNDEF;
return false;
}
}
dte->set_read_cb(nullptr);
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
mode = modem_mode::UNDEF;
return false;
}
mode = m;
return true;
}
netif.stop();
if (!device->set_mode(modem_mode::COMMAND_MODE)) {
return false;
}
dte->set_read_cb([&](uint8_t *data, size_t len) -> bool {
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_INFO);
return false;
});
netif.wait_until_ppp_exits();
dte->set_read_cb(nullptr);
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
return false;
}
mode = m;
return true;
break;
case modem_mode::DATA_MODE:
if (mode == modem_mode::DATA_MODE) {
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE) {
return false;
}
if (!device->setup_data_mode()) {
@ -71,7 +117,17 @@ bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
if (!dte->set_mode(modem_mode::CMUX_MODE)) {
return false;
}
mode = modem_mode::COMMAND_MODE;
mode = modem_mode::CMUX_MODE;
if (!device->setup_data_mode()) {
return false;
}
if (!device->set_mode(modem_mode::DATA_MODE)) {
return false;
}
if (!dte->set_mode(modem_mode::DATA_MODE)) {
return false;
}
netif.start();
return true;
}
return false;

View File

@ -15,6 +15,7 @@
#include <cstring>
#include "esp_log.h"
#include "cxx_include/esp_modem_dte.hpp"
#include "cxx_include/esp_modem_cmux.hpp"
#include "esp_modem_config.h"
using namespace esp_modem;
@ -22,36 +23,34 @@ using namespace esp_modem;
static const size_t dte_default_buffer_size = 1000;
DTE::DTE(const esp_modem_dte_config *config, std::unique_ptr<Terminal> terminal):
buffer_size(config->dte_buffer_size), consumed(0),
buffer(std::make_unique<uint8_t[]>(buffer_size)),
term(std::move(terminal)), command_term(term.get()), other_term(nullptr),
buffer(config->dte_buffer_size),
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
mode(modem_mode::UNDEF) {}
DTE::DTE(std::unique_ptr<Terminal> terminal):
buffer_size(dte_default_buffer_size), consumed(0),
buffer(std::make_unique<uint8_t[]>(buffer_size)),
term(std::move(terminal)), command_term(term.get()), other_term(nullptr),
buffer(dte_default_buffer_size),
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
mode(modem_mode::UNDEF) {}
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
{
Scoped<Lock> l(lock);
Scoped<Lock> l(internal_lock);
command_result res = command_result::TIMEOUT;
command_term->set_read_cb([&](uint8_t *data, size_t len) {
if (!data) {
data = buffer.get();
len = command_term->read(data + consumed, buffer_size - consumed);
len = command_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
} else {
consumed = 0; // if the underlying terminal contains data, we cannot fragment
buffer.consumed = 0; // if the underlying terminal contains data, we cannot fragment
}
if (memchr(data + consumed, separator, len)) {
res = got_line(data, consumed + len);
if (memchr(data + buffer.consumed, separator, len)) {
res = got_line(data, buffer.consumed + len);
if (res == command_result::OK || res == command_result::FAIL) {
signal.set(GOT_LINE);
return true;
}
}
consumed += len;
buffer.consumed += len;
return false;
});
command_term->write((uint8_t *)command.c_str(), command.length());
@ -59,7 +58,7 @@ command_result DTE::command(const std::string &command, got_line_cb got_line, ui
if (got_lf && res == command_result::TIMEOUT) {
throw_if_esp_fail(ESP_ERR_INVALID_STATE);
}
consumed = 0;
buffer.consumed = 0;
command_term->set_read_cb(nullptr);
return res;
}
@ -69,39 +68,74 @@ command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32
return command(cmd, got_line, time_ms, '\n');
}
bool DTE::setup_cmux()
bool DTE::exit_cmux()
{
auto original_term = std::move(term);
if (original_term == nullptr) {
if (!cmux_term->deinit()) {
return false;
}
auto cmux_term = std::make_shared<CMux>(std::move(original_term), std::move(buffer), buffer_size);
auto ejected = cmux_term->detach();
// return the ejected terminal and buffer back to this DTE
command_term = std::move(ejected.first);
buffer = std::move(ejected.second);
data_term = command_term;
return true;
}
bool DTE::setup_cmux()
{
cmux_term = std::make_shared<CMux>(command_term, std::move(buffer));
if (cmux_term == nullptr) {
return false;
}
buffer_size = 0;
if (!cmux_term->init()) {
return false;
}
term = std::make_unique<CMuxInstance>(cmux_term, 0);
if (term == nullptr) {
command_term = std::make_unique<CMuxInstance>(cmux_term, 0);
if (command_term == nullptr) {
return false;
}
command_term = term.get(); // use command terminal as previously
other_term = std::make_unique<CMuxInstance>(cmux_term, 1);
data_term = std::make_unique<CMuxInstance>(cmux_term, 1);
return true;
}
bool DTE::set_mode(modem_mode m)
{
mode = m;
if (m == modem_mode::DATA_MODE) {
term->set_read_cb(on_data);
if (other_term) { // if we have the other terminal, let's use it for commands
command_term = other_term.get();
// transitions (COMMAND|UNDEF) -> CMUX
if (m == modem_mode::CMUX_MODE) {
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
if (setup_cmux()) {
mode = m;
return true;
}
mode = modem_mode::UNDEF;
return false;
}
}
// transitions (COMMAND|CMUX|UNDEF) -> DATA
if (m == modem_mode::DATA_MODE) {
if (mode == modem_mode::CMUX_MODE) {
// mode stays the same, but need to swap terminals (as command has been switch)
data_term.swap(command_term);
} else {
mode = m;
}
// prepare the data terminal's callback to the configured std::function (used by netif)
data_term->set_read_cb(on_data);
return true;
}
// transitions (DATA|CMUX|UNDEF) -> COMMAND
if (m == modem_mode::COMMAND_MODE) {
if (mode == modem_mode::CMUX_MODE) {
if (exit_cmux()) {
mode = m;
return true;
}
mode = modem_mode::UNDEF;
return false;
} else {
mode = m;
return true;
}
} else if (m == modem_mode::CMUX_MODE) {
return setup_cmux();
}
return true;
}
@ -109,10 +143,10 @@ bool DTE::set_mode(modem_mode m)
void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
{
on_data = std::move(f);
term->set_read_cb([this](uint8_t *data, size_t len) {
data_term->set_read_cb([this](uint8_t *data, size_t len) {
if (!data) { // if no data available from terminal callback -> need to explicitly read some
data = buffer.get();
len = term->read(buffer.get(), buffer_size);
len = data_term->read(buffer.get(), buffer.size);
}
if (on_data) {
return on_data(data, len);
@ -123,14 +157,20 @@ void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
int DTE::read(uint8_t **d, size_t len)
{
auto data_to_read = std::min(len, buffer_size);
auto data_to_read = std::min(len, buffer.size);
auto data = buffer.get();
auto actual_len = term->read(data, data_to_read);
auto actual_len = data_term->read(data, data_to_read);
*d = data;
return actual_len;
}
int DTE::write(uint8_t *data, size_t len)
{
return term->write(data, len);
}
return data_term->write(data, len);
}
/**
* Implemented here to keep all headers C++11 compliant
*/
unique_buffer::unique_buffer(size_t size):
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}

View File

@ -54,13 +54,16 @@ void Netif::start()
signal.set(PPP_STARTED);
}
void Netif::stop() {}
void Netif::stop()
{
ppp_dte->set_read_cb(nullptr);
signal.clear(PPP_STARTED);
}
Netif::~Netif() = default;
void Netif::wait_until_ppp_exits()
{
}
} // namespace esp_modem

View File

@ -25,7 +25,7 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
if (command == "+++") {
response = "NO CARRIER\r\n";
} else if (command == "ATE1\r" || command == "ATE0\r") {
response = "OK\r\n";
response = "OK\r\n ";
} else if (command == "ATO\r") {
response = "ERROR\r\n";
} else if (command.find("ATD") != std::string::npos) {
@ -43,7 +43,16 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
} else if (command.find("AT+CPIN?\r") != std::string::npos) {
response = pin_ok ? "+CPIN: READY\r\nOK\r\n" : "+CPIN: SIM PIN\r\nOK\r\n";
} else if (command.find("AT") != std::string::npos) {
response = "OK\r\n";
if (command.length() > 4) {
response = command;
response[0] = 'O';
response[1] = 'K';
response[2] = '\r';
response[3] = '\n';
} else {
response = "OK\r\n";
}
}
if (!response.empty()) {
data_len = response.length();
@ -55,7 +64,7 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
}
if (len > 2 && data[0] == 0xf9) { // Simple CMUX responder
// turn the request into a reply -> implements CMUX loopback
if (data[2] == 0x3f) { // SABM command
if (data[2] == 0x3f || data[2] == 0x53) { // SABM command
data[2] = 0x73;
} else if (data[2] == 0xef) { // Generic request
data[2] = 0xff; // generic reply

View File

@ -77,6 +77,7 @@ TEST_CASE("DTE send/receive command", "[esp_modem]")
CHECK(ret == command_result::OK);
}
TEST_CASE("DCE commands", "[esp_modem]")
{
auto term = std::make_unique<LoopbackTerm>();
@ -96,7 +97,6 @@ TEST_CASE("DCE commands", "[esp_modem]")
}, 1000);
CHECK(ret == command_result::OK);
}
TEST_CASE("DCE AT commands", "[esp_modem]")
{
auto term = std::make_unique<LoopbackTerm>();
@ -128,9 +128,21 @@ TEST_CASE("DCE modes", "[esp_modem]")
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
CHECK(dce != nullptr);
// UNDER -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
// CMD -> CMD (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == false);
// CMD -> DATA (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == true);
// DATA -> CMUX (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MODE) == false);
// DATA back -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
// CMD -> 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);
}
@ -171,7 +183,7 @@ TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MODE) == true);
const auto test_command = "Test\n";
// 1 byte payload size
uint8_t test_payload[] = {0xf9, 0x05, 0xff, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x0a, 0xbb, 0xf9 };
uint8_t test_payload[] = {0xf9, 0x09, 0xff, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x0a, 0xbb, 0xf9 };
loopback->inject(&test_payload[0], sizeof(test_payload), 1);
auto ret = dce->command(test_command, [&](uint8_t *data, size_t len) {
std::string response((char *) data, len);
@ -181,7 +193,7 @@ TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
CHECK(ret == command_result::OK);
// 2 byte payload size
uint8_t long_payload[453] = { 0xf9, 0x05, 0xef, 0x7c, 0x03, 0x7e }; // header
uint8_t long_payload[453] = { 0xf9, 0x09, 0xef, 0x7c, 0x03, 0x7e }; // header
long_payload[5] = 0x7e; // payload to validate
long_payload[449] = 0x7e;
long_payload[450] = '\n';