diff --git a/components/esp_modem/examples/modem_console/main/console_helper.hpp b/components/esp_modem/examples/modem_console/main/console_helper.hpp index a799df651..cd42d7444 100644 --- a/components/esp_modem/examples/modem_console/main/console_helper.hpp +++ b/components/esp_modem/examples/modem_console/main/console_helper.hpp @@ -10,6 +10,7 @@ #pragma once #include +#include #include #include #include diff --git a/components/esp_modem/examples/simple_cmux_client/main/simple_cmux_client_main.cpp b/components/esp_modem/examples/simple_cmux_client/main/simple_cmux_client_main.cpp index 89337ce92..e5513080c 100644 --- a/components/esp_modem/examples/simple_cmux_client/main/simple_cmux_client_main.cpp +++ b/components/esp_modem/examples/simple_cmux_client/main/simple_cmux_client_main.cpp @@ -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; } diff --git a/components/esp_modem/include/cxx_include/esp_modem_buffer.hpp b/components/esp_modem/include/cxx_include/esp_modem_buffer.hpp new file mode 100644 index 000000000..b04903b35 --- /dev/null +++ b/components/esp_modem/include/cxx_include/esp_modem_buffer.hpp @@ -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 data; + size_t size{}; + size_t consumed{}; +}; + +} diff --git a/components/esp_modem/include/cxx_include/esp_modem_cmux.hpp b/components/esp_modem/include/cxx_include/esp_modem_cmux.hpp index f88a85ac2..16c6adc87 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_cmux.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_cmux.hpp @@ -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 t, std::unique_ptr 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 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, 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 read_cb[MAX_TERMINALS_NUM]; /*!< Function pointers to read callbacks */ - std::unique_ptr term; /*!< The original terminal */ + std::shared_ptr 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 buffer; + unique_buffer buffer; Lock lock; }; diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp index 7d080bf10..f909bfce8 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce.hpp @@ -40,6 +40,7 @@ public: modem_mode get(); private: + bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m); modem_mode mode; }; diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce_module.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce_module.hpp index 628d2e877..72f141ec8 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce_module.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce_module.hpp @@ -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; } diff --git a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp index c505b2ed1..17b48f51e 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp @@ -15,18 +15,20 @@ #pragma once #include +#include #include #include -#include #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; /*!< Declaring "Scoped 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 buffer; /*!< DTE buffer */ - std::unique_ptr term; /*!< Primary terminal for this DTE */ - Terminal *command_term; /*!< Reference to the terminal used for sending commands */ - std::unique_ptr 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_term; /*!< Primary terminal for this DTE */ + std::shared_ptr command_term; /*!< Reference to the terminal used for sending commands */ + std::shared_ptr 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 on_data; /*!< on data callback for current terminal */ + std::function on_data; /*!< on data callback for current terminal */ }; /** diff --git a/components/esp_modem/include/cxx_include/esp_modem_primitives.hpp b/components/esp_modem/include/cxx_include/esp_modem_primitives.hpp index 7509eced5..2df6bf85f 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_primitives.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_primitives.hpp @@ -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; using TaskT = std::thread; diff --git a/components/esp_modem/include/esp_modem_c_api_types.h b/components/esp_modem/include/esp_modem_c_api_types.h index 577b4ce03..2c37b796b 100644 --- a/components/esp_modem/include/esp_modem_c_api_types.h +++ b/components/esp_modem/include/esp_modem_c_api_types.h @@ -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; /** diff --git a/components/esp_modem/src/esp_modem_c_api.cpp b/components/esp_modem/src/esp_modem_c_api.cpp index 6f5a6ad7f..e89fc1950 100644 --- a/components/esp_modem/src/esp_modem_c_api.cpp +++ b/components/esp_modem/src/esp_modem_c_api.cpp @@ -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; } diff --git a/components/esp_modem/src/esp_modem_cmux.cpp b/components/esp_modem/src/esp_modem_cmux.cpp index f37bd8512..010216278 100644 --- a/components/esp_modem/src/esp_modem_cmux.cpp +++ b/components/esp_modem/src/esp_modem_cmux.cpp @@ -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 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 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 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 f) read_cb[inst] = std::move(f); } } + +std::pair, unique_buffer> CMux::detach() +{ + return std::make_pair(std::move(term), std::move(buffer)); +} diff --git a/components/esp_modem/src/esp_modem_dce.cpp b/components/esp_modem/src/esp_modem_dce.cpp index 197851135..21c2abc7a 100644 --- a/components/esp_modem/src/esp_modem_dce.cpp +++ b/components/esp_modem/src/esp_modem_dce.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #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 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(); + std::weak_ptr 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({"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; diff --git a/components/esp_modem/src/esp_modem_dte.cpp b/components/esp_modem/src/esp_modem_dte.cpp index 26a89868f..f3df20ddf 100644 --- a/components/esp_modem/src/esp_modem_dte.cpp +++ b/components/esp_modem/src/esp_modem_dte.cpp @@ -15,6 +15,7 @@ #include #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): - buffer_size(config->dte_buffer_size), consumed(0), - buffer(std::make_unique(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): - buffer_size(dte_default_buffer_size), consumed(0), - buffer(std::make_unique(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 l(lock); + Scoped 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(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(command_term, std::move(buffer)); if (cmux_term == nullptr) { return false; } - buffer_size = 0; if (!cmux_term->init()) { return false; } - term = std::make_unique(cmux_term, 0); - if (term == nullptr) { + command_term = std::make_unique(cmux_term, 0); + if (command_term == nullptr) { return false; } - command_term = term.get(); // use command terminal as previously - other_term = std::make_unique(cmux_term, 1); + data_term = std::make_unique(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 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 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); -} \ No newline at end of file + 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(size)), size(size), consumed(0) {} diff --git a/components/esp_modem/src/esp_modem_netif_linux.cpp b/components/esp_modem/src/esp_modem_netif_linux.cpp index d7c684e59..f965d1084 100644 --- a/components/esp_modem/src/esp_modem_netif_linux.cpp +++ b/components/esp_modem/src/esp_modem_netif_linux.cpp @@ -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 \ No newline at end of file diff --git a/components/esp_modem/test/host_test/main/LoopbackTerm.cpp b/components/esp_modem/test/host_test/main/LoopbackTerm.cpp index 0377372df..f3f30227a 100644 --- a/components/esp_modem/test/host_test/main/LoopbackTerm.cpp +++ b/components/esp_modem/test/host_test/main/LoopbackTerm.cpp @@ -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 diff --git a/components/esp_modem/test/host_test/main/test_modem.cpp b/components/esp_modem/test/host_test/main/test_modem.cpp index f8d8458a5..881754b37 100644 --- a/components/esp_modem/test/host_test/main/test_modem.cpp +++ b/components/esp_modem/test/host_test/main/test_modem.cpp @@ -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(); @@ -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(); @@ -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';