Skip to content

Commit ac48fd3

Browse files
committed
feat(modem_sim): Modem simulator based on esp-at
1 parent f43dd50 commit ac48fd3

File tree

8 files changed

+426
-2
lines changed

8 files changed

+426
-2
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: "modem_sim: build-tests"
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
types: [opened, synchronize, reopened, labeled]
9+
10+
jobs:
11+
build_modem_sim:
12+
if: contains(github.event.pull_request.labels.*.name, 'modem_sim') || github.event_name == 'push'
13+
name: Build
14+
strategy:
15+
matrix:
16+
idf_ver: ["release-v5.4"]
17+
runs-on: ubuntu-22.04
18+
container: espressif/idf:${{ matrix.idf_ver }}
19+
steps:
20+
- name: Checkout esp-protocols
21+
uses: actions/checkout@v3
22+
- name: Build ESP-AT with IDF-${{ matrix.idf_ver }}
23+
shell: bash
24+
run: |
25+
cd common_components/modem_sim
26+
./install.sh
27+
source export.sh
28+
idf.py build

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ repos:
6161
- repo: local
6262
hooks:
6363
- id: commit message scopes
64-
name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws"
65-
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)\)\:)'
64+
name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws, modem_sim"
65+
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)\)\:)'
6666
language: pygrep
6767
args: [--multiline]
6868
stages: [commit-msg]

common_components/modem_sim/export.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
source $IDF_PATH/export.sh
4+
5+
export AT_CUSTOM_COMPONENTS="`pwd`/pppd_cmd"
6+
7+
cd modem_sim_esp32/esp-at
8+
9+
python -m pip install -r requirements.txt
10+
11+
python build.py reconfigure
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/bin/bash
2+
3+
# Create directory "modem_sim_esp32", go inside it
4+
# Usage: ./install.sh [platform] [module]
5+
6+
SCRIPT_DIR=$(dirname "$0")
7+
mkdir -p modem_sim_esp32
8+
cd modem_sim_esp32
9+
10+
# Shallow clone https://github.com/espressif/esp-at.git
11+
if [ ! -d "esp-at" ]; then
12+
git clone --depth 1 https://github.com/espressif/esp-at.git
13+
else
14+
echo "esp-at directory already exists, skipping clone."
15+
fi
16+
17+
cd esp-at
18+
19+
# Add esp-idf directory which is a symlink to the $IDF_PATH
20+
if [ -z "$IDF_PATH" ]; then
21+
echo "Error: IDF_PATH environment variable is not set"
22+
exit 1
23+
fi
24+
25+
if [ ! -L "esp-idf" ]; then
26+
ln -sf "$IDF_PATH" esp-idf
27+
else
28+
echo "esp-idf symlink already exists, skipping."
29+
fi
30+
31+
# Create "build" directory
32+
mkdir -p build
33+
34+
# Default values for platform and module
35+
platform="PLATFORM_ESP32"
36+
module="WROOM-32"
37+
38+
# Override defaults if parameters are provided
39+
if [ ! -z "$1" ]; then
40+
platform="$1"
41+
fi
42+
if [ ! -z "$2" ]; then
43+
module="$2"
44+
fi
45+
46+
# Create file "build/module_info.json" with content
47+
cat > build/module_info.json << EOF
48+
{
49+
"platform": "$platform",
50+
"module": "$module",
51+
"description": "4MB, Wi-Fi + BLE, OTA, TX:17 RX:16",
52+
"silence": 0
53+
}
54+
EOF
55+
56+
cp "$SCRIPT_DIR/sdkconfig.defaults" "module_config/module_esp32_default/sdkconfig.defaults"
57+
58+
echo "Installation completed successfully!"
59+
echo "Created modem_sim_esp32 directory with esp-at repository and configuration"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
file(GLOB_RECURSE srcs *.c)
3+
4+
set(includes "include")
5+
6+
# Add more required components you need here, separated by spaces
7+
set(require_components at freertos nvs_flash)
8+
9+
idf_component_register(
10+
SRCS ${srcs}
11+
INCLUDE_DIRS ${includes}
12+
REQUIRES ${require_components})
13+
14+
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE TRUE)
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#include <stdio.h>
7+
#include <string.h>
8+
#include <stdbool.h>
9+
#include "esp_at.h"
10+
#include "driver/gpio.h"
11+
#include "driver/uart.h"
12+
#include "freertos/FreeRTOS.h"
13+
#include "freertos/event_groups.h"
14+
#include "freertos/semphr.h"
15+
#include "freertos/queue.h"
16+
#include "esp_netif.h"
17+
#include "esp_netif_ppp.h"
18+
#include "esp_check.h"
19+
20+
extern uint8_t g_at_cmd_port;
21+
22+
static uint8_t at_test_cmd_test(uint8_t *cmd_name)
23+
{
24+
uint8_t buffer[64] = {0};
25+
snprintf((char *)buffer, 64, "test command: <AT%s=?> is executed\r\n", cmd_name);
26+
esp_at_port_write_data(buffer, strlen((char *)buffer));
27+
28+
return ESP_AT_RESULT_CODE_OK;
29+
}
30+
31+
static uint8_t at_query_cmd_test(uint8_t *cmd_name)
32+
{
33+
uint8_t buffer[64] = {0};
34+
snprintf((char *)buffer, 64, "query command: <AT%s?> is executed\r\n", cmd_name);
35+
esp_at_port_write_data(buffer, strlen((char *)buffer));
36+
37+
return ESP_AT_RESULT_CODE_OK;
38+
}
39+
40+
static uint8_t at_setup_cmd_test(uint8_t para_num)
41+
{
42+
uint8_t index = 0;
43+
printf("setup command: <AT%s=%d> is executed\r\n", esp_at_get_current_cmd_name(), para_num);
44+
45+
// get first parameter, and parse it into a digit
46+
int32_t digit = 0;
47+
if (esp_at_get_para_as_digit(index++, &digit) != ESP_AT_PARA_PARSE_RESULT_OK) {
48+
return ESP_AT_RESULT_CODE_ERROR;
49+
}
50+
printf("digit: %d\r\n", digit);
51+
52+
// get second parameter, and parse it into a string
53+
uint8_t *str = NULL;
54+
if (esp_at_get_para_as_str(index++, &str) != ESP_AT_PARA_PARSE_RESULT_OK) {
55+
return ESP_AT_RESULT_CODE_ERROR;
56+
}
57+
printf("string: %s\r\n", str);
58+
59+
// allocate a buffer and construct the data, then send the data to mcu via interface (uart/spi/sdio/socket)
60+
uint8_t *buffer = (uint8_t *)malloc(512);
61+
if (!buffer) {
62+
return ESP_AT_RESULT_CODE_ERROR;
63+
}
64+
int len = snprintf((char *)buffer, 512, "setup command: <AT%s=%d,\"%s\"> is executed\r\n",
65+
esp_at_get_current_cmd_name(), digit, str);
66+
esp_at_port_write_data(buffer, len);
67+
68+
// remember to free the buffer
69+
free(buffer);
70+
71+
return ESP_AT_RESULT_CODE_OK;
72+
}
73+
74+
#define TAG "at_custom_cmd"
75+
static esp_netif_t *s_netif = NULL;
76+
77+
static void on_ppp_event(void *arg, esp_event_base_t base, int32_t event_id, void *data)
78+
{
79+
esp_netif_t **netif = data;
80+
if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) {
81+
printf("Disconnected!");
82+
}
83+
}
84+
85+
static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data)
86+
{
87+
ip_event_got_ip_t *event = (ip_event_got_ip_t *)data;
88+
esp_netif_t *netif = event->esp_netif;
89+
if (event_id == IP_EVENT_PPP_GOT_IP) {
90+
printf("Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif),
91+
esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip));
92+
ESP_ERROR_CHECK(esp_netif_napt_enable(s_netif));
93+
94+
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
95+
ESP_LOGI(TAG, "Disconnected");
96+
}
97+
}
98+
99+
static SemaphoreHandle_t at_sync_sema = NULL;
100+
static void wait_data_callback(void)
101+
{
102+
static uint8_t buffer[1024] = {0};
103+
// xSemaphoreGive(at_sync_sema);
104+
int len = esp_at_port_read_data(buffer, sizeof(buffer) - 1);
105+
ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE);
106+
esp_netif_receive(s_netif, buffer, len, NULL);
107+
}
108+
109+
static esp_err_t transmit(void *h, void *buffer, size_t len)
110+
{
111+
// struct eppp_handle *handle = h;
112+
printf("transmit: %d bytes\n", len);
113+
// ESP_LOG_BUFFER_HEXDUMP("ppp_uart_send", buffer, len, ESP_LOG_INFO);
114+
esp_at_port_write_data(buffer, len);
115+
return ESP_OK;
116+
}
117+
118+
static uint8_t at_exe_cmd_test(uint8_t *cmd_name)
119+
{
120+
uint8_t buffer[64] = {0};
121+
snprintf((char *)buffer, 64, "execute command: <AT%s> is executed\r\n", cmd_name);
122+
esp_at_port_write_data(buffer, strlen((char *)buffer));
123+
printf("YYYEEES Command <AT%s> executed successfully\r\n", cmd_name);
124+
if (!at_sync_sema) {
125+
at_sync_sema = xSemaphoreCreateBinary();
126+
assert(at_sync_sema != NULL);
127+
esp_netif_driver_ifconfig_t driver_cfg = {
128+
.handle = (void *)1,
129+
.transmit = transmit,
130+
};
131+
const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg;
132+
133+
esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP();
134+
esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg,
135+
.driver = ppp_driver_cfg,
136+
.stack = ESP_NETIF_NETSTACK_DEFAULT_PPP
137+
};
138+
139+
s_netif = esp_netif_new(&netif_ppp_config);
140+
esp_netif_ppp_config_t netif_params;
141+
ESP_ERROR_CHECK(esp_netif_ppp_get_params(s_netif, &netif_params));
142+
netif_params.ppp_our_ip4_addr.addr = ESP_IP4TOADDR(192, 168, 11, 1);
143+
netif_params.ppp_their_ip4_addr.addr = ESP_IP4TOADDR(192, 168, 11, 2);
144+
netif_params.ppp_error_event_enabled = true;
145+
ESP_ERROR_CHECK(esp_netif_ppp_set_params(s_netif, &netif_params));
146+
if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) {
147+
printf("Failed to register IP event handler");
148+
}
149+
if (esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event, NULL) != ESP_OK) {
150+
printf("Failed to register NETIF_PPP_STATUS event handler");
151+
}
152+
153+
154+
}
155+
esp_at_port_write_data((uint8_t *)"CONNECT\r\n", strlen("CONNECT\r\n"));
156+
157+
// set the callback function which will be called by AT port after receiving the input data
158+
esp_at_port_enter_specific(wait_data_callback);
159+
esp_netif_action_start(s_netif, 0, 0, 0);
160+
esp_netif_action_connected(s_netif, 0, 0, 0);
161+
162+
// receive input data
163+
// while(xSemaphoreTake(at_sync_sema, portMAX_DELAY)) {
164+
// int len = esp_at_port_read_data(buffer, sizeof(buffer) - 1);
165+
// if (len > 0) {
166+
// buffer[len] = '\0'; // null-terminate the string
167+
// printf("Received data: %s\n", buffer);
168+
// } else {
169+
// printf("No data received or error occurred.\n");
170+
// continue;
171+
// }
172+
// }
173+
174+
// uart_write_bytes(g_at_cmd_port, "CONNECT\r\n", strlen("CONNECT\r\n"));
175+
while (1) {
176+
vTaskDelay(pdMS_TO_TICKS(1000));
177+
printf("-");
178+
}
179+
return ESP_AT_RESULT_CODE_OK;
180+
}
181+
182+
static uint8_t at_test_cereg(uint8_t *cmd_name)
183+
{
184+
printf("%s: AT command <AT%s> is executed\r\n", __func__, cmd_name);
185+
return ESP_AT_RESULT_CODE_OK;
186+
}
187+
188+
static uint8_t at_query_cereg(uint8_t *cmd_name)
189+
{
190+
printf("%s: AT command <AT%s> is executed\r\n", __func__, cmd_name);
191+
// static uint8_t buffer[] = "+CEREG: 0,1,2,3,4,5\r\n";
192+
static uint8_t buffer[] = "+CEREG: 7,8\r\n";
193+
esp_at_port_write_data(buffer, sizeof(buffer));
194+
return ESP_AT_RESULT_CODE_OK;
195+
}
196+
197+
static uint8_t at_setup_cereg(uint8_t num)
198+
{
199+
printf("%s: AT command <AT%d> is executed\r\n", __func__, num);
200+
return ESP_AT_RESULT_CODE_OK;
201+
}
202+
203+
static uint8_t at_exe_cereg(uint8_t *cmd_name)
204+
{
205+
printf("%s: AT command <AT%s> is executed\r\n", __func__, cmd_name);
206+
return ESP_AT_RESULT_CODE_OK;
207+
}
208+
209+
210+
static const esp_at_cmd_struct at_custom_cmd[] = {
211+
{"+PPPD", at_test_cmd_test, at_query_cmd_test, at_setup_cmd_test, at_exe_cmd_test},
212+
{"+CEREG", at_test_cereg, at_query_cereg, at_setup_cereg, at_exe_cereg},
213+
/**
214+
* @brief You can define your own AT commands here.
215+
*/
216+
};
217+
218+
bool esp_at_custom_cmd_register(void)
219+
{
220+
return esp_at_custom_cmd_array_regist(at_custom_cmd, sizeof(at_custom_cmd) / sizeof(esp_at_cmd_struct));
221+
}
222+
223+
ESP_AT_CMD_SET_INIT_FN(esp_at_custom_cmd_register, 1);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#pragma once
7+
#include "esp_at_core.h"
8+
#include "esp_at.h"
9+
10+
/**
11+
* @brief You can include more header files here for your own AT commands.
12+
*/

0 commit comments

Comments
 (0)