esp_modem: Refactor the modem to a standalone managed component

This commit is contained in:
David Cermak
2020-11-18 21:20:35 +01:00
parent 5565a09ed1
commit c8e89098bd
63 changed files with 5081 additions and 2180 deletions

View File

@ -0,0 +1,8 @@
# 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(modem-console)

View File

@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := modem-console
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
include $(IDF_PATH)/make/project.mk

View File

@ -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 is mainly targets experimenting with a modem device, sending custom commands and switching to PPP mode using esp-console, command line API.
## How to use this example
See the README.md file in the upper level `pppos` directory for more information about the PPPoS examples.

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "modem_console_main.c"
"httpget_handle.c"
"ping_handle.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,108 @@
/* Modem console 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 <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "esp_log.h"
#include "esp_http_client.h"
static const char *TAG = "modem_console_httpget";
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
if ((bool)evt->user_data &&
!esp_http_client_is_chunked_response(evt->client)) {
ESP_LOG_BUFFER_HEXDUMP(TAG, evt->data, evt->data_len, ESP_LOG_INFO);
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
break;
}
return ESP_OK;
}
static struct {
struct arg_str *host;
struct arg_lit *hex;
struct arg_end *end;
} http_args;
static int do_http_client(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&http_args);
if (nerrors != 0) {
arg_print_errors(stderr, http_args.end, argv[0]);
return 1;
}
esp_http_client_config_t config = {
.event_handler = http_event_handler,
};
if (http_args.host->count > 0) {
config.url = http_args.host->sval[0];
} else {
config.url = "http://httpbin.org/get";
}
if (http_args.hex->count > 0) {
// show hex data from http-get
config.user_data = (void*)true;
}
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
return 0;
}
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
return 1;
}
void modem_console_register_http(void)
{
http_args.host = arg_str0(NULL, NULL, "<host>", "address or host-name to send GET request (defaults to http://httpbin.org/get)");
http_args.hex = arg_litn("p", "print-hex", 0, 1, "print hex output"),
http_args.end = arg_end(1);
const esp_console_cmd_t http_cmd = {
.command = "httpget",
.help = "http get command to test data mode",
.hint = NULL,
.func = &do_http_client,
.argtable = &http_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&http_cmd));
}

View File

@ -0,0 +1,307 @@
/* Modem console 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 <stdio.h>
#include <string.h>
#include <esp_modem_dce_common_commands.h>
#include "sdkconfig.h"
#include "esp_console.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "argtable3/argtable3.h"
#include "esp_modem.h"
#include "esp_log.h"
// utilities to check network connectivity
void modem_console_register_http(void);
void modem_console_register_ping(void);
static esp_modem_dce_t *s_dce = NULL;
static const char *TAG = "modem_console";
static struct {
struct arg_str *command;
struct arg_int *param_int;
struct arg_str *param_str;
struct arg_str *param_pdp;
struct arg_str *param_bool;
struct arg_str *param;
struct arg_str *result;
struct arg_end *end;
} at_args;
static struct {
struct arg_str *param;
struct arg_end *end;
} modem_args;
static struct {
struct arg_str *command;
struct arg_int *timeout;
struct arg_str *pattern;
struct arg_lit *no_cr;
struct arg_end *end;
} generic_at_args;
static char s_common_in_str[100]; // used as common string input param holder
static char s_common_out_str[100]; // used as output string/command result holder
static esp_err_t handle_line_pattern(esp_modem_dce_t *dce, const char *line)
{
esp_err_t err = ESP_OK;
ESP_LOGI(TAG, "handle_line_pattern: DCE response: %s\n", line);
if (strstr(line, dce->handle_line_ctx)) {
err = esp_modem_process_command_done(dce, ESP_MODEM_STATE_SUCCESS);
}
return err;
}
static int do_dce(int argc, char **argv)
{
// specific DCE generic command params
static bool bool_result;
static char pdp_type[10];
static char pdp_apn[10];
static esp_modem_dce_pdp_ctx_t pdp = { .type = pdp_type, .apn = pdp_apn };
static esp_modem_dce_csq_ctx_t csq;
static esp_modem_dce_cbc_ctx_t cbc;
int nerrors = arg_parse(argc, argv, (void **) &at_args);
if (nerrors != 0) {
arg_print_errors(stderr, at_args.end, argv[0]);
return 1;
}
void * command_param = NULL;
void * command_result = NULL;
// parse input params
if (at_args.param_int->count > 0) {
command_param = (void*)(at_args.param_int->ival[0]);
} else if (at_args.param_bool->count > 0) {
const char * bool_in_str = at_args.param_bool->sval[0];
s_common_out_str[0] = '\0';
command_result = s_common_out_str;
if (strstr(bool_in_str,"true") || strstr(bool_in_str,"1")) {
command_param = (void*)true;
} else {
command_param = (void*)false;
}
} else if (at_args.param_pdp->count > 0) {
// parse out three comma separated sub-arguments
sscanf(at_args.param_pdp->sval[0], "%d,%s", &pdp.cid, pdp_type);
char *str_apn = strchr(pdp_type, ',');
if (str_apn) {
strncpy(pdp_apn, str_apn + 1, sizeof(pdp_apn));
str_apn[0] = '\0';
}
command_param = &pdp;
} else if (at_args.param_str->count > 0) {
strncpy(s_common_in_str, at_args.param_str->sval[0], sizeof(s_common_in_str));
command_param = s_common_in_str;
} else if (at_args.param->count > 0) { // default param is treated as string
strncpy(s_common_in_str, at_args.param->sval[0], sizeof(s_common_in_str));
command_param = s_common_in_str;
}
// parse output params
if (at_args.result->count > 0) {
const char *res = at_args.result->sval[0];
if (strstr(res, "csq")) {
command_result = &csq;
}else if (strstr(res, "cbc")) {
command_result = &cbc;
} else if (strstr(res, "str")) {
command_param = (void*)sizeof(s_common_out_str);
command_result = s_common_out_str;
} else if (strstr(res, "bool")) {
command_result = &bool_result;
} else {
command_param = (void*)sizeof(s_common_out_str);
command_result = s_common_out_str;
}
}
// by default (if no param/result provided) expect string output
if (command_param == NULL && command_result == NULL) {
s_common_out_str[0] = '\0';
command_param = (void*)sizeof(s_common_out_str);
command_result = s_common_out_str;
}
esp_err_t err = esp_modem_command_list_run(s_dce, at_args.command->sval[0], command_param, command_result);
if (err == ESP_OK) {
printf("Command %s succeeded\n", at_args.command->sval[0]);
if (command_result == s_common_out_str && s_common_out_str[0] != '\0') {
ESP_LOGI(TAG, "Command string output: %s", s_common_out_str);
} else if (command_result == &csq) {
ESP_LOGI(TAG, "Command CSQ output: rssi:%d, ber:%d", csq.rssi, csq.ber);
} else if (command_result == &cbc) {
ESP_LOGI(TAG, "Command battery output:%d mV", cbc.battery_status);
} else if (command_result == &bool_result) {
ESP_LOGI(TAG, "Command bool output: %s", bool_result ? "true" : "false");
}
return 0;
}
ESP_LOGE(TAG, "Command %s failed with %d", at_args.command->sval[0], err);
return 1;
}
static int do_at_command(int argc, char **argv)
{
int timeout = 1000;
int nerrors = arg_parse(argc, argv, (void **)&generic_at_args);
if (nerrors != 0) {
arg_print_errors(stderr, generic_at_args.end, argv[0]);
return 1;
}
esp_modem_dce_handle_line_t handle_line = esp_modem_dce_handle_response_default;
strncpy(s_common_in_str, generic_at_args.command->sval[0], sizeof(s_common_in_str) - 2);
if (generic_at_args.no_cr->count == 0) {
size_t cmd_len = strlen(generic_at_args.command->sval[0]);
s_common_in_str[cmd_len] = '\r';
s_common_in_str[cmd_len + 1] = '\0';
}
if (generic_at_args.timeout->count > 0) {
timeout = generic_at_args.timeout->ival[0];
}
if (generic_at_args.pattern->count > 0) {
strncpy(s_common_out_str, generic_at_args.pattern->sval[0], sizeof(s_common_out_str));
handle_line = handle_line_pattern;
}
if (esp_modem_dce_generic_command(s_dce, s_common_in_str, timeout, handle_line, s_common_out_str) == ESP_OK) {
return 0;
}
return 1;
}
static int do_modem_lifecycle(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&modem_args);
if (nerrors != 0) {
arg_print_errors(stderr, modem_args.end, argv[0]);
return 1;
}
if (modem_args.param->count > 0) {
esp_err_t err = ESP_FAIL;
if (strstr(modem_args.param->sval[0], "PPP")) {
err = esp_modem_start_ppp(s_dce->dte);
} else if (strstr(modem_args.param->sval[0], "CMD")) {
err = esp_modem_stop_ppp(s_dce->dte);
} else {
return 1;
}
if (err == ESP_OK) {
ESP_LOGI(TAG, "set_working_mode %s succeeded", at_args.param->sval[0]);
} else {
ESP_LOGI(TAG, "set_working_mode %s failed with %d", at_args.param->sval[0], err);
}
return 0;
}
return 1;
}
static void register_dce(void)
{
at_args.command = arg_str1(NULL, NULL, "<command>", "Symbolic name of DCE command");
at_args.param_int = arg_int0("i", "int", "<num>", "Input parameter of integer type");
at_args.param_str = arg_str0("s", "str", "<str>", "Input parameter of string type");
at_args.param_pdp = arg_str0("p", "pdp", "<pdp>", "Comma separated string with PDP context");
at_args.param_bool = arg_str0("b", "bool", "<true/false>", "Input parameter of bool type");
at_args.param = arg_str0(NULL, NULL, "<param>", "Default input argument treated as string");
at_args.result = arg_str0("o", "out", "<type>", "Type of output parameter");
at_args.end = arg_end(1);
const esp_console_cmd_t at_cmd = {
.command = "dce",
.help = "send symbolic command to the modem",
.hint = NULL,
.func = &do_dce,
.argtable = &at_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&at_cmd));
}
static void register_at_command(void)
{
generic_at_args.command = arg_str1(NULL, NULL, "<command>", "AT command to send to the modem");
generic_at_args.timeout = arg_int0("t", "timeout", "<timeout>", "command timeout");
generic_at_args.pattern = arg_str0("p", "pattern", "<pattern>", "command response to wait for");
generic_at_args.no_cr = arg_litn("n", "no-cr", 0, 1, "not add trailing CR to the command");
generic_at_args.end = arg_end(1);
const esp_console_cmd_t at_command = {
.command = "at",
.help = "send generic AT command to the modem",
.hint = NULL,
.func = &do_at_command,
.argtable = &generic_at_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&at_command));
}
static void register_modem_lifecycle(void)
{
modem_args.param = arg_str1(NULL, NULL, "<mode>", "PPP or CMD");
modem_args.end = arg_end(1);
const esp_console_cmd_t modem_cmd = {
.command = "modem",
.help = "set modem mode",
.hint = NULL,
.func = &do_modem_lifecycle,
.argtable = &modem_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&modem_cmd));
}
static esp_console_repl_t *s_repl = NULL;
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// init the DTE
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
dte_config.event_task_stack_size = 4096;
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();
esp_modem_dte_t *dte = esp_modem_dte_new(&dte_config);
s_dce = esp_modem_dce_new(&dce_config);
assert(s_dce != NULL);
esp_netif_t *esp_netif = esp_netif_new(&ppp_netif_config);
assert(esp_netif);
ESP_ERROR_CHECK(esp_modem_default_attach(dte, s_dce, esp_netif));
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
// init console REPL environment
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
register_dce();
register_at_command();
register_modem_lifecycle();
modem_console_register_http();
modem_console_register_ping();
// start console REPL
ESP_ERROR_CHECK(esp_console_start_repl(s_repl));
}

View File

@ -0,0 +1,141 @@
/* Ping handle 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 <stdio.h>
#include <string.h>
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "esp_log.h"
#include "ping/ping_sock.h"
#include "lwip/netdb.h"
static const char *TAG = "modem_console_ping";
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
ESP_LOGI(TAG, "%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
ESP_LOGE(TAG, "From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
ip_addr_t target_addr;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
if (IP_IS_V4(&target_addr)) {
ESP_LOGI(TAG, "\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
} else {
ESP_LOGI(TAG, "\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
}
ESP_LOGI(TAG, "%d packets transmitted, %d received, %d%% packet loss, time %dms\n",
transmitted, received, loss, total_time_ms);
// delete the ping sessions, so that we clean up all resources and can create a new ping session
// we don't have to call delete function in the callback, instead we can call delete function from other tasks
esp_ping_delete_session(hdl);
}
static struct {
struct arg_dbl *timeout;
struct arg_int *count;
struct arg_str *host;
struct arg_end *end;
} ping_args;
static int do_ping_cmd(int argc, char **argv)
{
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
int nerrors = arg_parse(argc, argv, (void **)&ping_args);
if (nerrors != 0) {
arg_print_errors(stderr, ping_args.end, argv[0]);
return 1;
}
if (ping_args.timeout->count > 0) {
config.timeout_ms = (uint32_t)(ping_args.timeout->dval[0] * 1000);
}
if (ping_args.count->count > 0) {
config.count = (uint32_t)(ping_args.count->ival[0]);
}
// parse IP address
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *res = NULL;
memset(&hint, 0, sizeof(hint));
memset(&target_addr, 0, sizeof(target_addr));
/* convert domain name to IP address */
if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) {
printf("ping: unknown host %s\n", ping_args.host->sval[0]);
return 1;
}
if (res->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
} else {
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
}
freeaddrinfo(res);
config.target_addr = target_addr;
/* set callback functions */
esp_ping_callbacks_t cbs = {
.on_ping_success = cmd_ping_on_ping_success,
.on_ping_timeout = cmd_ping_on_ping_timeout,
.on_ping_end = cmd_ping_on_ping_end,
.cb_args = NULL
};
esp_ping_handle_t ping;
esp_ping_new_session(&config, &cbs, &ping);
esp_ping_start(ping);
return 0;
}
void modem_console_register_ping(void)
{
ping_args.timeout = arg_dbl0("W", "timeout", "<t>", "Time to wait for a response, in seconds");
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
ping_args.end = arg_end(1);
const esp_console_cmd_t ping_cmd = {
.command = "ping",
.help = "send ICMP ECHO_REQUEST to network hosts",
.hint = NULL,
.func = &do_ping_cmd,
.argtable = &ping_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&ping_cmd));
}

View File

@ -0,0 +1,8 @@
# Override some defaults to enable PPP
CONFIG_LWIP_PPP_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
# Disable legacy API
CONFIG_MODEM_LEGACY_API=n