forked from espressif/esp-protocols
Merge pull request #42 from david-cermak/feature/modem_cmux_ext
esp_modem: CMUX mode improvements
This commit is contained in:
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <esp_console.h>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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{};
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
modem_mode get();
|
||||
|
||||
private:
|
||||
bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
|
||||
modem_mode mode;
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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';
|
||||
|
Reference in New Issue
Block a user