mirror of
				https://github.com/espressif/esp-protocols.git
				synced 2025-11-02 23:51:38 +01:00 
			
		
		
		
	feat(modem): Add support for guessing mode
This commit is contained in:
		@@ -30,9 +30,11 @@ public:
 | 
			
		||||
    ~DCE_Mode() = default;
 | 
			
		||||
    bool set(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
 | 
			
		||||
    modem_mode get();
 | 
			
		||||
    modem_mode guess(DTE *dte, bool with_cmux = false);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
 | 
			
		||||
    modem_mode guess_unsafe(DTE *dte, bool with_cmux);
 | 
			
		||||
    modem_mode mode;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
@@ -79,6 +81,11 @@ public:
 | 
			
		||||
        return dte->command(command, std::move(got_line), time_ms);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    modem_mode guess_mode(bool with_cmux = false)
 | 
			
		||||
    {
 | 
			
		||||
        return mode.guess(dte.get(), with_cmux);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool set_mode(modem_mode m)
 | 
			
		||||
    {
 | 
			
		||||
        return mode.set(dte.get(), device.get(), netif, m);
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,18 @@ public:
 | 
			
		||||
 | 
			
		||||
    int write(DTE_Command command);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief send data to the selected terminal, by default (without term_id argument)
 | 
			
		||||
     * this API works the same as write: sends data to the secondary terminal, which is
 | 
			
		||||
     * typically used as data terminal (for networking).
 | 
			
		||||
     *
 | 
			
		||||
     * @param data Data pointer to write
 | 
			
		||||
     * @param len Data len to write
 | 
			
		||||
     * @param term_id Terminal id: Primary if id==0, Secondary if id==1
 | 
			
		||||
     * @return number of bytes written
 | 
			
		||||
     */
 | 
			
		||||
    int send(uint8_t *data, size_t len, int term_id = 1);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @brief Reading from the underlying terminal
 | 
			
		||||
     * @param d Returning the data pointer of the received payload
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 */
 | 
			
		||||
@@ -37,6 +37,17 @@ enum class modem_mode {
 | 
			
		||||
    CMUX_MANUAL_DATA,    /*!< Sets the primary terminal to DATA mode in manual CMUX */
 | 
			
		||||
    CMUX_MANUAL_COMMAND, /*!< Sets the primary terminal to COMMAND mode in manual CMUX */
 | 
			
		||||
    CMUX_MANUAL_SWAP,    /*!< Swaps virtual terminals in manual CMUX mode (primary <-> secondary) */
 | 
			
		||||
    RESUME_DATA_MODE,    /*!< This is used when the device is already in DATA mode and we need the modem lib to
 | 
			
		||||
                          * enter the mode without switching. On success, we would end up in DATA-mode, UNDEF otherwise */
 | 
			
		||||
    RESUME_COMMAND_MODE, /*!< This is used when the device is already in COMMAND mode and we want to resume it
 | 
			
		||||
                          * On success, we would end up in DATA-mode, UNDEF otherwise */
 | 
			
		||||
    RESUME_CMUX_MANUAL_MODE, /*!< This is used when the device is already in CMUX mode and we need the modem lib to
 | 
			
		||||
                              * enter it without switching. On success, we would end up in CMUX_MANUAL-mode, UNDEF otherwise */
 | 
			
		||||
    RESUME_CMUX_MANUAL_DATA, /*!< This is used when the device is already in CMUX-DATA mode and we need the modem lib to
 | 
			
		||||
                              * enter it without switching. On success, we would end up in CMUX_MANUAL-DATA mode, UNDEF otherwise */
 | 
			
		||||
    AUTODETECT,              /*!< Auto-detection command: It tries to send a few packets in order to recognize which mode the
 | 
			
		||||
                              * the device currently is and update the modem library mode. On success the modem is updated,
 | 
			
		||||
                              * otherwise it's set to UNDEF */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 */
 | 
			
		||||
@@ -103,6 +103,51 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
 | 
			
		||||
        break;
 | 
			
		||||
    case modem_mode::AUTODETECT: {
 | 
			
		||||
        auto guessed = guess_unsafe(dte, true);
 | 
			
		||||
        if (guessed == modem_mode::UNDEF) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        // prepare the undefined mode before to allow all possible transitions
 | 
			
		||||
        if (!dte->set_mode(modem_mode::UNDEF)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        mode = modem_mode::UNDEF;
 | 
			
		||||
        ESP_LOGD("DCE mode", "Detected mode: %d", static_cast<int>(guessed));
 | 
			
		||||
        if (guessed == modem_mode::DATA_MODE) {
 | 
			
		||||
            return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_DATA_MODE);
 | 
			
		||||
        } else if (guessed == esp_modem::modem_mode::COMMAND_MODE) {
 | 
			
		||||
            return set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_COMMAND_MODE);
 | 
			
		||||
        } else if (guessed == esp_modem::modem_mode::CMUX_MODE) {
 | 
			
		||||
            if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_MODE)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            // now we guess the mode for each terminal
 | 
			
		||||
            guessed = guess_unsafe(dte, false);
 | 
			
		||||
            ESP_LOGD("DCE mode", "Detected mode on primary term: %d", static_cast<int>(guessed));
 | 
			
		||||
            // now we need to access the second terminal, so we could simply send a SWAP command
 | 
			
		||||
            // (switching to data mode does the swapping internally, so we only swap if we're in CMD mode)
 | 
			
		||||
            if (guessed == modem_mode::DATA_MODE) {
 | 
			
		||||
                // switch to DATA on the primary terminal and swap terminals
 | 
			
		||||
                if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // swap terminals
 | 
			
		||||
                if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::CMUX_MANUAL_SWAP)) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            guessed = guess_unsafe(dte, false);
 | 
			
		||||
            ESP_LOGD("DCE mode", "Detected mode on secondary term: %d", static_cast<int>(guessed));
 | 
			
		||||
            if (guessed == modem_mode::DATA_MODE) {
 | 
			
		||||
                if (!set_unsafe(dte, device, netif, esp_modem::modem_mode::RESUME_CMUX_MANUAL_DATA)) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    case modem_mode::COMMAND_MODE:
 | 
			
		||||
        if (mode == modem_mode::COMMAND_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -122,6 +167,32 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
 | 
			
		||||
        }
 | 
			
		||||
        mode = m;
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::RESUME_DATA_MODE:
 | 
			
		||||
        if (!dte->set_mode(modem_mode::DATA_MODE)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        netif.start();
 | 
			
		||||
        mode = modem_mode::DATA_MODE;
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::RESUME_COMMAND_MODE:
 | 
			
		||||
        if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        mode = modem_mode::COMMAND_MODE;
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::RESUME_CMUX_MANUAL_MODE:
 | 
			
		||||
        if (!dte->set_mode(modem_mode::CMUX_MANUAL_MODE)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        mode = modem_mode::CMUX_MANUAL_MODE;
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::RESUME_CMUX_MANUAL_DATA:
 | 
			
		||||
        if (!dte->set_mode(modem_mode::CMUX_MANUAL_SWAP)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        netif.start();
 | 
			
		||||
        mode = modem_mode::CMUX_MANUAL_MODE;
 | 
			
		||||
        return true;
 | 
			
		||||
    case modem_mode::DATA_MODE:
 | 
			
		||||
        if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -191,4 +262,114 @@ modem_mode DCE_Mode::get()
 | 
			
		||||
    return mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
modem_mode DCE_Mode::guess(DTE *dte, bool with_cmux)
 | 
			
		||||
{
 | 
			
		||||
    Scoped<DTE> lock(*dte);
 | 
			
		||||
    return guess_unsafe(dte, with_cmux);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This namespace contains probe packets and expected replies on 3 different protocols,
 | 
			
		||||
 * the modem device could use (as well as timeouts and mode ids for synchronisation)
 | 
			
		||||
 */
 | 
			
		||||
namespace probe {
 | 
			
		||||
 | 
			
		||||
namespace ppp {
 | 
			
		||||
// Test that we're in the PPP mode by sending an LCP protocol echo request and expecting LCP echo reply
 | 
			
		||||
constexpr std::array<uint8_t, 16> lcp_echo_request = {0x7e, 0xff, 0x03, 0xc0, 0x21, 0x09, 0x01, 0x00, 0x08, 0x99, 0xd1, 0x35, 0xc1, 0x8e, 0x2c, 0x7e };
 | 
			
		||||
constexpr std::array<uint8_t, 5> lcp_echo_reply_head = {0x7e, 0xff, 0x7d, 0x23, 0xc0};
 | 
			
		||||
const size_t mode = 1 << 0;
 | 
			
		||||
const int timeout = 200;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace cmd {
 | 
			
		||||
// For command mode, we just send a simple AT command
 | 
			
		||||
const char at[] = "\r\nAT\r\n";
 | 
			
		||||
const size_t max_at_reply = 16; // account for some whitespaces and/or CMUX encapsulation
 | 
			
		||||
const char reply[] = { 'O', 'K' };
 | 
			
		||||
const int mode = 1 << 1;
 | 
			
		||||
const int timeout = 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace cmux {
 | 
			
		||||
// For CMUX mode, we send an SABM on control terminal (0)
 | 
			
		||||
const uint8_t sabm0_reqest[] = {0xf9, 0x03, 0x3f, 0x01, 0x1c, 0xf9};
 | 
			
		||||
const uint8_t sabm0_reply[] = {0xf9, 0x03, 0x73, 0x01};
 | 
			
		||||
const int mode = 1 << 0;
 | 
			
		||||
const int timeout = 200;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
modem_mode DCE_Mode::guess_unsafe(DTE *dte, bool with_cmux)
 | 
			
		||||
{
 | 
			
		||||
    // placeholder for reply and its size, since it could come in pieces, and we have to cache
 | 
			
		||||
    // this is captured by the lambda by reference.
 | 
			
		||||
    // must make sure the lambda is cleared before exiting this function (done by dte->on_read(nullptr))
 | 
			
		||||
    uint8_t reply[std::max(probe::cmd::max_at_reply, std::max(sizeof(probe::ppp::lcp_echo_request), sizeof(probe::cmux::sabm0_reply)))];
 | 
			
		||||
    size_t reply_pos = 0;
 | 
			
		||||
    auto signal = std::make_shared<SignalGroup>();
 | 
			
		||||
    std::weak_ptr<SignalGroup> weak_signal = signal;
 | 
			
		||||
    dte->on_read([weak_signal, with_cmux, &reply, &reply_pos](uint8_t *data, size_t len) {
 | 
			
		||||
        // storing the response in the `reply` array and de-fragmenting
 | 
			
		||||
        if (reply_pos >= sizeof(reply)) {
 | 
			
		||||
            return command_result::TIMEOUT;
 | 
			
		||||
        }
 | 
			
		||||
        auto reply_size = std::min((size_t)sizeof(reply) - reply_pos, len);
 | 
			
		||||
        ::memcpy(reply + reply_pos, data, reply_size);
 | 
			
		||||
        reply_pos += reply_size;
 | 
			
		||||
        ESP_LOG_BUFFER_HEXDUMP("esp-modem: guess mode data:", reply, reply_pos, ESP_LOG_DEBUG);
 | 
			
		||||
 | 
			
		||||
        // Check whether the response resembles the "golden" reply (for these 3 protocols)
 | 
			
		||||
        if (reply_pos >= sizeof(probe::ppp::lcp_echo_reply_head)) {
 | 
			
		||||
            // check for initial 2 bytes
 | 
			
		||||
            auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head.data(), 2));
 | 
			
		||||
            // and check the other two bytes for protocol ID: LCP
 | 
			
		||||
            if (ptr && ptr[3] == probe::ppp::lcp_echo_reply_head[3] && ptr[4] == probe::ppp::lcp_echo_reply_head[4]) {
 | 
			
		||||
                if (auto signal = weak_signal.lock()) {
 | 
			
		||||
                    signal->set(probe::ppp::mode);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (reply_pos >= 4 && memmem(reply, reply_pos, probe::cmd::reply, sizeof(probe::cmd::reply))) {
 | 
			
		||||
            if (reply[0] != 0xf9) {   // double check that the reply is not wrapped in CMUX headers
 | 
			
		||||
                if (auto signal = weak_signal.lock()) {
 | 
			
		||||
                    signal->set(probe::cmd::mode);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (with_cmux && reply_pos >= sizeof(probe::cmux::sabm0_reply)) {
 | 
			
		||||
            // checking the initial 3 bytes
 | 
			
		||||
            auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::cmux::sabm0_reply, 3));
 | 
			
		||||
            // and checking that DLCI is 0 (control frame)
 | 
			
		||||
            if (ptr && (ptr[3] >> 2) == 0) {
 | 
			
		||||
                if (auto signal = weak_signal.lock()) {
 | 
			
		||||
                    signal->set(probe::cmux::mode);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return command_result::TIMEOUT;
 | 
			
		||||
    });
 | 
			
		||||
    auto guessed = modem_mode::UNDEF;
 | 
			
		||||
    // Check the PPP mode fist by sending LCP echo request
 | 
			
		||||
    dte->send((uint8_t *)probe::ppp::lcp_echo_request.data(), sizeof(probe::ppp::lcp_echo_request), 0);
 | 
			
		||||
    if (signal->wait(probe::ppp::mode, probe::ppp::timeout)) {
 | 
			
		||||
        guessed = modem_mode::DATA_MODE;
 | 
			
		||||
    } else {    // LCP echo timeout
 | 
			
		||||
        // now check for AT mode
 | 
			
		||||
        reply_pos = 0;
 | 
			
		||||
        dte->send((uint8_t *)probe::cmd::at, sizeof(probe::cmd::at), 0);
 | 
			
		||||
        if (signal->wait(probe::cmd::mode, probe::cmd::timeout)) {
 | 
			
		||||
            guessed = modem_mode::COMMAND_MODE;
 | 
			
		||||
        } else if (with_cmux) {     // no AT reply, check for CMUX mode (if requested)
 | 
			
		||||
            reply_pos = 0;
 | 
			
		||||
            dte->send((uint8_t *) probe::cmux::sabm0_reqest, sizeof(probe::cmux::sabm0_reqest), 0);
 | 
			
		||||
            if (signal->wait(probe::cmux::mode, probe::cmux::timeout)) {
 | 
			
		||||
                guessed = modem_mode::CMUX_MODE;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    dte->on_read(nullptr);
 | 
			
		||||
    return guessed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // esp_modem
 | 
			
		||||
 
 | 
			
		||||
@@ -223,13 +223,13 @@ bool DTE::set_mode(modem_mode m)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // transitions (COMMAND|DUAL|CMUX|UNDEF) -> DATA
 | 
			
		||||
    if (m == modem_mode::DATA_MODE) {
 | 
			
		||||
    if (m == modem_mode::DATA_MODE || m == modem_mode::RESUME_DATA_MODE) {
 | 
			
		||||
        if (mode == modem_mode::CMUX_MODE || mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::DUAL_MODE) {
 | 
			
		||||
            // mode stays the same, but need to swap terminals (as command has been switched)
 | 
			
		||||
            secondary_term.swap(primary_term);
 | 
			
		||||
            set_command_callbacks();
 | 
			
		||||
        } else {
 | 
			
		||||
            mode = m;
 | 
			
		||||
            mode = modem_mode::DATA_MODE;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -316,6 +316,12 @@ int DTE::write(uint8_t *data, size_t len)
 | 
			
		||||
    return secondary_term->write(data, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DTE::send(uint8_t *data, size_t len, int term_id)
 | 
			
		||||
{
 | 
			
		||||
    Terminal *term = term_id == 0 ? primary_term.get() : secondary_term.get();
 | 
			
		||||
    return term->write(data, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DTE::write(DTE_Command command)
 | 
			
		||||
{
 | 
			
		||||
    return primary_term->write(command.data, command.len);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 */
 | 
			
		||||
@@ -87,8 +87,10 @@ void Netif::start()
 | 
			
		||||
        receive(data, len);
 | 
			
		||||
        return true;
 | 
			
		||||
    });
 | 
			
		||||
    signal.set(PPP_STARTED);
 | 
			
		||||
    esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
 | 
			
		||||
    if (!signal.is_any(PPP_STARTED)) {
 | 
			
		||||
        signal.set(PPP_STARTED);
 | 
			
		||||
        esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Netif::stop()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user