CMUX: Initial version of CMUX implementation

This commit is contained in:
David Cermak
2021-04-06 08:33:40 +02:00
parent eca6aaad33
commit 7a09175d5b
10 changed files with 184 additions and 165 deletions

View File

@ -61,7 +61,7 @@ is provided implementing these method using the uart driver.
## CMUX terminal
The below diagram depicts the idea of using CMUX terminal mode using the CMUXedTerminal class which is a terminal
The below diagram depicts the idea of using CMUX terminal mode using the CMux class which is a terminal
(it implements the basic read/write methods) interfacing arbitrary number of virtual terminals,
but at the same time it is also composed of another terminal, the real terminal, which is multiplexed.

View File

@ -20,6 +20,8 @@
#define BROKER_URL "mqtt://mqtt.eclipseprojects.io"
using namespace esp_modem;
static const char *TAG = "pppos_example";
static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
@ -114,7 +116,6 @@ static void on_ip_event(void *arg, esp_event_base_t event_base,
}
static void modem_test_app(esp_modem_dte_config_t *dte_config, esp_modem_dce_config_t *dce_config, esp_netif_config_t *ppp_config);
extern "C" void app_main(void)
{
@ -167,7 +168,7 @@ extern "C" void app_main(void)
std::string apn = "internet";
// auto device = create_generic_module(uart_dte, apn);
auto device = create_SIM7600_module(uart_dte, apn);
// auto device = create_SIM7600_module(uart_dte, apn);
// bool pin_ok = true;
// if (device->read_pin(pin_ok) == command_result::OK && !pin_ok) {
// throw_if_false(device->set_pin("1234") == command_result::OK, "Cannot set PIN!");
@ -187,37 +188,45 @@ extern "C" void app_main(void)
// std::cout << "|" << number << "|" << std::endl;
// std::cout << "----" << std::endl;
// auto my_dce = create_generic_module_dce(uart_dte, device, esp_netif);
auto my_dce = create_SIM7600_dce_from_module(uart_dte, device, esp_netif);
auto my_dce = create_SIM7600_dce(&dce_config, uart_dte, esp_netif);
my_dce->set_command_mode();
my_dce->get_module_name(number);
std::cout << "|" << number << "|" << std::endl;
bool pin_ok = true;
if (my_dce->read_pin(pin_ok) == command_result::OK && !pin_ok) {
throw_if_false(my_dce->set_pin("1234") == command_result::OK, "Cannot set PIN!");
}
vTaskDelay(pdMS_TO_TICKS(1000));
// return;
// my_dce->set_cmux();
// my_dce->set_cmux();
my_dce->set_mode(esp_modem::modem_mode::CMUX_MODE);
my_dce->get_imsi(number);
std::cout << "|" << number << "|" << std::endl;
// while (1) {
// vTaskDelay(pdMS_TO_TICKS(1000));
// uart_dte->write((uint8_t*)"AT+CPIN?\r", 9);
// my_dce->get_imsi(number);
// std::cout << "|" << number << "|" << std::endl;
//
// }
// uart_dte->send_cmux_command(2, "AT+CPIN?");
// return;
my_dce->get_module_name(number);
my_dce->set_data_mode();
// my_dce->get_module_name(number);
// my_dce->set_data_mode();
//
// my_dce->command("AT+CPIN?\r", [&](uint8_t *data, size_t len) {
// std::string response((char*)data, len);
// ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data);
// std::cout << response << std::endl;
// return command_result::OK;
// }, 1000);
my_dce->command("AT+CPIN?\r", [&](uint8_t *data, size_t len) {
std::string response((char*)data, len);
ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data);
std::cout << response << std::endl;
return command_result::OK;
}, 1000);
while (1) {
// while (1)
{
my_dce->set_data();
/* Config MQTT */
@ -230,14 +239,20 @@ extern "C" void app_main(void)
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdTRUE, portMAX_DELAY);
esp_mqtt_client_destroy(mqtt_client);
while (1) {
vTaskDelay(pdMS_TO_TICKS(2000));
my_dce->get_imsi(number);
std::cout << "|" << number << "|" << std::endl;
}
// vTaskDelay(pdMS_TO_TICKS(20000));
my_dce->exit_data();
uart_dte->command("AT+CPIN?\r", [&](uint8_t *data, size_t len) {
std::string response((char*)data, len);
// ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data);
std::cout << response << std::endl;
return command_result::OK;
}, 1000);
// my_dce->exit_data();
// uart_dte->command("AT+CPIN?\r", [&](uint8_t *data, size_t len) {
// std::string response((char*)data, len);
//// ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data);
// std::cout << response << std::endl;
// return command_result::OK;
// }, 1000);
}
// ddd->send_command("AT+COPS=?\r", [&](uint8_t *data, size_t len) {

View File

@ -15,7 +15,11 @@
#ifndef _ESP_MODEM_CMUX_HPP_
#define _ESP_MODEM_CMUX_HPP_
#include "esp_modem_terminal.hpp"
namespace esp_modem {
constexpr size_t max_terms = 2;
/**
* @defgroup ESP_MODEM_CMUX ESP_MODEM CMUX class
* @brief Definition of CMUX terminal
@ -43,19 +47,20 @@ enum class cmux_state {
*
* @note Implementation of CMUX protocol is experimental
*/
class CMUXedTerminal: public Terminal {
class CMuxInstance;
class CMux {
public:
explicit CMUXedTerminal(std::unique_ptr<Terminal> t, std::unique_ptr<uint8_t[]> b, size_t buff_size):
term(std::move(t)), buffer(std::move(b)) {}
~CMUXedTerminal() override = default;
void setup_cmux();
void set_data_cb(std::function<bool(size_t len)> f) override {}
int write(uint8_t *data, size_t len) override;
int read(uint8_t *data, size_t len) override { return term->read(data, len); }
void start() override;
void stop() override {}
explicit CMux(std::unique_ptr<Terminal> t, std::unique_ptr<uint8_t[]> b, size_t buff_size):
term(std::move(t)), buffer_size(buff_size), buffer(std::move(b)) {}
~CMux() = default;
void init();
void set_read_cb(int inst, std::function<bool(uint8_t *data, size_t len)> f);
int write(int i, uint8_t *data, size_t len);
private:
static bool process_cmux_recv(size_t len);
std::function<bool(uint8_t *data, size_t len)> read_cb[max_terms];
void data_available(uint8_t *data, size_t len);
void send_sabm(size_t i);
std::unique_ptr<Terminal> term;
cmux_state state;
@ -65,9 +70,25 @@ private:
uint8_t frame_header[6];
size_t frame_header_offset;
size_t buffer_size;
size_t consumed;
std::unique_ptr<uint8_t[]> buffer;
bool on_cmux(size_t len);
bool on_cmux(uint8_t *data, size_t len);
static uint8_t fcs_crc(const uint8_t frame[6]);
Lock lock;
int instance;
};
class CMuxInstance: public Terminal {
public:
explicit CMuxInstance(std::shared_ptr<CMux> parent, int i): cmux(std::move(parent)), instance(i) {}
int write(uint8_t *data, size_t len) override { return cmux->write(instance, data, len); }
void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f) override { return cmux->set_read_cb(instance, std::move(f)); }
int read(uint8_t *data, size_t len) override { return 0; }
void start() override { }
void stop() override { }
private:
std::shared_ptr<CMux> cmux;
int instance;
};
/**

View File

@ -42,8 +42,17 @@ public:
return actual_len;
}
void set_data_cb(std::function<bool(size_t len)> f) {
term->set_data_cb(std::move(f));
void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f)
{
on_data = std::move(f);
term->set_read_cb([this](uint8_t *data, size_t len) {
if (!data) {
auto data_to_read = std::min(len, buffer_size - consumed);
data = buffer.get();
len = term->read(data, data_to_read);
}
return on_data(data, len);
});
}
void start() { term->start(); }
@ -54,7 +63,10 @@ public:
term->start();
mode = m;
if (m == modem_mode::DATA_MODE) {
term->set_data_cb(on_data);
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();
}
} else if (m == modem_mode::CMUX_MODE) {
setup_cmux();
}
@ -62,23 +74,32 @@ public:
command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms) override;
void send_cmux_command(uint8_t i, const std::string &command);
private:
Lock lock;
void setup_cmux();
// command_result command(Terminal *t, const std::string &command, got_line_cb got_line, uint32_t time_ms);
static const size_t GOT_LINE = signal_group::bit0;
size_t buffer_size;
size_t consumed;
std::unique_ptr<uint8_t[]> buffer;
std::unique_ptr<Terminal> term;
Terminal *command_term;
std::unique_ptr<Terminal> other_term;
modem_mode mode;
signal_group signal;
std::function<bool(size_t len)> on_data;
std::function<bool(uint8_t *data, size_t len)> on_data;
};
//class DTE_inst: public DTE {
//public:
// DTE_inst(std::shared_ptr<DTE> parent) : DTE(t), dte(parent) {}
//private:
// std::shared_ptr<DTE> dte;
//};
} // namespace esp_modem
#endif // _ESP_MODEM_DTE_HPP_

View File

@ -36,9 +36,10 @@ class Terminal {
public:
virtual ~Terminal() = default;
virtual void set_data_cb(std::function<bool(size_t len)> f) { on_data = std::move(f); }
// virtual void set_data_cb(std::function<bool(size_t len)> f) { on_data = std::move(f); }
void set_error_cb(std::function<void(terminal_error)> f) { on_error = std::move(f); }
virtual void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f) { on_data = std::move(f); }
virtual int write(uint8_t *data, size_t len) = 0;
@ -51,7 +52,8 @@ public:
virtual size_t max_virtual_terms() { return 1; }
protected:
std::function<bool(size_t len)> on_data;
// std::function<bool(size_t len)> on_data;
std::function<bool(uint8_t *data, size_t len)> on_data;
std::function<void(terminal_error)> on_error;
};

View File

@ -14,16 +14,13 @@
#include <cstring>
#include <unistd.h>
#include <cxx_include/esp_modem_cmux.hpp>
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_log.h"
using namespace esp_modem;
/* CRC8 is the reflected CRC8/ROHC algorithm */
#define FCS_POLYNOMIAL 0xe0 /* reversed crc8 */
#define FCS_INIT_VALUE 0xFF
#define FCS_GOOD_VALUE 0xCF
#define EA 0x01 /* Extension bit */
#define CR 0x02 /* Command / Response */
#define PF 0x10 /* Poll / Final */
@ -54,86 +51,59 @@ using namespace esp_modem;
/* Flag sequence field between messages (start of frame) */
#define SOF_MARKER 0xF9
static uint8_t crc8(const uint8_t *src, size_t len, uint8_t polynomial, uint8_t initial_value,
bool reversed)
uint8_t CMux::fcs_crc(const uint8_t frame[6])
{
uint8_t crc = initial_value;
size_t i, j;
// #define FCS_GOOD_VALUE 0xCF
uint8_t crc = 0xFF; // FCS_INIT_VALUE
for (i = 0; i < len; i++) {
crc ^= src[i];
for (int i = 1; i < 4; i++) {
crc ^= frame[i];
for (j = 0; j < 8; j++) {
if (reversed) {
for (int j = 0; j < 8; j++) {
if (crc & 0x01) {
crc = (crc >> 1) ^ polynomial;
crc = (crc >> 1) ^ 0xe0; // FCS_POLYNOMIAL
} else {
crc >>= 1;
}
} else {
if (crc & 0x80) {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
}
return crc;
}
void CMUXedTerminal::start()
{
for (size_t i = 0; i < 3; i++)
{
send_sabm(i);
usleep(100'000);
}
}
void CMUXedTerminal::send_sabm(size_t dlci)
void CMux::send_sabm(size_t dlci)
{
uint8_t frame[6];
frame[0] = SOF_MARKER;
frame[1] = (dlci << 2) | 0x3;
frame[2] = FT_SABM | PF;
frame[3] = 1;
frame[4] = 0xFF - crc8(&frame[1], 3, FCS_POLYNOMIAL, FCS_INIT_VALUE, true);
frame[4] = 0xFF - fcs_crc(frame);
frame[5] = SOF_MARKER;
term->write(frame, 6);
}
bool CMUXedTerminal::process_cmux_recv(size_t len)
void CMux::data_available(uint8_t *data, size_t len)
{
return false;
if (type == 0xFF && len > 0 && dlci > 0) {
int virtual_term = dlci - 1;
if (virtual_term < max_terms && read_cb[virtual_term])
read_cb[virtual_term](data, len);
}
}
bool output(uint8_t *data, size_t len, std::string message)
bool CMux::on_cmux(uint8_t *data, size_t actual_len)
{
// printf("OUTPUT: %s len=%ld \n", message.c_str(), len);
for (int i=0; i< len; ++i) {
printf("0x%02x, ",data[i]);
if (!data) {
auto data_to_read = std::min(actual_len, buffer_size);
data = buffer.get();
actual_len = term->read(data, data_to_read);
}
printf("----\n");
printf("%.*s", (int)len, data);
return true;
}
bool CMUXedTerminal::on_cmux(size_t len)
{
auto data_to_read = std::min(len, buffer_size);
auto data = buffer.get();
auto actual_len = term->read(data, data_to_read);
// consumed += actual_len;
ESP_LOG_BUFFER_HEXDUMP("Received", data, actual_len, ESP_LOG_INFO);
for (int i=0; i< len; ++i) {
printf("0x%02x, ",data[i]);
}
printf("\n");
ESP_LOG_BUFFER_HEXDUMP("Received", data, actual_len, ESP_LOG_VERBOSE);
uint8_t* frame = data;
auto available_len = len;
auto available_len = actual_len;
size_t payload_offset = 0;
size_t footer_offset = 0;
while (available_len > 0) {
@ -171,12 +141,12 @@ bool CMUXedTerminal::on_cmux(size_t len)
case cmux_state::PAYLOAD:
if (available_len < payload_len) { // payload
state = cmux_state::PAYLOAD;
output(frame, available_len, "PAYLOAD partial read"); // partial read
data_available(frame, available_len); // partial read
payload_len -= available_len;
return false;
} else { // complete
if (payload_len > 0) {
output(&frame[0], payload_len, "PAYLOAD full read"); // rest read
data_available(&frame[0], payload_len); // rest read
}
available_len -= payload_len;
frame += payload_len;
@ -196,7 +166,7 @@ bool CMUXedTerminal::on_cmux(size_t len)
return true;
}
if (payload_len == 0) {
output(frame_header, 0, "Null payload");
data_available(frame_header, 0); // Null payload
}
frame += footer_offset;
available_len -= footer_offset;
@ -208,12 +178,12 @@ bool CMUXedTerminal::on_cmux(size_t len)
}
return true;
}
void CMUXedTerminal::setup_cmux()
void CMux::init()
{
frame_header_offset = 0;
state = cmux_state::INIT;
term->set_data_cb([this](size_t len){
this->on_cmux(len);
term->set_read_cb([this](uint8_t *data, size_t len) {
this->on_cmux(data, len);
return false;
});
@ -224,51 +194,27 @@ void CMUXedTerminal::setup_cmux()
}
}
void DTE::setup_cmux()
{
auto original_term = std::move(term);
auto cmux_term = std::make_unique<CMUXedTerminal>(std::move(original_term), std::move(buffer), buffer_size);
buffer_size = 0;
cmux_term->setup_cmux();
term = std::move(cmux_term);
}
int CMux::write(int virtual_term, uint8_t *data, size_t len) {
void DTE::send_cmux_command(uint8_t i, const std::string& command)
{
uint8_t frame[6];
frame[0] = SOF_MARKER;
frame[1] = (i << 2) + 1;
frame[2] = FT_UIH;
frame[3] = (command.length() << 1) + 1;
frame[4] = 0xFF - crc8(&frame[1], 3, FCS_POLYNOMIAL, FCS_INIT_VALUE, true);
frame[5] = SOF_MARKER;
term->write(frame, 4);
term->write((uint8_t *)command.c_str(), command.length());
term->write(frame + 4, 2);
ESP_LOG_BUFFER_HEXDUMP("Send", frame, 4, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("Send", (uint8_t *)command.c_str(), command.length(), ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("Send", frame+4, 2, ESP_LOG_INFO);
}
int CMUXedTerminal::write(uint8_t *data, size_t len) {
size_t i = 1;
int i = virtual_term + 1;
uint8_t frame[6];
frame[0] = SOF_MARKER;
frame[1] = (i << 2) + 1;
frame[2] = FT_UIH;
frame[3] = (len << 1) + 1;
frame[4] = 0xFF - crc8(&frame[1], 3, FCS_POLYNOMIAL, FCS_INIT_VALUE, true);
frame[4] = 0xFF - fcs_crc(frame);
frame[5] = SOF_MARKER;
term->write(frame, 4);
term->write(data, len);
term->write(frame + 4, 2);
ESP_LOG_BUFFER_HEXDUMP("Send", frame, 4, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("Send", data, len, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("Send", frame+4, 2, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEXDUMP("Send", frame, 4, ESP_LOG_VERBOSE);
ESP_LOG_BUFFER_HEXDUMP("Send", data, len, ESP_LOG_VERBOSE);
ESP_LOG_BUFFER_HEXDUMP("Send", frame+4, 2, ESP_LOG_VERBOSE);
return 0;
}
void CMux::set_read_cb(int inst, std::function<bool(uint8_t *, size_t)> f) {
if (inst < max_terms)
read_cb[inst] = std::move(f);
}

View File

@ -29,14 +29,12 @@ bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
return false;
netif.stop();
device->set_mode(modem_mode::COMMAND_MODE);
uint8_t* data;
dte->set_data_cb([&](size_t len) -> bool {
auto actual_len = dte->read(&data, 64);
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, actual_len, ESP_LOG_INFO);
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_data_cb(nullptr);
dte->set_read_cb(nullptr);
dte->set_mode(modem_mode::COMMAND_MODE);
mode = m;
return true;
@ -54,6 +52,10 @@ bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
return false;
device->set_mode(modem_mode::CMUX_MODE);
dte->set_mode(modem_mode::CMUX_MODE);
// auto dte1 = create_virtual(dte, 1);
// auto dte2 = create_virtual(dte, 2);
// device->swap_dte(dte1);
// netif->swap_dte(dte2;);
mode = modem_mode::COMMAND_MODE;
return true;
}

View File

@ -23,19 +23,22 @@ const int DTE_BUFFER_SIZE = 1024;
DTE::DTE(std::unique_ptr<Terminal> terminal):
buffer_size(DTE_BUFFER_SIZE), consumed(0),
buffer(std::make_unique<uint8_t[]>(buffer_size)),
term(std::move(terminal)), mode(modem_mode::UNDEF) {}
term(std::move(terminal)), command_term(term.get()), other_term(nullptr),
mode(modem_mode::UNDEF) {}
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms)
{
Scoped<Lock> l(lock);
command_result res = command_result::TIMEOUT;
term->set_data_cb([&](size_t len){
command_term->set_read_cb([&](uint8_t *data, size_t len) {
if (!data) {
auto data_to_read = std::min(len, buffer_size - consumed);
auto data = buffer.get() + consumed;
auto actual_len = term->read(data, data_to_read);
consumed += actual_len;
if (memchr(data, '\n', actual_len)) {
res = got_line(buffer.get(), consumed);
data = buffer.get() + consumed;
len = command_term->read(data, data_to_read);
}
consumed += len;
if (memchr(data, '\n', len)) {
res = got_line(data, consumed);
if (res == command_result::OK || res == command_result::FAIL) {
signal.set(GOT_LINE);
return true;
@ -43,12 +46,23 @@ command_result DTE::command(const std::string &command, got_line_cb got_line, ui
}
return false;
});
term->write((uint8_t *)command.c_str(), command.length());
command_term->write((uint8_t *)command.c_str(), command.length());
auto got_lf = signal.wait(GOT_LINE, time_ms);
if (got_lf && res == command_result::TIMEOUT) {
throw_if_esp_fail(ESP_ERR_INVALID_STATE);
}
consumed = 0;
term->set_data_cb(nullptr);
command_term->set_read_cb(nullptr);
return res;
}
void DTE::setup_cmux()
{
auto original_term = std::move(term);
auto cmux_term = std::make_shared<CMux>(std::move(original_term), std::move(buffer), buffer_size);
buffer_size = 0;
cmux_term->init();
term = std::make_unique<CMuxInstance>(cmux_term, 0);
command_term = term.get(); // use command terminal as previously
other_term = std::make_unique<CMuxInstance>(cmux_term, 1);
}

View File

@ -53,7 +53,7 @@ esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args) {
// check if PPP error events are enabled, if not, do enable the error occurred/state changed
// to notify the modem layer when switching modes
esp_netif_ppp_config_t ppp_config;
// esp_netif_ppp_get_params(esp_netif, &ppp_config);
esp_netif_ppp_get_params(esp_netif, &ppp_config);
if (!ppp_config.ppp_error_event_enabled) {
ppp_config.ppp_error_event_enabled = true;
esp_netif_ppp_set_params(esp_netif, &ppp_config);
@ -81,10 +81,8 @@ Netif::Netif(std::shared_ptr<DTE> e, esp_netif_t *ppp_netif) :
}
void Netif::start() {
ppp_dte->set_data_cb([this](size_t len) -> bool {
uint8_t *data;
auto actual_len = ppp_dte->read(&data, len);
receive(data, actual_len);
ppp_dte->set_read_cb([this](uint8_t *data, size_t len) -> bool {
receive(data, len);
return false;
});
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);

View File

@ -149,7 +149,7 @@ public:
int read(uint8_t *data, size_t len) override;
void set_data_cb(std::function<bool(size_t len)> f) override {
void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f) override {
on_data = std::move(f);
signal.set(TASK_PARAMS);
}
@ -185,7 +185,7 @@ std::unique_ptr<Terminal> create_uart_terminal(const esp_modem_dte_config *confi
}
void uart_terminal::task() {
std::function<bool(size_t len)> on_data_priv = nullptr;
std::function<bool(uint8_t *data, size_t len)> on_data_priv = nullptr;
uart_event_t event;
size_t len;
signal.set(TASK_INIT);
@ -207,7 +207,7 @@ void uart_terminal::task() {
uart_get_buffered_data_len(uart.port, &len);
// ESP_LOGI(TAG, "UART_DATA len=%d, on_data=%d", len, (bool)on_data);
if (len && on_data_priv) {
if (on_data_priv(len)) {
if (on_data_priv(nullptr, len)) {
on_data_priv = nullptr;
}
}