diff --git a/esp_modem/CMakeLists.txt b/esp_modem/CMakeLists.txt index 48235631f..c98b05dcf 100644 --- a/esp_modem/CMakeLists.txt +++ b/esp_modem/CMakeLists.txt @@ -7,7 +7,8 @@ set(srcs "src/esp_modem.c" "src/esp_modem_recov_helper.c" "src/esp_sim800.c" "src/esp_sim7600.c" - "src/esp_bg96.c") + "src/esp_bg96.c" + "src/esp_modem_dte.cpp") set(include_dirs "include") @@ -20,3 +21,5 @@ idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS private_include REQUIRES driver) + +target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17) \ No newline at end of file diff --git a/esp_modem/examples/modem_console/main/modem_console_main.c b/esp_modem/examples/modem_console/main/modem_console_main.c index 6a2fe5610..1e8f51d3b 100644 --- a/esp_modem/examples/modem_console/main/modem_console_main.c +++ b/esp_modem/examples/modem_console/main/modem_console_main.c @@ -277,7 +277,9 @@ void app_main(void) // init the DTE esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); + dte_config.pattern_queue_size = 100; dte_config.event_task_stack_size = 4096; + dte_config.event_task_priority = 15; esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("internet"); dce_config.populate_command_list = true; esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP(); diff --git a/esp_modem/examples/simple_cxx_client/CMakeLists.txt b/esp_modem/examples/simple_cxx_client/CMakeLists.txt new file mode 100644 index 000000000..ccd2e1433 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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) + +set(EXTRA_COMPONENT_DIRS "../..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(simple_cxx_client) + + diff --git a/esp_modem/examples/simple_cxx_client/Makefile b/esp_modem/examples/simple_cxx_client/Makefile new file mode 100644 index 000000000..25571d087 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := pppos_client + +include $(IDF_PATH)/make/project.mk + diff --git a/esp_modem/examples/simple_cxx_client/README.md b/esp_modem/examples/simple_cxx_client/README.md new file mode 100644 index 000000000..376f37eb8 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/README.md @@ -0,0 +1,10 @@ +# PPPoS simple client example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview +This example shows how to act as a MQTT client after the PPPoS channel created by using [ESP-MQTT](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/mqtt.html) APIs. + +## How to use this example + +See the README.md file in the upper level `pppos` directory for more information about the PPPoS examples. diff --git a/esp_modem/examples/simple_cxx_client/main/CMakeLists.txt b/esp_modem/examples/simple_cxx_client/main/CMakeLists.txt new file mode 100644 index 000000000..2d88e9d93 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "simple_client.cpp" + INCLUDE_DIRS ".") diff --git a/esp_modem/examples/simple_cxx_client/main/Kconfig.projbuild b/esp_modem/examples/simple_cxx_client/main/Kconfig.projbuild new file mode 100644 index 000000000..68b4e2048 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/main/Kconfig.projbuild @@ -0,0 +1,143 @@ +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_PPP_APN + string "Set MODEM APN" + default "internet" + help + Set APN (Access Point Name), a logical name to choose data network + + config EXAMPLE_MODEM_LEGACY_API + bool "Use Modem legacy API" + default y + select MODEM_LEGACY_API + help + Set this to true to use backward compatible API to the original modem + component in example/protocol folder in IDFv4.2 and below + + 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 + + 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 diff --git a/esp_modem/examples/simple_cxx_client/main/simple_client.cpp b/esp_modem/examples/simple_cxx_client/main/simple_client.cpp new file mode 100644 index 000000000..0d1d5a1ef --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/main/simple_client.cpp @@ -0,0 +1,347 @@ +/* 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 +#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.h" +#include "esp_modem_netif.h" +#include "esp_log.h" +#include "cxx_include/esp_modem_dte.hpp" +#include "cxx_include/uart_terminal.hpp" + +#define BROKER_URL "mqtt://mqtt.eclipse.org" + +static const char *TAG = "pppos_example"; +static EventGroupHandle_t event_group = NULL; +static const int CONNECT_BIT = BIT0; +static const int STOP_BIT = BIT1; +static const int GOT_DATA_BIT = BIT2; + +#if CONFIG_EXAMPLE_SEND_MSG +/** + * @brief This example will also show how to send short message using the infrastructure provided by esp modem library. + * @note Not all modem support SMG. + * + */ +static esp_err_t example_default_handle(esp_modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +static esp_err_t example_handle_cmgs(esp_modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CMGS", strlen("+CMGS"))) { + err = ESP_OK; + } + return err; +} + +#define MODEM_SMS_MAX_LENGTH (128) +#define MODEM_COMMAND_TIMEOUT_SMS_MS (120000) +#define MODEM_PROMPT_TIMEOUT_MS (10) + +static esp_err_t example_send_message_text(modem_dce_t *user_dce, const char *phone_num, const char *text) +{ + esp_modem_dce_t *dce = &user_dce->parent; + modem_dte_t *dte = dce->dte; + dce->handle_line = example_default_handle; + /* Set text mode */ + if (dte->send_cmd(dte, "AT+CMGF=1\r", MODEM_COMMAND_TIMEOUT_DEFAULT) != ESP_OK) { + ESP_LOGE(TAG, "send command failed"); + goto err; + } + if (dce->state != MODEM_STATE_SUCCESS) { + ESP_LOGE(TAG, "set message format failed"); + goto err; + } + ESP_LOGD(TAG, "set message format ok"); + /* Specify character set */ + dce->handle_line = example_default_handle; + if (dte->send_cmd(dte, "AT+CSCS=\"GSM\"\r", MODEM_COMMAND_TIMEOUT_DEFAULT) != ESP_OK) { + ESP_LOGE(TAG, "send command failed"); + goto err; + } + if (dce->state != MODEM_STATE_SUCCESS) { + ESP_LOGE(TAG, "set character set failed"); + goto err; + } + ESP_LOGD(TAG, "set character set ok"); + /* send message */ + char command[MODEM_SMS_MAX_LENGTH] = {0}; + int length = snprintf(command, MODEM_SMS_MAX_LENGTH, "AT+CMGS=\"%s\"\r", phone_num); + /* set phone number and wait for "> " */ + dte->send_wait(dte, command, length, "\r\n> ", MODEM_PROMPT_TIMEOUT_MS); + /* end with CTRL+Z */ + snprintf(command, MODEM_SMS_MAX_LENGTH, "%s\x1A", text); + dce->handle_line = example_handle_cmgs; + if (dte->send_cmd(dte, command, MODEM_COMMAND_TIMEOUT_SMS_MS) != ESP_OK) { + ESP_LOGE(TAG, "send command failed"); + goto err; + } + if (dce->state != MODEM_STATE_SUCCESS) { + ESP_LOGE(TAG, "send message failed"); + goto err; + } + ESP_LOGD(TAG, "send message ok"); + return ESP_OK; +err: + return ESP_FAIL; +} +#endif + +static void modem_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + switch (event_id) { + case ESP_MODEM_EVENT_PPP_START: + ESP_LOGI(TAG, "Modem PPP Started"); + break; + case ESP_MODEM_EVENT_PPP_STOP: + ESP_LOGI(TAG, "Modem PPP Stopped"); + xEventGroupSetBits(event_group, STOP_BIT); + break; + case ESP_MODEM_EVENT_UNKNOWN: + ESP_LOGW(TAG, "Unknown line received: %s", (char *)event_data); + break; + default: + break; + } +} + +//static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + 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 (event->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; + } +} + +static void on_ppp_changed(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "PPP state changed event %d", event_id); + if (event_id == NETIF_PPP_ERRORUSER) { + /* User interrupted event from esp-netif */ + esp_netif_t *netif = (esp_netif_t *)event_data; + ESP_LOGI(TAG, "User interrupted event from netif:%p", netif); + } +} + + +static void on_ip_event(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "IP event! %d", event_id); + if (event_id == IP_EVENT_PPP_GOT_IP) { + esp_netif_dns_info_t dns_info; + + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + esp_netif_t *netif = event->esp_netif; + + ESP_LOGI(TAG, "Modem Connect to PPP Server"); + ESP_LOGI(TAG, "~~~~~~~~~~~~~~"); + ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip)); + ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask)); + ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw)); + esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_info); + ESP_LOGI(TAG, "Name Server1: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4)); + esp_netif_get_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_info); + ESP_LOGI(TAG, "Name Server2: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4)); + ESP_LOGI(TAG, "~~~~~~~~~~~~~~"); + xEventGroupSetBits(event_group, CONNECT_BIT); + + ESP_LOGI(TAG, "GOT ip event!!!"); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Modem Disconnect from PPP Server"); + } else if (event_id == IP_EVENT_GOT_IP6) { + ESP_LOGI(TAG, "GOT IPv6 event!"); + + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + ESP_LOGI(TAG, "Got IPv6 address " IPV6STR, IPV62STR(event->ip6_info.ip)); + } +} + + +static void modem_test_app(esp_modem_dte_config_t *dte_config, esp_modem_dce_config_t *dce_config, esp_netif_config_t *ppp_config); + +extern "C" void app_main(void) +{ + + /* Init and register system/core components */ + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL)); + + event_group = xEventGroupCreate(); + + /* Configure the DTE */ + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); + /* setup UART specific configuration based on kconfig options */ + dte_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN; + dte_config.rx_io_num = CONFIG_EXAMPLE_MODEM_UART_RX_PIN; + dte_config.rts_io_num = CONFIG_EXAMPLE_MODEM_UART_RTS_PIN; + dte_config.cts_io_num = CONFIG_EXAMPLE_MODEM_UART_CTS_PIN; + dte_config.rx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE; + dte_config.tx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_TX_BUFFER_SIZE; + dte_config.pattern_queue_size = CONFIG_EXAMPLE_MODEM_UART_PATTERN_QUEUE_SIZE; + dte_config.event_queue_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE; + dte_config.event_task_stack_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE; + dte_config.event_task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY; + dte_config.line_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2; + + /* Configure the DCE */ + esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN); + + /* Configure the PPP netif */ + esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP(); + + /* Run the modem demo app */ + return modem_test_app(&dte_config, &dce_config,&netif_ppp_config); +} + +#include + +static void modem_test_app(esp_modem_dte_config_t *dte_config, esp_modem_dce_config_t *dce_config, esp_netif_config_t *ppp_config) +{ + /* create dte object */ + esp_modem_dte_t *dte; // = esp_modem_dte_new(dte_config); +// assert(dte != NULL); +// dte_config->line_buffer_size = 1000000; + uint8_t data[32] = {}; + int actual_len = 0; + auto ddd = create_dte(dte_config); + ddd->set_mode(dte_mode::UNDEF); + ddd->send_command("AT\r", [&](uint8_t *data, size_t len) { + std::string response((char*)data, len); + ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data); + std::cout << response << std::endl; + return true; + }, 1000); + +// auto uart = create_uart_terminal(dte_config); +// uart->set_data_cb([&](size_t len){ +// actual_len = uart->read(data, 32); +// ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)data); +// }); +// uart->write((uint8_t*)"AT\r",3); + + vTaskDelay(pdMS_TO_TICKS(1000)); +// int len = uart->read(data, 32); +// ESP_LOGI(TAG, "len=%d data %s", len, (char*)data); +// vTaskDelay(pdMS_TO_TICKS(1000)); +// len = uart->read(data, 32); + ESP_LOGI(TAG, "len=%d data %s", actual_len, (char*)data); + return; + + /* create dce object */ +#if CONFIG_EXAMPLE_MODEM_DEVICE_SIM800 + dce_config->device = ESP_MODEM_DEVICE_SIM800; +#elif CONFIG_EXAMPLE_MODEM_DEVICE_BG96 + dce_config->device = ESP_MODEM_DEVICE_BG96; +#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 + dce_config->device = ESP_MODEM_DEVICE_SIM7600; +#else +#error "Unsupported DCE" +#endif + esp_modem_dce_t *dce = esp_modem_dce_new(dce_config); + assert(dce != NULL); + + /* create netif object */ + esp_netif_t *esp_netif = esp_netif_new(ppp_config); + assert(esp_netif); +#if !defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_NONE) && (defined(CONFIG_LWIP_PPP_PAP_SUPPORT) || defined(CONFIG_LWIP_PPP_CHAP_SUPPORT)) +#if CONFIG_LWIP_PPP_PAP_SUPPORT + esp_netif_auth_type_t auth_type = NETIF_PPP_AUTHTYPE_PAP; +#elif CONFIG_LWIP_PPP_CHAP_SUPPORT + esp_netif_auth_type_t auth_type = NETIF_PPP_AUTHTYPE_CHAP; +#else +#error "Unsupported AUTH Negotiation" +#endif + esp_netif_ppp_set_auth(esp_netif, auth_type, CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME, CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD); +#endif + /* Register event handler */ + ESP_ERROR_CHECK(esp_modem_set_event_handler(dte, modem_event_handler, ESP_EVENT_ANY_ID, NULL)); + + /* attach the DCE, DTE, netif to initialize the modem */ + ESP_ERROR_CHECK(esp_modem_default_attach(dte, dce, esp_netif)); + + while (1) { + ESP_ERROR_CHECK(esp_modem_default_start(dte)); + /* Start PPP mode */ + ESP_ERROR_CHECK(esp_modem_start_ppp(dte)); + /* Wait for IP address */ + xEventGroupWaitBits(event_group, CONNECT_BIT, pdTRUE, pdTRUE, portMAX_DELAY); + + /* Config MQTT */ + esp_mqtt_client_config_t mqtt_config = { }; + mqtt_config.uri = BROKER_URL; + esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config); + esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, NULL); + esp_mqtt_client_start(mqtt_client); + xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdTRUE, portMAX_DELAY); + esp_mqtt_client_destroy(mqtt_client); + + /* Exit PPP mode */ + ESP_ERROR_CHECK(esp_modem_stop_ppp(dte)); + + xEventGroupWaitBits(event_group, STOP_BIT, pdTRUE, pdTRUE, portMAX_DELAY); + ESP_LOGI(TAG, "Restart after 60 seconds"); + vTaskDelay(pdMS_TO_TICKS(60000)); + } + + /* Default destroy all modem sub-units attached to it (DTE, DCE, netif) */ + ESP_ERROR_CHECK(esp_modem_default_destroy(dte)); +} diff --git a/esp_modem/examples/simple_cxx_client/sdkconfig.defaults b/esp_modem/examples/simple_cxx_client/sdkconfig.defaults new file mode 100644 index 000000000..6c519dc85 --- /dev/null +++ b/esp_modem/examples/simple_cxx_client/sdkconfig.defaults @@ -0,0 +1,8 @@ +# Override some defaults to enable PPP +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT=y +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096 +# Do not enable IPV6 in dte<->dce link local +CONFIG_LWIP_PPP_ENABLE_IPV6=n +CONFIG_COMPILER_CXX_EXCEPTIONS=y \ No newline at end of file diff --git a/esp_modem/include/cxx_include/esp_modem_dte.hpp b/esp_modem/include/cxx_include/esp_modem_dte.hpp new file mode 100644 index 000000000..d25586030 --- /dev/null +++ b/esp_modem/include/cxx_include/esp_modem_dte.hpp @@ -0,0 +1,86 @@ +#ifndef SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_HPP +#define SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_HPP + +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "terminal_objects.hpp" + + + +enum class terminal_error { + BUFFER_OVERFLOW, + CHECKSUM_ERROR, + UNEXPECTED_CONTROL_FLOW, +}; + +class terminal { +public: + virtual ~terminal() = default; + void set_data_cb(std::function f) { on_data = std::move(f); } + void set_error_cb(std::function f) { on_error = std::move(f); } + virtual int write(uint8_t *data, size_t len) = 0; + virtual int read(uint8_t *data, size_t len) = 0; + virtual void start() = 0; + virtual void stop() = 0; + +protected: + std::function on_data; + std::function on_error; +}; + +class dte_adapter: public terminal { +public: + dte_adapter(std::unique_ptr terminal): + original_dte(std::move(terminal)) {} + ~dte_adapter() override = default; + int write(uint8_t *data, size_t len) override { return original_dte->write(data, len); } + int read(uint8_t *data, size_t len) override { return original_dte->read(data, len); } +private: + std::unique_ptr original_dte; +}; + + +enum class dte_mode { + UNDEF, + COMMAND_MODE, + DATA_MODE +}; + +const int DTE_BUFFER_SIZE = 1024; + +struct dte_data { + uint8_t *data; + size_t len; +}; + +typedef std::function got_line_cb; + +class dte { +public: + explicit dte(std::unique_ptr terminal); + ~dte() = default; +// void set_line_cb(got_line f) { on_line_cb = std::move(f); } + + void set_mode(dte_mode m) { term->start(); mode = m; } + bool send_command(const std::string& command, got_line_cb got_line, uint32_t time_ms); + +private: + const size_t GOT_LINE = BIT0; + size_t buffer_size; + size_t consumed; + std::unique_ptr buffer; + std::unique_ptr term; + got_line_cb on_line; + dte_mode mode; + signal_group signal; +}; + + + + +#endif //SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_HPP diff --git a/esp_modem/include/cxx_include/terminal_objects.hpp b/esp_modem/include/cxx_include/terminal_objects.hpp new file mode 100644 index 000000000..a218c7ece --- /dev/null +++ b/esp_modem/include/cxx_include/terminal_objects.hpp @@ -0,0 +1,84 @@ +// +// Created by david on 2/26/21. +// + +#ifndef SIMPLE_CXX_CLIENT_TERMINAL_OBJECTS_HPP +#define SIMPLE_CXX_CLIENT_TERMINAL_OBJECTS_HPP +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + + +class esp_err_exception: virtual public std::exception { +public: + explicit esp_err_exception(esp_err_t err): esp_err(err) {} + explicit esp_err_exception(std::string msg): esp_err(ESP_FAIL), message(std::move(msg)) {} + explicit esp_err_exception(std::string msg, esp_err_t err): esp_err(err), message(std::move(msg)) {} + virtual esp_err_t get_err_t() { return esp_err; } + ~esp_err_exception() noexcept override = default; + virtual const char* what() const noexcept { + return message.c_str(); + } +private: + esp_err_t esp_err; + std::string message; +}; + +static inline void throw_if_false(bool condition, std::string message) +{ + if (!condition) { + throw(esp_err_exception(std::move(message))); + } +} + +static inline void throw_if_esp_fail(esp_err_t err, std::string message) +{ + if (err != ESP_OK) { + throw(esp_err_exception(std::move(message), err)); + } +} + +static inline void throw_if_esp_fail(esp_err_t err) +{ + if (err != ESP_OK) { + throw(esp_err_exception(err)); + } +} + +struct signal_group { + explicit signal_group(): event_group(nullptr) + { + event_group = xEventGroupCreate(); + throw_if_false(event_group != nullptr, "create signal event group failed"); + } + + void set(uint32_t bits) + { + xEventGroupSetBits(event_group, bits); + } + + bool wait(uint32_t flags, uint32_t time_ms) // waiting for all and clearing if set + { + EventBits_t bits = xEventGroupWaitBits(event_group, flags, pdTRUE, pdTRUE, pdMS_TO_TICKS(time_ms)); + return bits & flags; + } + + bool is_any(uint32_t flags) + { + return xEventGroupGetBits(event_group) & flags; + } + + bool wait_any(uint32_t flags, uint32_t time_ms) // waiting for any bit, not clearing them + { + EventBits_t bits = xEventGroupWaitBits(event_group, flags, pdFALSE, pdFALSE, pdMS_TO_TICKS(time_ms)); + return bits & flags; + } + + ~signal_group() + { + if (event_group) vEventGroupDelete(event_group); + } + + EventGroupHandle_t event_group; +}; + +#endif //SIMPLE_CXX_CLIENT_TERMINAL_OBJECTS_HPP diff --git a/esp_modem/include/cxx_include/uart_terminal.hpp b/esp_modem/include/cxx_include/uart_terminal.hpp new file mode 100644 index 000000000..eeed20345 --- /dev/null +++ b/esp_modem/include/cxx_include/uart_terminal.hpp @@ -0,0 +1,17 @@ +// +// Created by david on 2/25/21. +// + +#ifndef SIMPLE_CXX_CLIENT_UART_TERMINAL_HPP +#define SIMPLE_CXX_CLIENT_UART_TERMINAL_HPP + +#include "cxx_include/esp_modem_dte.hpp" +#include "esp_modem_dte_config.h" + +std::unique_ptr create_uart_terminal(const struct dte_config *config); + +class dte; + +std::unique_ptr create_dte(const struct dte_config *config); + +#endif //SIMPLE_CXX_CLIENT_UART_TERMINAL_HPP diff --git a/esp_modem/include/esp_modem.h b/esp_modem/include/esp_modem.h index b00177b8f..c1b4725ec 100644 --- a/esp_modem/include/esp_modem.h +++ b/esp_modem/include/esp_modem.h @@ -19,6 +19,7 @@ extern "C" { #include "esp_event.h" #include "driver/uart.h" +#include "esp_modem_dte_config.h" /** * @brief Forward declare DTE and DCE objects @@ -52,21 +53,15 @@ typedef enum { * @{ */ -/** - * @brief Modem flow control type - * - */ -typedef enum { - ESP_MODEM_FLOW_CONTROL_NONE = 0, - ESP_MODEM_FLOW_CONTROL_SW, - ESP_MODEM_FLOW_CONTROL_HW -} esp_modem_flow_ctrl_t; + /** * @brief ESP Modem DTE Configuration * */ -typedef struct { +typedef struct dte_config +#if 0 +{ uart_port_t port_num; /*!< UART port number */ uart_word_length_t data_bits; /*!< Data bits of UART */ uart_stop_bits_t stop_bits; /*!< Stop bits of UART */ @@ -84,7 +79,9 @@ typedef struct { uint32_t event_task_stack_size; /*!< UART Event Task Stack size */ int event_task_priority; /*!< UART Event Task Priority */ int line_buffer_size; /*!< Line buffer size for command mode */ -} esp_modem_dte_config_t; +} +#endif +esp_modem_dte_config_t; /** * @brief ESP Modem DTE Default Configuration diff --git a/esp_modem/include/esp_modem_dte_config.h b/esp_modem/include/esp_modem_dte_config.h new file mode 100644 index 000000000..b93ad12b4 --- /dev/null +++ b/esp_modem/include/esp_modem_dte_config.h @@ -0,0 +1,39 @@ +// +// Created by david on 2/24/21. +// + +#ifndef SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_CONFIG_H +#define SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_CONFIG_H + +/** + * @brief Modem flow control type + * + */ +typedef enum { + ESP_MODEM_FLOW_CONTROL_NONE = 0, + ESP_MODEM_FLOW_CONTROL_SW, + ESP_MODEM_FLOW_CONTROL_HW +} esp_modem_flow_ctrl_t; + +struct dte_config { + uart_port_t port_num; /*!< UART port number */ + uart_word_length_t data_bits; /*!< Data bits of UART */ + uart_stop_bits_t stop_bits; /*!< Stop bits of UART */ + uart_parity_t parity; /*!< Parity type */ + esp_modem_flow_ctrl_t flow_control; /*!< Flow control type */ + int baud_rate; /*!< Communication baud rate */ + int tx_io_num; /*!< TXD Pin Number */ + int rx_io_num; /*!< RXD Pin Number */ + int rts_io_num; /*!< RTS Pin Number */ + int cts_io_num; /*!< CTS Pin Number */ + int rx_buffer_size; /*!< UART RX Buffer Size */ + int tx_buffer_size; /*!< UART TX Buffer Size */ + int pattern_queue_size; /*!< UART Pattern Queue Size */ + int event_queue_size; /*!< UART Event Queue Size */ + uint32_t event_task_stack_size; /*!< UART Event Task Stack size */ + int event_task_priority; /*!< UART Event Task Priority */ + int line_buffer_size; /*!< Line buffer size for command mode */ +}; + + +#endif //SIMPLE_CXX_CLIENT_ESP_MODEM_DTE_CONFIG_H diff --git a/esp_modem/src/esp_modem_dte.cpp b/esp_modem/src/esp_modem_dte.cpp new file mode 100644 index 000000000..695b6384c --- /dev/null +++ b/esp_modem/src/esp_modem_dte.cpp @@ -0,0 +1,334 @@ +// +// Created by david on 2/24/21. +// + +#include "cxx_include/esp_modem_dte.hpp" +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_modem.h" +#include "esp_modem_dce.h" +#include "esp_log.h" +#include "sdkconfig.h" +#include "esp_modem_internal.h" +#include "esp_modem_dte_internal.h" +#include "esp_modem_dte_config.h" +#include +#include + +#define ESP_MODEM_EVENT_QUEUE_SIZE (16) +#define MIN_PATTERN_INTERVAL (9) +#define MIN_POST_IDLE (0) +#define MIN_PRE_IDLE (0) + + +static const char *TAG = "dte_uart"; + +//class uart_terminal; + +//class dte { +//public: +// esp_err_t init(std::unique_ptr t); +// +//private: +// std::unique_ptr m_terminal; +// +//}; + +struct uart_task { + explicit uart_task(size_t stack_size, size_t priority, void* task_param, TaskFunction_t task_function): + task_handle(nullptr) + { + BaseType_t ret = xTaskCreate(task_function, "uart_task", stack_size, task_param, priority, &task_handle); + throw_if_false(ret == pdTRUE, "create uart event task failed"); + } + ~uart_task() + { + if (task_handle) vTaskDelete(task_handle); + } + + TaskHandle_t task_handle; /*!< UART event task handle */ +}; + + + +struct uart_event_loop { + explicit uart_event_loop(): event_loop_hdl(nullptr) + { + esp_event_loop_args_t loop_args = {}; + loop_args.queue_size = ESP_MODEM_EVENT_QUEUE_SIZE; + loop_args.task_name = nullptr; + throw_if_esp_fail(esp_event_loop_create(&loop_args, &event_loop_hdl), "create event loop failed"); + } + void run() { esp_event_loop_run(event_loop_hdl, pdMS_TO_TICKS(0)); } + + ~uart_event_loop() { if (event_loop_hdl) esp_event_loop_delete(event_loop_hdl); } + + esp_event_loop_handle_t event_loop_hdl; +}; + +struct uart_resource { + explicit uart_resource(const struct dte_config *config); + + ~uart_resource(); + + bool get_event(uart_event_t& event, uint32_t time_ms) + { + return xQueueReceive(event_queue, &event, pdMS_TO_TICKS(time_ms)); + } + void reset_events() + { + uart_flush_input(port); + xQueueReset(event_queue); + } + + uart_port_t port; /*!< UART port */ + QueueHandle_t event_queue; /*!< UART event queue handle */ +// esp_modem_on_receive receive_cb; /*!< ptr to data reception */ +// void *receive_cb_ctx; /*!< ptr to rx fn context data */ + int line_buffer_size; /*!< line buffer size in command mode */ + int pattern_queue_size; /*!< UART pattern queue size */ +}; + +class uart_terminal: public terminal { +public: + explicit uart_terminal(const struct dte_config *config): + uart(config), event_loop(), signal(), + task_handle(config->event_task_stack_size, config->event_task_priority, this, s_task) {} + + ~uart_terminal() override = default; + void start() override + { + signal.set(TASK_START); + } + void stop() override + { + signal.set(TASK_STOP); + } +// { ESP_LOGE(TAG, "uart_terminal destruct"); } + + int write(uint8_t *data, size_t len) override; + int read(uint8_t *data, size_t len) override; + +private: + static void s_task(void * task_param) + { + auto t = static_cast(task_param); + t->task(); + vTaskDelete(NULL); + } + void task(); + + const size_t TASK_INIT = BIT0; + const size_t TASK_START = BIT1; + const size_t TASK_STOP = BIT2; + + + uart_resource uart; + uart_event_loop event_loop; + signal_group signal; + uart_task task_handle; + +}; + + +uart_resource::~uart_resource() +{ + if (port >= UART_NUM_0 && port < UART_NUM_MAX) { + uart_driver_delete(port); + } +} + + + + +uart_resource::uart_resource(const struct dte_config *config): + port(-1) +// buffer(std::make_unique(config->line_buffer_size)) +{ + esp_err_t res; + + line_buffer_size = config->line_buffer_size; + + /* TODO: Bind methods */ + + /* Config UART */ + uart_config_t uart_config = {}; + uart_config.baud_rate = config->baud_rate; + uart_config.data_bits = config->data_bits; + uart_config.parity = config->parity; + uart_config.stop_bits = config->stop_bits; + uart_config.flow_ctrl = (config->flow_control == ESP_MODEM_FLOW_CONTROL_HW) ? UART_HW_FLOWCTRL_CTS_RTS + : UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_REF_TICK; + + throw_if_esp_fail(uart_param_config(config->port_num, &uart_config), "config uart parameter failed"); + + if (config->flow_control == ESP_MODEM_FLOW_CONTROL_HW) { + res = uart_set_pin(config->port_num, config->tx_io_num, config->rx_io_num, + config->rts_io_num, config->cts_io_num); + } else { + res = uart_set_pin(config->port_num, config->tx_io_num, config->rx_io_num, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + } + throw_if_esp_fail(res, "config uart gpio failed"); + /* Set flow control threshold */ + if (config->flow_control == ESP_MODEM_FLOW_CONTROL_HW) { + res = uart_set_hw_flow_ctrl(config->port_num, UART_HW_FLOWCTRL_CTS_RTS, UART_FIFO_LEN - 8); + } else if (config->flow_control == ESP_MODEM_FLOW_CONTROL_SW) { + res = uart_set_sw_flow_ctrl(config->port_num, true, 8, UART_FIFO_LEN - 8); + } + throw_if_esp_fail(res, "config uart flow control failed"); + /* Install UART driver and get event queue used inside driver */ + res = uart_driver_install(config->port_num, config->rx_buffer_size, config->tx_buffer_size, + config->event_queue_size, &(event_queue), 0); + throw_if_esp_fail(res, "install uart driver failed"); + throw_if_esp_fail(uart_set_rx_timeout(config->port_num, 1), "set rx timeout failed"); + + /* Set pattern interrupt, used to detect the end of a line. */ +// res = uart_enable_pattern_det_baud_intr(config->port_num, '\n', 1, MIN_PATTERN_INTERVAL, MIN_POST_IDLE, MIN_PRE_IDLE); + /* Set pattern queue size */ +// pattern_queue_size = config->pattern_queue_size; +// res |= uart_pattern_queue_reset(config->port_num, config->pattern_queue_size); + /* Starting in command mode -> explicitly disable RX interrupt */ +// uart_disable_rx_intr(config->port_num); + uart_set_rx_full_threshold(config->port_num, 64); + throw_if_esp_fail(res, "config uart pattern failed"); + /* mark UART as initialized */ + port = config->port_num; +} + +std::unique_ptr create_uart_terminal(const struct dte_config *config) +{ + try { + auto term = std::make_unique(config); + term->start(); + return term; + } catch (std::bad_alloc& e) { + ESP_LOGE(TAG, "Out of memory"); + return nullptr; + } catch (esp_err_exception& e) { + esp_err_t err = e.get_err_t(); + ESP_LOGE(TAG, "Error occurred during UART term init: %d", err); + ESP_LOGE(TAG, "%s", e.what()); + return nullptr; + } + +} + +std::unique_ptr create_dte(const struct dte_config *config) +{ + try { + auto term = std::make_unique(std::make_unique(config)); +// term->start(); + return term; + } catch (std::bad_alloc& e) { + ESP_LOGE(TAG, "Out of memory"); + return nullptr; + } catch (esp_err_exception& e) { + esp_err_t err = e.get_err_t(); + ESP_LOGE(TAG, "Error occurred during UART term init: %d", err); + ESP_LOGE(TAG, "%s", e.what()); + return nullptr; + } + +} + + +void uart_terminal::task() +{ + uart_event_t event; + size_t len; + signal.set(TASK_INIT); + signal.wait_any(TASK_START | TASK_STOP, portMAX_DELAY); + if (signal.is_any(TASK_STOP)) { + return; // deletes the static task + } + while(signal.is_any(TASK_START)) { + event_loop.run(); + if (uart.get_event(event, 100)) { + switch (event.type) { + case UART_DATA: + ESP_LOGI(TAG, "UART_DATA"); + uart_get_buffered_data_len(uart.port, &len); + ESP_LOGI(TAG, "UART_DATA len=%d, on_data=%d", len, (bool)on_data); + if (len && on_data) { + on_data(len); + } + break; + case UART_FIFO_OVF: + ESP_LOGW(TAG, "HW FIFO Overflow"); + uart.reset_events(); + break; + case UART_BUFFER_FULL: + ESP_LOGW(TAG, "Ring Buffer Full"); + uart.reset_events(); + break; + case UART_BREAK: + ESP_LOGW(TAG, "Rx Break"); + break; + case UART_PARITY_ERR: + ESP_LOGE(TAG, "Parity Error"); + break; + case UART_FRAME_ERR: + ESP_LOGE(TAG, "Frame Error"); + break; + case UART_PATTERN_DET: + ESP_LOGI(TAG, "UART_PATTERN_DET"); + break; + default: + ESP_LOGW(TAG, "unknown uart event type: %d", event.type); + break; + } + } + +// ESP_LOGI(TAG, "uart_event_task_entry"); +// vTaskDelay(pdMS_TO_TICKS(200)); + + } +} + +int uart_terminal::read(uint8_t *data, size_t len) +{ + size_t length = 0; + uart_get_buffered_data_len(uart.port, &length); + if (length > 0) { + return uart_read_bytes(uart.port, data, length, portMAX_DELAY); + } + return 0; +} + +int uart_terminal::write(uint8_t *data, size_t len) +{ + return uart_write_bytes(uart.port, data, len); +} + +dte::dte(std::unique_ptr terminal): + buffer_size(DTE_BUFFER_SIZE), consumed(0), + buffer(std::make_unique(buffer_size)), + term(std::move(terminal)), mode(dte_mode::UNDEF) {} + + +bool dte::send_command(const std::string& command, got_line_cb got_line, uint32_t time_ms) +{ + term->write((uint8_t *)command.c_str(), command.length()); + term->set_data_cb([&](size_t len){ + auto data_to_read = std::min(len, buffer_size - consumed); + auto data = buffer.get() + consumed; + auto actual_len = term->read(data, data_to_read); + consumed += actual_len; + if (memchr(data, '\n', actual_len)) { + ESP_LOGI("in the lambda", "FOUND"); + if (got_line(buffer.get(), consumed)) { + signal.set(GOT_LINE); + } + } + ESP_LOGI("in the lambda", "len=%d data %s", len, (char*)buffer.get()); + }); + auto res = signal.wait(GOT_LINE, time_ms); + consumed = 0; + return res; +} \ No newline at end of file