Skip to content

[modem]: Modem simulator based on esp-at #831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/modem_sim__build.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
11 changes: 11 additions & 0 deletions common_components/modem_sim/export.sh
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions common_components/modem_sim/install.sh
Original file line number Diff line number Diff line change
@@ -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"
14 changes: 14 additions & 0 deletions common_components/modem_sim/pppd_cmd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
223 changes: 223 additions & 0 deletions common_components/modem_sim/pppd_cmd/custom/at_custom_cmd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#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: <AT%s=?> 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: <AT%s?> 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: <AT%s=%d> 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: <AT%s=%d,\"%s\"> 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: <AT%s> is executed\r\n", cmd_name);
esp_at_port_write_data(buffer, strlen((char *)buffer));
printf("YYYEEES Command <AT%s> 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 <AT%s> 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 <AT%s> 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 <AT%d> 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 <AT%s> 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);
12 changes: 12 additions & 0 deletions common_components/modem_sim/pppd_cmd/include/at_custom_cmd.h
Original file line number Diff line number Diff line change
@@ -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.
*/
Loading