diff --git a/.github/workflows/modem_sim__build.yml b/.github/workflows/modem_sim__build.yml new file mode 100644 index 0000000000..4515c87a90 --- /dev/null +++ b/.github/workflows/modem_sim__build.yml @@ -0,0 +1,28 @@ +name: "modem_sim: build-tests" + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, labeled] + +jobs: + build_modem_sim: + if: contains(github.event.pull_request.labels.*.name, 'modem_sim') || github.event_name == 'push' + name: Build + strategy: + matrix: + idf_ver: ["release-v5.4"] + runs-on: ubuntu-22.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v3 + - name: Build ESP-AT with IDF-${{ matrix.idf_ver }} + shell: bash + run: | + cd common_components/modem_sim + ./install.sh + source export.sh + idf.py build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c589b45b4e..2eee6e3944 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,8 +61,8 @@ repos: - repo: local hooks: - id: commit message scopes - name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws" - entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|dns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls|lws)\)\:)' + name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws, modem_sim" + entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|dns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls|lws|modem_sim)\)\:)' language: pygrep args: [--multiline] stages: [commit-msg] diff --git a/common_components/modem_sim/export.sh b/common_components/modem_sim/export.sh new file mode 100755 index 0000000000..5ac6e2da19 --- /dev/null +++ b/common_components/modem_sim/export.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +source $IDF_PATH/export.sh + +export AT_CUSTOM_COMPONENTS="`pwd`/pppd_cmd" + +cd modem_sim_esp32/esp-at + +python -m pip install -r requirements.txt + +python build.py reconfigure diff --git a/common_components/modem_sim/install.sh b/common_components/modem_sim/install.sh new file mode 100755 index 0000000000..a1ca7ed1d3 --- /dev/null +++ b/common_components/modem_sim/install.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Create directory "modem_sim_esp32", go inside it +# Usage: ./install.sh [platform] [module] + +SCRIPT_DIR=$(pwd) +mkdir -p modem_sim_esp32 +cd modem_sim_esp32 + +# Shallow clone https://github.com/espressif/esp-at.git +if [ ! -d "esp-at" ]; then + git clone --depth 1 https://github.com/espressif/esp-at.git +else + echo "esp-at directory already exists, skipping clone." +fi + +cd esp-at + +# Add esp-idf directory which is a symlink to the $IDF_PATH +if [ -z "$IDF_PATH" ]; then + echo "Error: IDF_PATH environment variable is not set" + exit 1 +fi + +if [ ! -L "esp-idf" ]; then + ln -sf "$IDF_PATH" esp-idf +else + echo "esp-idf symlink already exists, skipping." +fi + +# Create "build" directory +mkdir -p build + +# Default values for platform and module +platform="PLATFORM_ESP32" +module="WROOM-32" + +# Override defaults if parameters are provided +if [ ! -z "$1" ]; then + platform="$1" +fi +if [ ! -z "$2" ]; then + module="$2" +fi + +# Create file "build/module_info.json" with content +cat > build/module_info.json << EOF +{ + "platform": "$platform", + "module": "$module", + "description": "4MB, Wi-Fi + BLE, OTA, TX:17 RX:16", + "silence": 0 +} +EOF + +cp "$SCRIPT_DIR/sdkconfig.defaults" "module_config/module_esp32_default/sdkconfig.defaults" + +echo "Installation completed successfully!" +echo "Created modem_sim_esp32 directory with esp-at repository and configuration" diff --git a/common_components/modem_sim/pppd_cmd/CMakeLists.txt b/common_components/modem_sim/pppd_cmd/CMakeLists.txt new file mode 100644 index 0000000000..28aba2a308 --- /dev/null +++ b/common_components/modem_sim/pppd_cmd/CMakeLists.txt @@ -0,0 +1,14 @@ + +file(GLOB_RECURSE srcs *.c) + +set(includes "include") + +# Add more required components you need here, separated by spaces +set(require_components at freertos nvs_flash) + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS ${includes} + REQUIRES ${require_components}) + +idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE TRUE) diff --git a/common_components/modem_sim/pppd_cmd/custom/at_custom_cmd.c b/common_components/modem_sim/pppd_cmd/custom/at_custom_cmd.c new file mode 100644 index 0000000000..68d5059a84 --- /dev/null +++ b/common_components/modem_sim/pppd_cmd/custom/at_custom_cmd.c @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_at.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "esp_check.h" + +extern uint8_t g_at_cmd_port; + +static uint8_t at_test_cmd_test(uint8_t *cmd_name) +{ + uint8_t buffer[64] = {0}; + snprintf((char *)buffer, 64, "test command: is executed\r\n", cmd_name); + esp_at_port_write_data(buffer, strlen((char *)buffer)); + + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_query_cmd_test(uint8_t *cmd_name) +{ + uint8_t buffer[64] = {0}; + snprintf((char *)buffer, 64, "query command: is executed\r\n", cmd_name); + esp_at_port_write_data(buffer, strlen((char *)buffer)); + + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_setup_cmd_test(uint8_t para_num) +{ + uint8_t index = 0; + printf("setup command: is executed\r\n", esp_at_get_current_cmd_name(), para_num); + + // get first parameter, and parse it into a digit + int32_t digit = 0; + if (esp_at_get_para_as_digit(index++, &digit) != ESP_AT_PARA_PARSE_RESULT_OK) { + return ESP_AT_RESULT_CODE_ERROR; + } + printf("digit: %d\r\n", digit); + + // get second parameter, and parse it into a string + uint8_t *str = NULL; + if (esp_at_get_para_as_str(index++, &str) != ESP_AT_PARA_PARSE_RESULT_OK) { + return ESP_AT_RESULT_CODE_ERROR; + } + printf("string: %s\r\n", str); + + // allocate a buffer and construct the data, then send the data to mcu via interface (uart/spi/sdio/socket) + uint8_t *buffer = (uint8_t *)malloc(512); + if (!buffer) { + return ESP_AT_RESULT_CODE_ERROR; + } + int len = snprintf((char *)buffer, 512, "setup command: is executed\r\n", + esp_at_get_current_cmd_name(), digit, str); + esp_at_port_write_data(buffer, len); + + // remember to free the buffer + free(buffer); + + return ESP_AT_RESULT_CODE_OK; +} + +#define TAG "at_custom_cmd" +static esp_netif_t *s_netif = NULL; + +static void on_ppp_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + esp_netif_t **netif = data; + if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) { + printf("Disconnected!"); + } +} + +static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + ip_event_got_ip_t *event = (ip_event_got_ip_t *)data; + esp_netif_t *netif = event->esp_netif; + if (event_id == IP_EVENT_PPP_GOT_IP) { + printf("Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip)); + ESP_ERROR_CHECK(esp_netif_napt_enable(s_netif)); + + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Disconnected"); + } +} + +static SemaphoreHandle_t at_sync_sema = NULL; +static void wait_data_callback(void) +{ + static uint8_t buffer[1024] = {0}; + // xSemaphoreGive(at_sync_sema); + int len = esp_at_port_read_data(buffer, sizeof(buffer) - 1); + ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE); + esp_netif_receive(s_netif, buffer, len, NULL); +} + +static esp_err_t transmit(void *h, void *buffer, size_t len) +{ + // struct eppp_handle *handle = h; + printf("transmit: %d bytes\n", len); + // ESP_LOG_BUFFER_HEXDUMP("ppp_uart_send", buffer, len, ESP_LOG_INFO); + esp_at_port_write_data(buffer, len); + return ESP_OK; +} + +static uint8_t at_exe_cmd_test(uint8_t *cmd_name) +{ + uint8_t buffer[64] = {0}; + snprintf((char *)buffer, 64, "execute command: is executed\r\n", cmd_name); + esp_at_port_write_data(buffer, strlen((char *)buffer)); + printf("YYYEEES Command executed successfully\r\n", cmd_name); + if (!at_sync_sema) { + at_sync_sema = xSemaphoreCreateBinary(); + assert(at_sync_sema != NULL); + esp_netif_driver_ifconfig_t driver_cfg = { + .handle = (void *)1, + .transmit = transmit, + }; + const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg; + + esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP(); + esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg, + .driver = ppp_driver_cfg, + .stack = ESP_NETIF_NETSTACK_DEFAULT_PPP + }; + + s_netif = esp_netif_new(&netif_ppp_config); + esp_netif_ppp_config_t netif_params; + ESP_ERROR_CHECK(esp_netif_ppp_get_params(s_netif, &netif_params)); + netif_params.ppp_our_ip4_addr.addr = ESP_IP4TOADDR(192, 168, 11, 1); + netif_params.ppp_their_ip4_addr.addr = ESP_IP4TOADDR(192, 168, 11, 2); + netif_params.ppp_error_event_enabled = true; + ESP_ERROR_CHECK(esp_netif_ppp_set_params(s_netif, &netif_params)); + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) { + printf("Failed to register IP event handler"); + } + if (esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event, NULL) != ESP_OK) { + printf("Failed to register NETIF_PPP_STATUS event handler"); + } + + + } + esp_at_port_write_data((uint8_t *)"CONNECT\r\n", strlen("CONNECT\r\n")); + + // set the callback function which will be called by AT port after receiving the input data + esp_at_port_enter_specific(wait_data_callback); + esp_netif_action_start(s_netif, 0, 0, 0); + esp_netif_action_connected(s_netif, 0, 0, 0); + + // receive input data + // while(xSemaphoreTake(at_sync_sema, portMAX_DELAY)) { + // int len = esp_at_port_read_data(buffer, sizeof(buffer) - 1); + // if (len > 0) { + // buffer[len] = '\0'; // null-terminate the string + // printf("Received data: %s\n", buffer); + // } else { + // printf("No data received or error occurred.\n"); + // continue; + // } + // } + + // uart_write_bytes(g_at_cmd_port, "CONNECT\r\n", strlen("CONNECT\r\n")); + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + printf("-"); + } + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_test_cereg(uint8_t *cmd_name) +{ + printf("%s: AT command is executed\r\n", __func__, cmd_name); + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_query_cereg(uint8_t *cmd_name) +{ + printf("%s: AT command is executed\r\n", __func__, cmd_name); + // static uint8_t buffer[] = "+CEREG: 0,1,2,3,4,5\r\n"; + static uint8_t buffer[] = "+CEREG: 7,8\r\n"; + esp_at_port_write_data(buffer, sizeof(buffer)); + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_setup_cereg(uint8_t num) +{ + printf("%s: AT command is executed\r\n", __func__, num); + return ESP_AT_RESULT_CODE_OK; +} + +static uint8_t at_exe_cereg(uint8_t *cmd_name) +{ + printf("%s: AT command is executed\r\n", __func__, cmd_name); + return ESP_AT_RESULT_CODE_OK; +} + + +static const esp_at_cmd_struct at_custom_cmd[] = { + {"+PPPD", at_test_cmd_test, at_query_cmd_test, at_setup_cmd_test, at_exe_cmd_test}, + {"+CEREG", at_test_cereg, at_query_cereg, at_setup_cereg, at_exe_cereg}, + /** + * @brief You can define your own AT commands here. + */ +}; + +bool esp_at_custom_cmd_register(void) +{ + return esp_at_custom_cmd_array_regist(at_custom_cmd, sizeof(at_custom_cmd) / sizeof(esp_at_cmd_struct)); +} + +ESP_AT_CMD_SET_INIT_FN(esp_at_custom_cmd_register, 1); diff --git a/common_components/modem_sim/pppd_cmd/include/at_custom_cmd.h b/common_components/modem_sim/pppd_cmd/include/at_custom_cmd.h new file mode 100644 index 0000000000..0aa0312eba --- /dev/null +++ b/common_components/modem_sim/pppd_cmd/include/at_custom_cmd.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include "esp_at_core.h" +#include "esp_at.h" + +/** + * @brief You can include more header files here for your own AT commands. + */ diff --git a/common_components/modem_sim/sdkconfig.defaults b/common_components/modem_sim/sdkconfig.defaults new file mode 100644 index 0000000000..9c95f9c660 --- /dev/null +++ b/common_components/modem_sim/sdkconfig.defaults @@ -0,0 +1,77 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Minimal Configuration +# +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +CONFIG_APP_PROJECT_VER_FROM_CONFIG=y +CONFIG_APP_PROJECT_VER="v4.1.0.0-dev" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="module_config/module_esp32_default/partitions_at.csv" +CONFIG_PARTITION_TABLE_MD5=n +CONFIG_AT_CUSTOMIZED_PARTITION_TABLE_FILE="module_config/module_esp32_default/at_customize.csv" +CONFIG_BT_ENABLED=y +CONFIG_BT_BTU_TASK_STACK_SIZE=5120 +CONFIG_BT_BLE_BLUFI_ENABLE=y +CONFIG_BT_STACK_NO_LOG=y +CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y +CONFIG_BTDM_CTRL_MODE_BTDM=y +CONFIG_BTDM_CTRL_LPCLK_SEL_EXT_32K_XTAL=y +CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE=200 +CONFIG_ESP_TLS_PSK_VERIFICATION=y +CONFIG_ESP_TLS_INSECURE=y +CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y +CONFIG_ESP_ERR_TO_NAME_LOOKUP=n +CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL=y +CONFIG_ETH_DMA_RX_BUFFER_NUM=3 +CONFIG_ETH_DMA_TX_BUFFER_NUM=3 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +CONFIG_HTTPD_MAX_URI_LEN=1024 +CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y +CONFIG_RTC_CLK_SRC_EXT_CRYS=y +CONFIG_RTC_EXT_CRYST_ADDIT_CURRENT=y +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_PM_ENABLE=y +CONFIG_PM_SLP_DISABLE_GPIO=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80=y +CONFIG_ESP_TASK_WDT_PANIC=y +CONFIG_ESP_TASK_WDT_TIMEOUT_S=60 +CONFIG_ESP_DEBUG_OCDAWARE=n +CONFIG_ESP_WIFI_IRAM_OPT=n +CONFIG_ESP_WIFI_RX_IRAM_OPT=n +CONFIG_ESP_WIFI_SLP_IRAM_OPT=y +CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT=y +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0 +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=n +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_LWIP_MAX_SOCKETS=16 +CONFIG_LWIP_SO_LINGER=y +CONFIG_LWIP_SO_RCVBUF=y +CONFIG_LWIP_IP4_REASSEMBLY=y +CONFIG_LWIP_IP6_REASSEMBLY=y +CONFIG_LWIP_IPV6_AUTOCONFIG=y +CONFIG_LWIP_TCP_MAXRTX=6 +CONFIG_LWIP_TCP_SYNMAXRTX=3 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_SERVER_SUPPORT=y +CONFIG_LWIP_SNTP_MAX_SERVERS=3 +CONFIG_LWIP_SNTP_STARTUP_DELAY=n +CONFIG_MBEDTLS_DYNAMIC_BUFFER=y +CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=n +CONFIG_MBEDTLS_HAVE_TIME_DATE=y +CONFIG_MBEDTLS_DHM_C=y +CONFIG_NEWLIB_NANO_FORMAT=y +CONFIG_VFS_SUPPORT_TERMIOS=n +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_AT_PROCESS_TASK_STACK_SIZE=6144 +CONFIG_AT_MQTT_COMMAND_SUPPORT=y +CONFIG_AT_HTTP_COMMAND_SUPPORT=y +CONFIG_AT_BLE_COMMAND_SUPPORT=n +CONFIG_AT_BLE_HID_COMMAND_SUPPORT=n +CONFIG_AT_BLUFI_COMMAND_SUPPORT=n