mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-23 15:27:28 +02:00
feat(modem): Add mqtt example in AT-only mode
Similar to pppos-client, but without PPP mode. This uses standard mqtt client and a loopback service that forwards the TCP traffic from localhost to the modem and vice-versa. (next step is to create a dedicated tcp-transport layer for modem) Console example uses DCE commandable to demontrate how to handle URC
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modem-console)
|
||||
|
@ -1,5 +1,6 @@
|
||||
idf_component_register(SRCS "modem_console_main.cpp"
|
||||
"console_helper.cpp"
|
||||
"my_module_dce.cpp"
|
||||
"httpget_handle.c"
|
||||
"ping_handle.c"
|
||||
REQUIRES console esp_http_client nvs_flash
|
||||
|
@ -17,7 +17,7 @@ menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_MODEM_DEVICE
|
||||
prompt "Choose supported modem device (DCE)"
|
||||
default EXAMPLE_MODEM_DEVICE_BG96
|
||||
default EXAMPLE_MODEM_DEVICE_SHINY
|
||||
help
|
||||
Select modem device connected to the ESP DTE.
|
||||
config EXAMPLE_MODEM_DEVICE_SHINY
|
||||
|
@ -89,6 +89,14 @@ void wakeup_modem(void)
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY
|
||||
command_result handle_urc(uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO);
|
||||
return command_result::TIMEOUT;
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
static RTC_RODATA_ATTR char apn_rtc[20] = DEFAULT_APN;
|
||||
@ -122,19 +130,19 @@ extern "C" void app_main(void)
|
||||
dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2;
|
||||
auto uart_dte = create_uart_dte(&dte_config);
|
||||
|
||||
#if CONFIG_EXAMPLE_MODEM_DEVICE_SHINY == 1
|
||||
#if defined(CONFIG_EXAMPLE_MODEM_DEVICE_SHINY)
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SHINY module...");
|
||||
auto dce = create_shiny_dce(&dce_config, uart_dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1
|
||||
#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_BG96)
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module...");
|
||||
auto dce = create_BG96_dce(&dce_config, uart_dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM800 == 1
|
||||
#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM800)
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SIM800 module...");
|
||||
auto dce = create_SIM800_dce(&dce_config, uart_dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7000 == 1
|
||||
#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7000)
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SIM7000 module...");
|
||||
auto dce = create_SIM7000_dce(&dce_config, uart_dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7070 == 1
|
||||
#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7070)
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SIM7070 module...");
|
||||
auto dce = create_SIM7070_dce(&dce_config, uart_dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1
|
||||
@ -303,8 +311,9 @@ extern "C" void app_main(void)
|
||||
|
||||
const ConsoleCommand GetOperatorName("get_operator_name", "reads the operator name", no_args, [&](ConsoleCommand * c) {
|
||||
std::string operator_name;
|
||||
int act;
|
||||
ESP_LOGI(TAG, "Reading operator name...");
|
||||
CHECK_ERR(dce->get_operator_name(operator_name), ESP_LOGI(TAG, "OK. Operator name: %s", operator_name.c_str()));
|
||||
CHECK_ERR(dce->get_operator_name(operator_name, act), ESP_LOGI(TAG, "OK. Operator name: %s", operator_name.c_str()));
|
||||
});
|
||||
|
||||
const struct GenericCommandArgs {
|
||||
@ -356,6 +365,20 @@ extern "C" void app_main(void)
|
||||
ESP_LOGI(TAG, "Resetting the module...");
|
||||
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
|
||||
});
|
||||
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY
|
||||
const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) {
|
||||
static int cnt = 0;
|
||||
if (++cnt % 2) {
|
||||
ESP_LOGI(TAG, "Adding URC handler");
|
||||
dce->set_on_read(handle_urc);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "URC removed");
|
||||
dce->set_on_read(nullptr);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
const struct SetApn {
|
||||
SetApn(): apn(STR1, nullptr, nullptr, "<apn>", "APN (Access Point Name)") {}
|
||||
CommandArgs apn;
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Modem console example: Custom DCE
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "cxx_include/esp_modem_dce_module.hpp"
|
||||
#include "generate/esp_modem_command_declare.inc"
|
||||
#include "my_module_dce.hpp"
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
//
|
||||
// Define preprocessor's forwarding to dce_commands definitions
|
||||
//
|
||||
|
||||
// Helper macros to handle multiple arguments of declared API
|
||||
#define ARGS0
|
||||
#define ARGS1 , p1
|
||||
#define ARGS2 , p1 , p2
|
||||
#define ARGS3 , p1 , p2 , p3
|
||||
#define ARGS4 , p1 , p2 , p3, p4
|
||||
#define ARGS5 , p1 , p2 , p3, p4, p5
|
||||
#define ARGS6 , p1 , p2 , p3, p4, p5, p6
|
||||
|
||||
#define _ARGS(x) ARGS ## x
|
||||
#define ARGS(x) _ARGS(x)
|
||||
|
||||
#define CMD_OK (1)
|
||||
#define CMD_FAIL (2)
|
||||
|
||||
//
|
||||
// Repeat all declarations and forward to the AT commands defined in esp_modem::dce_commands:: namespace
|
||||
//
|
||||
#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, arg_nr, ...) \
|
||||
return_type Shiny::DCE::name(__VA_ARGS__) { return esp_modem::dce_commands::name(this ARGS(arg_nr) ); }
|
||||
|
||||
DECLARE_ALL_COMMAND_APIS(return_type name(...) )
|
||||
|
||||
#undef ESP_MODEM_DECLARE_DCE_COMMAND
|
||||
|
||||
std::unique_ptr<Shiny::DCE> create_shiny_dce(const esp_modem::dce_config *config,
|
||||
std::shared_ptr<esp_modem::DTE> dte,
|
||||
esp_netif_t *netif)
|
||||
{
|
||||
return Shiny::Factory::create(config, std::move(dte), netif);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Definition of the command API, which makes the Shiny::DCE "command-able class"
|
||||
* @param cmd Command to send
|
||||
* @param got_line Recv line callback
|
||||
* @param time_ms timeout in ms
|
||||
* @param separator line break separator
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/
|
||||
command_result Shiny::DCE::command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms, const char separator)
|
||||
{
|
||||
if (!handling_urc) {
|
||||
return dte->command(cmd, got_line, time_ms, separator);
|
||||
}
|
||||
handle_cmd = got_line;
|
||||
signal.clear(CMD_OK | CMD_FAIL);
|
||||
esp_modem::DTE_Command command{cmd};
|
||||
dte->write(command);
|
||||
signal.wait_any(CMD_OK | CMD_FAIL, time_ms);
|
||||
handle_cmd = nullptr;
|
||||
if (signal.is_any(CMD_OK)) {
|
||||
return esp_modem::command_result::OK;
|
||||
}
|
||||
if (signal.is_any(CMD_FAIL)) {
|
||||
return esp_modem::command_result::FAIL;
|
||||
}
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle received data
|
||||
*
|
||||
* @param data Data received from the device
|
||||
* @param len Length of the data
|
||||
* @return standard command return code (OK|FAIL|TIMEOUT)
|
||||
*/
|
||||
command_result Shiny::DCE::handle_data(uint8_t *data, size_t len)
|
||||
{
|
||||
if (std::memchr(data, '\n', len)) {
|
||||
if (handle_urc) {
|
||||
handle_urc(data, len);
|
||||
}
|
||||
if (handle_cmd) {
|
||||
auto ret = handle_cmd(data, len);
|
||||
if (ret == esp_modem::command_result::TIMEOUT) {
|
||||
return command_result::TIMEOUT;
|
||||
}
|
||||
if (ret == esp_modem::command_result::OK) {
|
||||
signal.set(CMD_OK);
|
||||
}
|
||||
if (ret == esp_modem::command_result::FAIL) {
|
||||
signal.set(CMD_FAIL);
|
||||
}
|
||||
}
|
||||
}
|
||||
return command_result::TIMEOUT;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@ -11,6 +11,8 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cxx_include/esp_modem_dce_factory.hpp"
|
||||
#include "cxx_include/esp_modem_dce_module.hpp"
|
||||
|
||||
@ -28,13 +30,84 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
namespace Shiny {
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
class DCE : public esp_modem::DCE_T<MyShinyModem>, public CommandableIf {
|
||||
public:
|
||||
using DCE_T<MyShinyModem>::DCE_T;
|
||||
|
||||
command_result
|
||||
command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms) override
|
||||
{
|
||||
return command(cmd, got_line, time_ms, '\n');
|
||||
}
|
||||
|
||||
command_result
|
||||
command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms, const char separator) override;
|
||||
|
||||
int write(uint8_t *data, size_t len) override
|
||||
{
|
||||
return dte->write(data, len);
|
||||
}
|
||||
|
||||
void on_read(got_line_cb on_data) override
|
||||
{
|
||||
return dte->on_read(on_data);
|
||||
}
|
||||
|
||||
#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \
|
||||
esp_modem::return_type name(__VA_ARGS__);
|
||||
|
||||
DECLARE_ALL_COMMAND_APIS(forwards name(...))
|
||||
|
||||
#undef ESP_MODEM_DECLARE_DCE_COMMAND
|
||||
|
||||
void set_on_read(esp_modem::got_line_cb on_read_cb)
|
||||
{
|
||||
if (on_read_cb == nullptr) {
|
||||
handling_urc = false;
|
||||
handle_urc = nullptr;
|
||||
dte->on_read(nullptr);
|
||||
return;
|
||||
}
|
||||
handle_urc = std::move(on_read_cb);
|
||||
dte->on_read([this](uint8_t *data, size_t len) {
|
||||
this->handle_data(data, len);
|
||||
return command_result::TIMEOUT;
|
||||
});
|
||||
handling_urc = true;
|
||||
}
|
||||
|
||||
private:
|
||||
got_line_cb handle_urc{nullptr};
|
||||
got_line_cb handle_cmd{nullptr};
|
||||
SignalGroup signal;
|
||||
bool handling_urc {false};
|
||||
|
||||
command_result handle_data(uint8_t *data, size_t len);
|
||||
|
||||
};
|
||||
|
||||
class Factory: public ::esp_modem::dce_factory::Factory {
|
||||
public:
|
||||
|
||||
static std::unique_ptr<DCE> create(const esp_modem::dce_config *config,
|
||||
std::shared_ptr<esp_modem::DTE> dte,
|
||||
esp_netif_t *netif)
|
||||
{
|
||||
return build_generic_DCE<MyShinyModem, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Shiny
|
||||
|
||||
/**
|
||||
* @brief Helper create method which employs the DCE factory for creating DCE objects templated by a custom module
|
||||
* @return unique pointer of the resultant DCE
|
||||
*/
|
||||
std::unique_ptr<esp_modem::DCE> create_shiny_dce(const esp_modem::dce_config *config,
|
||||
std::unique_ptr<Shiny::DCE> create_shiny_dce(const esp_modem::dce_config *config,
|
||||
std::shared_ptr<esp_modem::DTE> dte,
|
||||
esp_netif_t *netif)
|
||||
{
|
||||
return esp_modem::dce_factory::Factory::build_unique<MyShinyModem>(config, std::move(dte), netif);
|
||||
}
|
||||
esp_netif_t *netif);
|
||||
|
@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modem_tcp_client)
|
10
components/esp_modem/examples/modem_tcp_client/README.md
Normal file
10
components/esp_modem/examples/modem_tcp_client/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Modem TCP client
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
This example demonstrates how to act as a MQTT client using modem's TCP commands (provided, the device supports "socket" related commands)
|
||||
|
||||
### Supported IDF versions
|
||||
|
||||
This example is supported from IDF `v4.4`.
|
@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS "modem_client.cpp"
|
||||
"sock_dce.cpp"
|
||||
"sock_commands.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
@ -0,0 +1,148 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_MODEM_DEVICE
|
||||
prompt "Choose supported modem device (DCE)"
|
||||
default EXAMPLE_MODEM_DEVICE_BG96
|
||||
help
|
||||
Select modem device connected to the ESP DTE.
|
||||
config EXAMPLE_MODEM_DEVICE_SIM800
|
||||
bool "SIM800"
|
||||
help
|
||||
SIMCom SIM800L is a GSM/GPRS module.
|
||||
It supports Quad-band 850/900/1800/1900MHz.
|
||||
config EXAMPLE_MODEM_DEVICE_BG96
|
||||
bool "BG96"
|
||||
help
|
||||
Quectel BG96 is a series of LTE Cat M1/Cat NB1/EGPRS module.
|
||||
config EXAMPLE_MODEM_DEVICE_SIM7600
|
||||
bool "SIM7600"
|
||||
help
|
||||
SIM7600 is Multi-Band LTE-TDD/LTE-FDD/HSPA+ and GSM/GPRS/EDGE module
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_MODEM_APN
|
||||
string "Set MODEM APN"
|
||||
default "internet"
|
||||
help
|
||||
Set APN (Access Point Name), a logical name to choose data network
|
||||
|
||||
config EXAMPLE_MODEM_PPP_AUTH_USERNAME
|
||||
string "Set username for authentication"
|
||||
default "espressif"
|
||||
depends on !EXAMPLE_MODEM_PPP_AUTH_NONE
|
||||
help
|
||||
Set username for PPP Authentication.
|
||||
|
||||
config EXAMPLE_MODEM_PPP_AUTH_PASSWORD
|
||||
string "Set password for authentication"
|
||||
default "esp32"
|
||||
depends on !EXAMPLE_MODEM_PPP_AUTH_NONE
|
||||
help
|
||||
Set password for PPP Authentication.
|
||||
|
||||
config EXAMPLE_MODEM_PPP_AUTH_NONE
|
||||
bool "Skip PPP authentication"
|
||||
default n
|
||||
help
|
||||
Set to true for the PPP client to skip authentication
|
||||
|
||||
config EXAMPLE_SEND_MSG
|
||||
bool "Short message (SMS)"
|
||||
default n
|
||||
help
|
||||
Select this, the modem will send a short message before power off.
|
||||
|
||||
if EXAMPLE_SEND_MSG
|
||||
config EXAMPLE_SEND_MSG_PEER_PHONE_NUMBER
|
||||
string "Peer Phone Number (with area code)"
|
||||
default "+8610086"
|
||||
help
|
||||
Enter the peer phone number that you want to send message to.
|
||||
endif
|
||||
|
||||
config EXAMPLE_NEED_SIM_PIN
|
||||
bool "SIM PIN needed"
|
||||
default n
|
||||
help
|
||||
Enable to set SIM PIN before starting the example
|
||||
|
||||
config EXAMPLE_SIM_PIN
|
||||
string "Set SIM PIN"
|
||||
default "1234"
|
||||
depends on EXAMPLE_NEED_SIM_PIN
|
||||
help
|
||||
Pin to unlock the SIM
|
||||
|
||||
menu "UART Configuration"
|
||||
config EXAMPLE_MODEM_UART_TX_PIN
|
||||
int "TXD Pin Number"
|
||||
default 25
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART TX.
|
||||
|
||||
config EXAMPLE_MODEM_UART_RX_PIN
|
||||
int "RXD Pin Number"
|
||||
default 26
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART RX.
|
||||
|
||||
config EXAMPLE_MODEM_UART_RTS_PIN
|
||||
int "RTS Pin Number"
|
||||
default 27
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART RTS.
|
||||
|
||||
config EXAMPLE_MODEM_UART_CTS_PIN
|
||||
int "CTS Pin Number"
|
||||
default 23
|
||||
range 0 31
|
||||
help
|
||||
Pin number of UART CTS.
|
||||
|
||||
config EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE
|
||||
int "UART Event Task Stack Size"
|
||||
range 2000 6000
|
||||
default 2048
|
||||
help
|
||||
Stack size of UART event task.
|
||||
|
||||
config EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY
|
||||
int "UART Event Task Priority"
|
||||
range 3 22
|
||||
default 5
|
||||
help
|
||||
Priority of UART event task.
|
||||
|
||||
config EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE
|
||||
int "UART Event Queue Size"
|
||||
range 10 40
|
||||
default 30
|
||||
help
|
||||
Length of UART event queue.
|
||||
|
||||
config EXAMPLE_MODEM_UART_PATTERN_QUEUE_SIZE
|
||||
int "UART Pattern Queue Size"
|
||||
range 10 40
|
||||
default 20
|
||||
help
|
||||
Length of UART pattern queue.
|
||||
|
||||
config EXAMPLE_MODEM_UART_TX_BUFFER_SIZE
|
||||
int "UART TX Buffer Size"
|
||||
range 256 2048
|
||||
default 512
|
||||
help
|
||||
Buffer size of UART TX buffer.
|
||||
|
||||
config EXAMPLE_MODEM_UART_RX_BUFFER_SIZE
|
||||
int "UART RX Buffer Size"
|
||||
range 256 2048
|
||||
default 1024
|
||||
help
|
||||
Buffer size of UART RX buffer.
|
||||
endmenu
|
||||
|
||||
endmenu
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* PPPoS Client Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <string>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ppp.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "sock_dce.hpp"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define BROKER_URL "mqtt.eclipseprojects.io"
|
||||
|
||||
static const char *TAG = "modem_client";
|
||||
static EventGroupHandle_t event_group = NULL;
|
||||
static const int CONNECT_BIT = BIT0;
|
||||
static const int GOT_DATA_BIT = BIT2;
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
|
||||
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
|
||||
esp_mqtt_client_handle_t client = event->client;
|
||||
int msg_id;
|
||||
switch ((esp_mqtt_event_id_t)event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
msg_id = esp_mqtt_client_subscribe(client, "/topic/esp-pppos", 0);
|
||||
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
msg_id = esp_mqtt_client_publish(client, "/topic/esp-pppos", "esp32-pppos", 0, 0, 0);
|
||||
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_UNSUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_PUBLISHED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
|
||||
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||||
printf("DATA=%.*s\r\n", event->data_len, event->data);
|
||||
xEventGroupSetBits(event_group, GOT_DATA_BIT);
|
||||
break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "MQTT other event id: %d", event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
|
||||
/* Init and register system/core components */
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
event_group = xEventGroupCreate();
|
||||
|
||||
/* Configure and create the UART DTE */
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
/* setup UART specific configuration based on kconfig options */
|
||||
dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN;
|
||||
dte_config.uart_config.rx_io_num = CONFIG_EXAMPLE_MODEM_UART_RX_PIN;
|
||||
dte_config.uart_config.rts_io_num = CONFIG_EXAMPLE_MODEM_UART_RTS_PIN;
|
||||
dte_config.uart_config.cts_io_num = CONFIG_EXAMPLE_MODEM_UART_CTS_PIN;
|
||||
dte_config.uart_config.rx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE;
|
||||
dte_config.uart_config.tx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_TX_BUFFER_SIZE;
|
||||
dte_config.uart_config.event_queue_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE;
|
||||
dte_config.task_stack_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE * 2;
|
||||
dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY;
|
||||
dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2;
|
||||
|
||||
auto dte = esp_modem::create_uart_dte(&dte_config);
|
||||
assert(dte);
|
||||
|
||||
/* Configure the DCE */
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN);
|
||||
|
||||
/* create the DCE and initialize network manually (using AT commands) */
|
||||
auto dce = sock_dce::create(&dce_config, std::move(dte));
|
||||
if (!dce->init_network()) {
|
||||
ESP_LOGE(TAG, "Failed to setup network");
|
||||
return;
|
||||
}
|
||||
|
||||
dce->init(8883);
|
||||
esp_mqtt_client_config_t mqtt_config = {};
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
mqtt_config.broker.address.uri = "mqtts://127.0.0.1";
|
||||
mqtt_config.session.message_retransmit_timeout = 10000;
|
||||
#else
|
||||
mqtt_config.uri = "mqtt://127.0.0.1";
|
||||
mqtt_config.message_retransmit_timeout = 10000;
|
||||
#endif
|
||||
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, NULL);
|
||||
esp_mqtt_client_start(mqtt_client);
|
||||
if (!dce->start(BROKER_URL, 8883)) {
|
||||
ESP_LOGE(TAG, "Failed to start DCE");
|
||||
return;
|
||||
}
|
||||
while (1) {
|
||||
while (dce->perform()) {
|
||||
ESP_LOGD(TAG, "...performing");
|
||||
}
|
||||
ESP_LOGE(TAG, "Loop exit.. retrying");
|
||||
// handle disconnections errors
|
||||
if (!dce->init_network()) {
|
||||
ESP_LOGE(TAG, "Failed to reinit network");
|
||||
return;
|
||||
}
|
||||
if (!dce->start("test.mosquitto.org", 1883)) {
|
||||
ESP_LOGI(TAG, "Network reinitialized, retrying");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include "sock_commands.hpp"
|
||||
#include "cxx_include/esp_modem_command_library_utils.hpp"
|
||||
|
||||
namespace sock_commands {
|
||||
|
||||
static const char *TAG = "sock_commands";
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
command_result net_open(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string response;
|
||||
auto ret = dce_commands::generic_get_string(term, "AT+NETOPEN?\r", response, 1000);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGV(TAG, "%s", response.data() );
|
||||
if (response.find("+NETOPEN: 1") != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Already there");
|
||||
return command_result::OK;
|
||||
} else if (response.find("+NETOPEN: 0") != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Need to setup");
|
||||
return dce_commands::generic_command(term, "AT+NETOPEN\r", "+NETOPEN: 1", "+NETOPEN: 0", 10000);
|
||||
}
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
command_result net_close(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return dce_commands::generic_command(term, "AT+NETCLOSE\r", "+NETCLOSE:", "ERROR", 30000);
|
||||
}
|
||||
|
||||
command_result tcp_open(CommandableIf *term, const std::string &host, int port, int timeout)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
auto ret = dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 50000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Setting Rx mode failed!");
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string ip_open = R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r";
|
||||
ret = dce_commands::generic_command(term, ip_open, "+CIPOPEN: 0,0", "ERROR", timeout);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "%s Failed", __func__ );
|
||||
return ret;
|
||||
}
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result tcp_close(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return dce_commands::generic_command(term, "AT+CIPCLOSE=0\r", "+CIPCLOSE:", "ERROR", 10000);
|
||||
}
|
||||
|
||||
command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string send = "AT+CIPSEND=0," + std::to_string(len) + "\r";
|
||||
auto ret = term->command(send, [&](uint8_t *data, size_t len) {
|
||||
std::string_view response((char *)data, len);
|
||||
ESP_LOGI(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
|
||||
if (response.find('>') != std::string::npos) {
|
||||
return command_result::OK;
|
||||
}
|
||||
return command_result::TIMEOUT;
|
||||
}, 50000, '>');
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
ret = command_result::TIMEOUT;
|
||||
ESP_LOGW(TAG, "Before setting...");
|
||||
term->on_read([&ret](uint8_t *cmd_data, size_t cmd_len) {
|
||||
std::string_view response((char *)cmd_data, cmd_len);
|
||||
ESP_LOGW(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
|
||||
|
||||
if (response.find("+CIPSEND:") != std::string::npos) {
|
||||
ret = command_result::OK;
|
||||
} else if (response.find("ERROR") != std::string::npos) {
|
||||
ret = command_result::FAIL;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
ESP_LOGW(TAG, "Before writing...");
|
||||
auto written = term->write(data, len);
|
||||
if (written != len) {
|
||||
ESP_LOGE(TAG, "written %d (%d)...", written, len);
|
||||
return command_result::FAIL;
|
||||
}
|
||||
uint8_t ctrl_z = '\x1A';
|
||||
term->write(&ctrl_z, 1);
|
||||
int count = 0;
|
||||
while (ret == command_result::TIMEOUT && count++ < 1000 ) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
term->on_read(nullptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string out;
|
||||
auto ret = dce_commands::generic_get_string(term, "AT+CIPRXGET=4,0\r", out);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
constexpr std::string_view pattern = "+CIPRXGET: 4,0,";
|
||||
if (out.find(pattern) == std::string::npos) {
|
||||
|
||||
return command_result::FAIL;
|
||||
}
|
||||
size_t data_len;
|
||||
if (std::from_chars(out.data() + pattern.size(), out.data() + out.size(), data_len).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, "size=%d", data_len);
|
||||
if (data_len == 0) {
|
||||
out_len = data_len;
|
||||
return command_result::OK;
|
||||
}
|
||||
return term->command("AT+CIPRXGET=2,0,100\r", [&](uint8_t *cmd_data, size_t cmd_len) {
|
||||
char pattern[] = "+CIPRXGET: 2,0,";
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, cmd_data, cmd_len, ESP_LOG_DEBUG);
|
||||
char *pos = strstr((char *)cmd_data, pattern);
|
||||
if (pos == nullptr) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
auto p1 = memchr(pos + sizeof(pattern) - 1, ',', 4);
|
||||
if (p1 == nullptr) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
*(char *)p1 = '\0';
|
||||
size_t actual_len = atoi(pos + sizeof(pattern) - 1);
|
||||
ESP_LOGD(TAG, "actual len=%d", actual_len);
|
||||
|
||||
pos = strchr((char *)p1 + 1, '\n');
|
||||
if (pos == nullptr) {
|
||||
ESP_LOGE(TAG, "not found");
|
||||
return command_result::FAIL;
|
||||
}
|
||||
if (actual_len > len) {
|
||||
ESP_LOGE(TAG, "TOO BIG");
|
||||
return command_result::FAIL;
|
||||
}
|
||||
out_len = actual_len;
|
||||
memcpy(data, pos + 1, actual_len);
|
||||
pos = strstr((char *)pos + 1 + actual_len, "OK");
|
||||
if (pos == nullptr) {
|
||||
ESP_LOGE(TAG, "ok NOT FOUND");
|
||||
return command_result::FAIL;
|
||||
}
|
||||
return command_result::OK;
|
||||
}, 50000);
|
||||
}
|
||||
|
||||
command_result get_ip(CommandableIf *term, std::string &ip)
|
||||
{
|
||||
std::string resp;
|
||||
auto ret = dce_commands::generic_get_string(term, "AT+IPADDR\r", resp, 5000);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
ip = resp;
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result set_rx_mode(CommandableIf *term, int mode)
|
||||
{
|
||||
return dce_commands::generic_command(term, "AT+CIPRXGET=" + std::to_string(mode) + "\r", "OK", "ERROR", 5000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "cxx_include/esp_modem_dce_module.hpp"
|
||||
#include "cxx_include/esp_modem_types.hpp"
|
||||
#include "socket_commands.inc"
|
||||
|
||||
namespace sock_commands {
|
||||
|
||||
//using namespace esp_modem;
|
||||
|
||||
#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \
|
||||
esp_modem::return_type name(esp_modem::CommandableIf *t, ## __VA_ARGS__);
|
||||
|
||||
DECLARE_SOCK_COMMANDS(declare name(Commandable *p, ...);)
|
||||
|
||||
#undef ESP_MODEM_DECLARE_DCE_COMMAND
|
||||
|
||||
}
|
483
components/esp_modem/examples/modem_tcp_client/main/sock_dce.cpp
Normal file
483
components/esp_modem/examples/modem_tcp_client/main/sock_dce.cpp
Normal file
@ -0,0 +1,483 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <charconv>
|
||||
#include <sys/socket.h>
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
|
||||
#include "sock_dce.hpp"
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
constexpr auto const *TAG = "sock_dce";
|
||||
|
||||
|
||||
bool DCE::perform()
|
||||
{
|
||||
if (listen_sock == -1) {
|
||||
ESP_LOGE(TAG, "Listening socket not ready");
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
if (sock == -1) { // no active socket, need to accept one first
|
||||
return accept_sock();
|
||||
}
|
||||
|
||||
// we have a socket, let's check the status
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 500000,
|
||||
};
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(sock, &fdset);
|
||||
FD_SET(data_ready_fd, &fdset);
|
||||
int s = select(std::max(sock, data_ready_fd) + 1, &fdset, nullptr, nullptr, &tv);
|
||||
if (s == 0) {
|
||||
ESP_LOGD(TAG, "perform select timeout...");
|
||||
return true;
|
||||
} else if (s < 0) {
|
||||
ESP_LOGE(TAG, "select error %d", errno);
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
if (FD_ISSET(sock, &fdset) && !sock_to_at()) {
|
||||
return false;
|
||||
}
|
||||
if (FD_ISSET(data_ready_fd, &fdset) && !at_to_sock()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCE::forwarding(uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
|
||||
if (state == status::SENDING) {
|
||||
switch (at.send(data, len)) {
|
||||
case Listener::state::OK:
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::FAIL:
|
||||
state = status::SENDING_FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::IN_PROGRESS:
|
||||
return;
|
||||
}
|
||||
} else if (state == status::RECEIVING || state == status::RECEIVING_1 ) {
|
||||
switch (at.recv(data, len)) {
|
||||
case Listener::state::OK:
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::FAIL:
|
||||
state = status::RECEIVING_FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::IN_PROGRESS:
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::string_view response((char *)data, len);
|
||||
at.check_async_replies(response);
|
||||
// Notification about Data Ready could come any time
|
||||
if (state == status::SENDING) {
|
||||
switch (at.send(response)) {
|
||||
case Listener::state::OK:
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::FAIL:
|
||||
state = status::SENDING_FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::IN_PROGRESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state == status::CONNECTING) {
|
||||
switch (at.connect(response)) {
|
||||
case Listener::state::OK:
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::FAIL:
|
||||
state = status::CONNECTION_FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Listener::state::IN_PROGRESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCE::close_sock()
|
||||
{
|
||||
if (sock > 0) {
|
||||
close(sock);
|
||||
sock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool DCE::at_to_sock()
|
||||
{
|
||||
uint64_t data;
|
||||
read(data_ready_fd, &data, sizeof(data));
|
||||
ESP_LOGD(TAG, "select read: modem data available %x", data);
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
if (state != status::IDLE) {
|
||||
ESP_LOGE(TAG, "Unexpected state %d", state);
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
state = status::RECEIVING;
|
||||
send_cmd("AT+CIPRXGET=2,0," + std::to_string(size) + "\r");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DCE::sock_to_at()
|
||||
{
|
||||
ESP_LOGD(TAG, "socket read: data available");
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
if (state != status::IDLE) {
|
||||
ESP_LOGE(TAG, "Unexpected state %d", state);
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
state = status::SENDING;
|
||||
int len = ::recv(sock, &buffer[0], size, 0);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "read error %d", errno);
|
||||
close_sock();
|
||||
return false;
|
||||
} else if (len == 0) {
|
||||
ESP_LOGE(TAG, "EOF %d", errno);
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, &buffer[0], len, ESP_LOG_VERBOSE);
|
||||
data_to_send = len;
|
||||
send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DCE::accept_sock()
|
||||
{
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 500000,
|
||||
};
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(listen_sock, &fdset);
|
||||
int s = select(listen_sock + 1, &fdset, nullptr, nullptr, &tv);
|
||||
if (s > 0 && FD_ISSET(listen_sock, &fdset)) {
|
||||
struct sockaddr_in source_addr = {};
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
|
||||
if (sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Socket accepted!");
|
||||
FD_ZERO(&fdset);
|
||||
return true;
|
||||
} else if (s == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCE::init(int port)
|
||||
{
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
esp_vfs_eventfd_register(&config);
|
||||
|
||||
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
assert(data_ready_fd > 0);
|
||||
|
||||
listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (listen_sock < 0) {
|
||||
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||
return;
|
||||
}
|
||||
int opt = 1;
|
||||
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
ESP_LOGI(TAG, "Socket created");
|
||||
struct sockaddr_in addr = { };
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
// inet_aton("127.0.0.1", &addr.sin_addr);
|
||||
|
||||
int err = bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Socket bound, port %d", 1883);
|
||||
err = listen(listen_sock, 1);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Listener::check_async_replies(std::string_view &response) const
|
||||
{
|
||||
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
|
||||
if (response.find("+CIPRXGET: 1") != std::string::npos) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
ESP_LOGD(TAG, "Got data on modem!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DCE::start(std::string host, int port)
|
||||
{
|
||||
dte->on_read(nullptr);
|
||||
tcp_close();
|
||||
if (set_rx_mode(1) != esp_modem::command_result::OK) {
|
||||
ESP_LOGE(TAG, "Unable to set Rx mode");
|
||||
return false;
|
||||
}
|
||||
dte->on_read([this](uint8_t *data, size_t len) {
|
||||
this->forwarding(data, len);
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
});
|
||||
send_cmd(R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
|
||||
state = status::CONNECTING;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DCE::init_network()
|
||||
{
|
||||
const int retries = 5;
|
||||
int i = 0;
|
||||
while (sync() != esp_modem::command_result::OK) {
|
||||
if (i++ > retries) {
|
||||
ESP_LOGE(TAG, "Failed to sync up");
|
||||
return false;
|
||||
}
|
||||
esp_modem::Task::Delay(1000);
|
||||
}
|
||||
ESP_LOGD(TAG, "Modem in sync");
|
||||
i = 0;
|
||||
while (setup_data_mode() != true) {
|
||||
if (i++ > retries) {
|
||||
ESP_LOGE(TAG, "Failed to setup pdp/data");
|
||||
return false;
|
||||
}
|
||||
esp_modem::Task::Delay(1000);
|
||||
}
|
||||
ESP_LOGD(TAG, "PDP configured");
|
||||
i = 0;
|
||||
while (net_open() != esp_modem::command_result::OK) {
|
||||
if (i++ > retries) {
|
||||
ESP_LOGE(TAG, "Failed to open network");
|
||||
return false;
|
||||
}
|
||||
esp_modem::Task::Delay(1000);
|
||||
}
|
||||
ESP_LOGD(TAG, "Network opened");
|
||||
i = 0;
|
||||
std::string ip_addr;
|
||||
while (get_ip(ip_addr) != esp_modem::command_result::OK) {
|
||||
if (i++ > retries) {
|
||||
ESP_LOGE(TAG, "Failed obtain an IP address");
|
||||
return false;
|
||||
}
|
||||
esp_modem::Task::Delay(5000);
|
||||
}
|
||||
ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
class Factory: public ::esp_modem::dce_factory::Factory {
|
||||
public:
|
||||
static std::unique_ptr<DCE> create(const esp_modem::dce_config *config, std::shared_ptr<esp_modem::DTE> dte)
|
||||
{
|
||||
return esp_modem::dce_factory::Factory::build_module_T<DCE, std::unique_ptr<DCE>>(config, std::move(dte));
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<DCE> create(const esp_modem::dce_config *config, std::shared_ptr<esp_modem::DTE> dte)
|
||||
{
|
||||
return Factory::create(config, std::move(dte));
|
||||
}
|
||||
|
||||
// Helper macros to handle multiple arguments of declared API
|
||||
#define ARGS0
|
||||
#define ARGS1 , p1
|
||||
#define ARGS2 , p1 , p2
|
||||
#define ARGS3 , p1 , p2 , p3
|
||||
|
||||
#define EXPAND_ARGS(x) ARGS ## x
|
||||
#define ARGS(x) EXPAND_ARGS(x)
|
||||
|
||||
//
|
||||
// Repeat all declarations and forward to the AT commands defined in ::sock_commands namespace
|
||||
//
|
||||
#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, arg_nr, ...) \
|
||||
esp_modem::return_type DCE::name(__VA_ARGS__) { return sock_commands::name(dte.get() ARGS(arg_nr) ); }
|
||||
|
||||
DECLARE_SOCK_COMMANDS(return_type name(...) )
|
||||
|
||||
#undef ESP_MODEM_DECLARE_DCE_COMMAND
|
||||
|
||||
|
||||
|
||||
|
||||
Listener::state Listener::recv(uint8_t *data, size_t len)
|
||||
{
|
||||
const int MIN_MESSAGE = 6;
|
||||
size_t actual_len = 0;
|
||||
auto *recv_data = (char *)data;
|
||||
if (data_to_recv == 0) {
|
||||
static constexpr std::string_view head = "+CIPRXGET: 2,0,";
|
||||
auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end());
|
||||
if (head_pos == nullptr) {
|
||||
return state::FAIL;
|
||||
}
|
||||
// state = status::RECEIVING_FAILED;
|
||||
// signal.set(IDLE);
|
||||
// return;
|
||||
// }
|
||||
if (head_pos - (char *)data > MIN_MESSAGE) {
|
||||
// check for async replies before the Recv header
|
||||
std::string_view response((char *)data, head_pos - (char *)data);
|
||||
check_async_replies(response);
|
||||
}
|
||||
|
||||
auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE);
|
||||
if (next_comma == nullptr) {
|
||||
return state::FAIL;
|
||||
}
|
||||
if (std::from_chars(head_pos + head.size(), next_comma, actual_len).ec == std::errc::invalid_argument) {
|
||||
ESP_LOGE(TAG, "cannot convert");
|
||||
return state::FAIL;
|
||||
}
|
||||
|
||||
auto next_nl = (char *)memchr(next_comma, '\n', 8 /* total_len size (~4) + markers */);
|
||||
if (next_nl == nullptr) {
|
||||
ESP_LOGE(TAG, "not found");
|
||||
return state::FAIL;
|
||||
}
|
||||
if (actual_len > size) {
|
||||
ESP_LOGE(TAG, "TOO BIG");
|
||||
return state::FAIL;
|
||||
}
|
||||
size_t total_len = 0;
|
||||
if (std::from_chars(next_comma + 1, next_nl - 1, total_len).ec == std::errc::invalid_argument) {
|
||||
ESP_LOGE(TAG, "cannot convert");
|
||||
return state::FAIL;
|
||||
}
|
||||
read_again = (total_len > 0);
|
||||
recv_data = next_nl + 1;
|
||||
auto first_data_len = len - (recv_data - (char *)data) /* minus size of the command marker */;
|
||||
if (actual_len > first_data_len) {
|
||||
::send(sock, recv_data, first_data_len, 0);
|
||||
data_to_recv = actual_len - first_data_len;
|
||||
return state::IN_PROGRESS;
|
||||
}
|
||||
::send(sock, recv_data, actual_len, 0);
|
||||
} else if (data_to_recv > len) { // continue sending
|
||||
::send(sock, recv_data, len, 0);
|
||||
data_to_recv -= len;
|
||||
return state::IN_PROGRESS;
|
||||
} else if (data_to_recv <= len) { // last read -> looking for "OK" marker
|
||||
::send(sock, recv_data, data_to_recv, 0);
|
||||
actual_len = data_to_recv;
|
||||
}
|
||||
|
||||
// "OK" after the data
|
||||
char *last_pos = nullptr;
|
||||
if (actual_len + 1 + 2 /* OK */ > len) {
|
||||
last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE);
|
||||
if (last_pos == nullptr || last_pos[1] != 'K') {
|
||||
data_to_recv = 0;
|
||||
return state::FAIL;
|
||||
}
|
||||
}
|
||||
if (last_pos != nullptr && (char *)data + len - last_pos > MIN_MESSAGE) {
|
||||
// check for async replies after the Recv header
|
||||
std::string_view response((char *)last_pos + 2 /* OK */, (char *)data + len - last_pos - 2);
|
||||
check_async_replies(response);
|
||||
}
|
||||
data_to_recv = 0;
|
||||
if (read_again) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
}
|
||||
return state::OK;
|
||||
}
|
||||
|
||||
Listener::state Listener::send(uint8_t *data, size_t len)
|
||||
{
|
||||
if (send_stat == 0) {
|
||||
if (memchr(data, '>', len) == NULL) {
|
||||
ESP_LOGE(TAG, "Missed >");
|
||||
return state::FAIL;
|
||||
}
|
||||
auto written = dte->write(&buffer[0], data_to_send);
|
||||
if (written != data_to_send) {
|
||||
ESP_LOGE(TAG, "written %d (%d)...", written, len);
|
||||
return state::FAIL;
|
||||
}
|
||||
data_to_send = 0;
|
||||
uint8_t ctrl_z = '\x1A';
|
||||
dte->write(&ctrl_z, 1);
|
||||
send_stat++;
|
||||
return state::IN_PROGRESS;
|
||||
}
|
||||
return Listener::state::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Listener::state Listener::send(std::string_view response)
|
||||
{
|
||||
if (send_stat == 1) {
|
||||
if (response.find("+CIPSEND:") != std::string::npos) {
|
||||
send_stat = 0;
|
||||
return state::OK;
|
||||
}
|
||||
if (response.find("ERROR") != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Failed to sent");
|
||||
send_stat = 0;
|
||||
return state::FAIL;
|
||||
}
|
||||
}
|
||||
return Listener::state::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Listener::state Listener::connect(std::string_view response)
|
||||
{
|
||||
if (response.find("+CIPOPEN: 0,0") != std::string::npos) {
|
||||
ESP_LOGI(TAG, "Connected!");
|
||||
return state::OK;
|
||||
}
|
||||
if (response.find("ERROR") != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Failed to open");
|
||||
return state::FAIL;
|
||||
}
|
||||
return Listener::state::IN_PROGRESS;
|
||||
}
|
||||
|
||||
|
||||
} // namespace sock_dce
|
102
components/esp_modem/examples/modem_tcp_client/main/sock_dce.hpp
Normal file
102
components/esp_modem/examples/modem_tcp_client/main/sock_dce.hpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include <cxx_include/esp_modem_dce_factory.hpp>
|
||||
#include "socket_commands.inc"
|
||||
#include "sock_commands.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
static constexpr size_t size = 512;
|
||||
|
||||
class Listener {
|
||||
public:
|
||||
enum class state {
|
||||
OK, FAIL, IN_PROGRESS
|
||||
};
|
||||
Listener(std::array<uint8_t, size> &b, int &s, int &ready_fd, std::shared_ptr<esp_modem::DTE> &dte_arg):
|
||||
buffer(b), sock(s), data_ready_fd(ready_fd), dte(dte_arg) {}
|
||||
state recv(uint8_t *data, size_t len);
|
||||
state send(uint8_t *data, size_t len);
|
||||
state send(std::string_view response);
|
||||
state connect(std::string_view response);
|
||||
void check_async_replies(std::string_view &response) const;
|
||||
private:
|
||||
std::array<uint8_t, size> &buffer;
|
||||
size_t data_to_recv = 0;
|
||||
bool read_again = false;
|
||||
int &sock;
|
||||
int &data_ready_fd;
|
||||
int send_stat = 0;
|
||||
size_t data_to_send = 0;
|
||||
std::shared_ptr<esp_modem::DTE> &dte;
|
||||
};
|
||||
|
||||
class DCE : public ::esp_modem::GenericModule {
|
||||
using esp_modem::GenericModule::GenericModule;
|
||||
public:
|
||||
|
||||
#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \
|
||||
esp_modem::return_type name(__VA_ARGS__);
|
||||
|
||||
DECLARE_SOCK_COMMANDS(declare name(Commandable *p, ...);)
|
||||
|
||||
#undef ESP_MODEM_DECLARE_DCE_COMMAND
|
||||
|
||||
bool init_network();
|
||||
bool start(std::string host, int port);
|
||||
|
||||
void init(int port);
|
||||
|
||||
bool perform();
|
||||
|
||||
private:
|
||||
esp_modem::SignalGroup signal;
|
||||
|
||||
void close_sock();
|
||||
bool accept_sock();
|
||||
bool sock_to_at();
|
||||
bool at_to_sock();
|
||||
|
||||
void forwarding(uint8_t *data, size_t len);
|
||||
|
||||
// void check_async_replies(std::string_view &response) const;
|
||||
|
||||
void send_cmd(std::string_view command)
|
||||
{
|
||||
dte->write((uint8_t *) command.begin(), command.size());
|
||||
}
|
||||
|
||||
enum class status {
|
||||
IDLE,
|
||||
CONNECTING,
|
||||
CONNECTION_FAILED,
|
||||
SENDING,
|
||||
SENDING_1,
|
||||
SENDING_FAILED,
|
||||
RECEIVING,
|
||||
RECEIVING_1,
|
||||
RECEIVING_FAILED
|
||||
};
|
||||
status state{status::IDLE};
|
||||
static constexpr uint8_t IDLE = 1;
|
||||
std::array<uint8_t, size> buffer;
|
||||
Listener at{buffer, sock, data_ready_fd, dte};
|
||||
size_t data_to_send = 0;
|
||||
// size_t data_to_recv = 0;
|
||||
bool read_again = false;
|
||||
int sock {-1};
|
||||
int listen_sock {-1};
|
||||
int data_ready_fd {-1};
|
||||
};
|
||||
|
||||
std::unique_ptr<DCE> create(const esp_modem::dce_config *config, std::shared_ptr<esp_modem::DTE> dte);
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
// Copyright 2021-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
|
||||
|
||||
#include "generate/esp_modem_command_declare_helper.inc"
|
||||
|
||||
#define DECLARE_SOCK_COMMANDS(...) \
|
||||
/**
|
||||
* @brief Opens network in AT command mode
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(net_open, command_result, 0) \
|
||||
\
|
||||
/**
|
||||
* @brief Closes network in AT command mode
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(net_close, command_result, 0) \
|
||||
\
|
||||
/**
|
||||
* @brief Opens a TCP connection
|
||||
* @param[in] host Host name or IP address to connect to
|
||||
* @param[in] port Port number
|
||||
* @param[in] timeout Connection timeout
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(tcp_open, command_result, 3, STRING_IN(p1, host), INT_IN(p2, port), INT_IN(p3, timeout)) \
|
||||
\
|
||||
/**
|
||||
* @brief Closes opened TCP socket
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(tcp_close, command_result, 0) \
|
||||
\
|
||||
/**
|
||||
* @brief Gets modem IP address
|
||||
* @param[out] addr String representation of modem's IP
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(get_ip, command_result, 1, STRING_OUT(p1, addr)) \
|
||||
\
|
||||
/**
|
||||
* @brief Sets Rx mode
|
||||
* @param[in] mode 0=auto, 1=manual
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_rx_mode, command_result, 1, INT_IN(p1, mode))
|
@ -1,6 +1,7 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
// Parameters
|
||||
// * handle different parameters for C++ and C API
|
||||
// * make parameter unique names, so they could be easily referenced and forwarded
|
||||
#define _ARG(param, name) param
|
||||
#define INT_IN(param, name) int _ARG(param, name)
|
||||
#ifdef __cplusplus
|
||||
#include <string>
|
||||
#define STRING_IN(param, name) const std::string& _ARG(param, name)
|
||||
#define STRING_OUT(param, name) std::string& _ARG(param, name)
|
||||
#define BOOL_IN(param, name) const bool _ARG(param, name)
|
||||
#define BOOL_OUT(param, name) bool& _ARG(param, name)
|
||||
#define INT_OUT(param, name) int& _ARG(param, name)
|
||||
#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name)
|
||||
|
||||
#define STRUCT_OUT(struct_name, p1) struct_name& p1
|
||||
#else
|
||||
#define STRING_IN(param, name) const char* _ARG(param, name)
|
||||
#define STRING_OUT(param, name) char* _ARG(param, name)
|
||||
#define BOOL_IN(param, name) const bool _ARG(param, name)
|
||||
#define BOOL_OUT(param, name) bool* _ARG(param, name)
|
||||
#define INT_OUT(param, name) int* _ARG(param, name)
|
||||
#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name)
|
||||
#define STRUCT_OUT(struct_name, p1) esp_modem_ ## struct_name ## _t* p1
|
||||
#endif
|
Reference in New Issue
Block a user