Add Network Tools And Nested Command Completion
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"main.c"
|
||||
"console_completion.c"
|
||||
"network_console.c"
|
||||
"status_led.c"
|
||||
"rs232_hw_test.c"
|
||||
"rs232_port_owner.c"
|
||||
@@ -28,6 +30,7 @@ idf_component_register(
|
||||
esp_wifi
|
||||
freertos
|
||||
led_strip
|
||||
lwip
|
||||
mbedtls
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Full-line linenoise completion for nested project console commands. */
|
||||
|
||||
#include "console_completion.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
|
||||
/* Keep full-line candidate strings grouped by their registered root command. */
|
||||
static const char *const s_completion_candidates[] = {
|
||||
/* Hardware debug commands and safe fixed arguments. */
|
||||
"debug transceiver",
|
||||
"debug transceiver enable",
|
||||
"debug transceiver disable",
|
||||
"debug drivers",
|
||||
"debug loopback-a",
|
||||
"debug loopback-b",
|
||||
"debug valid-test",
|
||||
"debug uart-loopback",
|
||||
"debug uart-suite",
|
||||
"debug cts-flow-test",
|
||||
"debug rts-flow-test",
|
||||
|
||||
/* Serial service lifecycle, persistence, counters, and settings. */
|
||||
"serial status",
|
||||
"serial start",
|
||||
"serial stop",
|
||||
"serial set",
|
||||
"serial set baud",
|
||||
"serial set data-bits",
|
||||
"serial set data-bits 7",
|
||||
"serial set data-bits 8",
|
||||
"serial set parity",
|
||||
"serial set parity none",
|
||||
"serial set parity even",
|
||||
"serial set parity odd",
|
||||
"serial set stop-bits",
|
||||
"serial set stop-bits 1",
|
||||
"serial set stop-bits 2",
|
||||
"serial set flow",
|
||||
"serial set flow none",
|
||||
"serial set flow rts-cts",
|
||||
"serial set dtr",
|
||||
"serial set dtr inactive",
|
||||
"serial set dtr active",
|
||||
"serial set dtr on-connect",
|
||||
"serial set rts-threshold",
|
||||
"serial save",
|
||||
"serial load",
|
||||
"serial defaults",
|
||||
"serial reset",
|
||||
"serial counters",
|
||||
"serial clear-counters",
|
||||
|
||||
/* Session broker inspection, ownership, and data operations. */
|
||||
"broker status",
|
||||
"broker clients",
|
||||
"broker counters",
|
||||
"broker clear-counters",
|
||||
"broker connect",
|
||||
"broker disconnect",
|
||||
"broker request-writer",
|
||||
"broker release-writer",
|
||||
"broker force-writer",
|
||||
"broker send-hex",
|
||||
"broker read",
|
||||
"broker events",
|
||||
|
||||
/* Native USB CDC status and writer ownership. */
|
||||
"usb help",
|
||||
"usb status",
|
||||
"usb counters",
|
||||
"usb clear-counters",
|
||||
"usb request-writer",
|
||||
"usb release-writer",
|
||||
|
||||
/* Wi-Fi lifecycle, persistence, profiles, AP policy, and diagnostics. */
|
||||
"wifi status",
|
||||
"wifi profiles",
|
||||
"wifi counters",
|
||||
"wifi clear-counters",
|
||||
"wifi start",
|
||||
"wifi stop",
|
||||
"wifi reconnect",
|
||||
"wifi save",
|
||||
"wifi load",
|
||||
"wifi defaults",
|
||||
"wifi reset",
|
||||
"wifi profile",
|
||||
"wifi profile set",
|
||||
"wifi profile secret",
|
||||
"wifi profile enable",
|
||||
"wifi profile disable",
|
||||
"wifi profile delete",
|
||||
"wifi ap",
|
||||
"wifi ap policy",
|
||||
"wifi ap policy off",
|
||||
"wifi ap policy fallback",
|
||||
"wifi ap policy always",
|
||||
"wifi ap ssid",
|
||||
"wifi ap channel",
|
||||
"wifi ap secret",
|
||||
"wifi ap show-secret",
|
||||
"wifi ping",
|
||||
"wifi nslookup",
|
||||
"wifi traceroute",
|
||||
};
|
||||
|
||||
static void console_completion_callback(const char *buffer, linenoiseCompletions *completions)
|
||||
{
|
||||
/* Preserve ESP-IDF completion for registered root command names. */
|
||||
if (strchr(buffer, ' ') == NULL) {
|
||||
esp_console_get_completion(buffer, completions);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t buffer_length = strlen(buffer);
|
||||
for (size_t index = 0;
|
||||
index < sizeof(s_completion_candidates) / sizeof(s_completion_candidates[0]);
|
||||
++index) {
|
||||
const char *const candidate = s_completion_candidates[index];
|
||||
const size_t candidate_length = strlen(candidate);
|
||||
|
||||
/* linenoise expects the complete replacement line, not only its suffix. */
|
||||
if (candidate_length > buffer_length &&
|
||||
strncmp(candidate, buffer, buffer_length) == 0) {
|
||||
linenoiseAddCompletion(completions, candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void console_completion_install(void)
|
||||
{
|
||||
linenoiseSetCompletionCallback(&console_completion_callback);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Install project-specific nested command completion for the console. */
|
||||
void console_completion_install(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "driver/uart.h"
|
||||
#include "console_completion.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_psram.h"
|
||||
#include "network_console.h"
|
||||
#include "rs232_hw_test.h"
|
||||
#include "rs232_port_owner.h"
|
||||
#include "serial_config.h"
|
||||
@@ -129,6 +131,9 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(session_console_register_commands());
|
||||
ESP_ERROR_CHECK(usb_console_register_commands());
|
||||
ESP_ERROR_CHECK(wifi_console_register_commands());
|
||||
ESP_ERROR_CHECK(network_console_register_root_commands());
|
||||
/* ESP-IDF handles root completion; this wrapper adds nested subcommands. */
|
||||
console_completion_install();
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
|
||||
ESP_LOGI(TAG, "Interactive test console ready at %d baud", CONSOLE_BAUD_RATE);
|
||||
|
||||
@@ -0,0 +1,842 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Root-level network diagnostics which operate on any active lwIP interface. */
|
||||
|
||||
#include "network_console.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "lwip/inet_chksum.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "lwip/prot/icmp.h"
|
||||
#include "lwip/prot/ip4.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "ping/ping_sock.h"
|
||||
|
||||
#define PING_DEFAULT_COUNT 4U
|
||||
#define PING_MIN_COUNT 1U
|
||||
#define PING_MAX_COUNT 20U
|
||||
#define TRACEROUTE_DEFAULT_HOPS 16U
|
||||
#define TRACEROUTE_MIN_HOPS 1U
|
||||
#define TRACEROUTE_MAX_HOPS 30U
|
||||
#define TRACEROUTE_TIMEOUT_US INT64_C(1000000)
|
||||
#define NUMERIC_ADDRESS_CAPACITY 48U
|
||||
|
||||
/* This covers two maximum-size IPv4 headers plus both required ICMP headers. */
|
||||
#define TRACEROUTE_REPLY_CAPACITY \
|
||||
(IP_HLEN_MAX + sizeof(struct icmp_hdr) + IP_HLEN_MAX + sizeof(struct icmp_echo_hdr))
|
||||
|
||||
static void print_command_usage(const char *command)
|
||||
{
|
||||
if (command != NULL && strcmp(command, "ping") == 0) {
|
||||
printf("Usage: ping <host> [count] (count: 1..20, default: 4)\n");
|
||||
} else if (command != NULL && strcmp(command, "nslookup") == 0) {
|
||||
printf("Usage: nslookup <host>\n");
|
||||
} else if (command != NULL && strcmp(command, "traceroute") == 0) {
|
||||
printf("Usage: traceroute <host> [max-hops] (IPv4 only; 1..30, default: 16)\n");
|
||||
} else {
|
||||
printf("Network commands: ping, nslookup, traceroute\n");
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_bounded_u32(const char *text, uint32_t minimum,
|
||||
uint32_t maximum, uint32_t *value)
|
||||
{
|
||||
if (text == NULL || value == NULL || *text == '\0') {
|
||||
return false;
|
||||
}
|
||||
for (const char *character = text; *character != '\0'; ++character) {
|
||||
if (*character < '0' || *character > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
unsigned long parsed = strtoul(text, &end, 10);
|
||||
if (errno != 0 || end == text || *end != '\0' ||
|
||||
parsed < minimum || parsed > maximum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = (uint32_t)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sockaddr_to_numeric(const struct sockaddr *address,
|
||||
socklen_t address_length,
|
||||
char *buffer, size_t buffer_size)
|
||||
{
|
||||
if (address == NULL || buffer == NULL || buffer_size == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const void *numeric_address = NULL;
|
||||
if (address->sa_family == AF_INET &&
|
||||
address_length >= (socklen_t)sizeof(struct sockaddr_in)) {
|
||||
numeric_address = &((const struct sockaddr_in *)address)->sin_addr;
|
||||
#if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6
|
||||
} else if (address->sa_family == AF_INET6 &&
|
||||
address_length >= (socklen_t)sizeof(struct sockaddr_in6)) {
|
||||
numeric_address = &((const struct sockaddr_in6 *)address)->sin6_addr;
|
||||
#endif
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return inet_ntop(address->sa_family, numeric_address, buffer,
|
||||
(socklen_t)buffer_size) != NULL;
|
||||
}
|
||||
|
||||
static bool addrinfo_to_ip_addr(const struct addrinfo *entry, ip_addr_t *target)
|
||||
{
|
||||
if (entry == NULL || entry->ai_addr == NULL || target == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->ai_family == AF_INET &&
|
||||
entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) {
|
||||
const struct sockaddr_in *socket_address =
|
||||
(const struct sockaddr_in *)entry->ai_addr;
|
||||
ip4_addr_t ipv4;
|
||||
inet_addr_to_ip4addr(&ipv4, &socket_address->sin_addr);
|
||||
ip_addr_copy_from_ip4(*target, ipv4);
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6
|
||||
if (entry->ai_family == AF_INET6 &&
|
||||
entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6)) {
|
||||
const struct sockaddr_in6 *socket_address =
|
||||
(const struct sockaddr_in6 *)entry->ai_addr;
|
||||
ip6_addr_t ipv6;
|
||||
inet6_addr_to_ip6addr(&ipv6, &socket_address->sin6_addr);
|
||||
ip_addr_copy_from_ip6(*target, ipv6);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int resolve_ping_target(const char *host, ip_addr_t *target,
|
||||
char *numeric, size_t numeric_size)
|
||||
{
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_RAW,
|
||||
};
|
||||
struct addrinfo *results = NULL;
|
||||
int resolver_result = getaddrinfo(host, NULL, &hints, &results);
|
||||
if (resolver_result != 0) {
|
||||
printf("ping: could not resolve '%s' (getaddrinfo error %d)\n",
|
||||
host, resolver_result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) {
|
||||
if (addrinfo_to_ip_addr(entry, target) &&
|
||||
sockaddr_to_numeric(entry->ai_addr, entry->ai_addrlen,
|
||||
numeric, numeric_size)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(results);
|
||||
|
||||
if (!found) {
|
||||
printf("ping: '%s' did not resolve to a supported IPv4 or IPv6 address\n", host);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
TaskHandle_t waiting_task;
|
||||
esp_err_t delete_error;
|
||||
bool received_reply;
|
||||
bool summary_valid;
|
||||
} ping_wait_context_t;
|
||||
|
||||
static void ping_on_success(esp_ping_handle_t handle, void *arguments)
|
||||
{
|
||||
(void)arguments;
|
||||
|
||||
uint16_t sequence = 0U;
|
||||
uint8_t ttl = 0U;
|
||||
uint32_t reply_size = 0U;
|
||||
uint32_t elapsed_ms = 0U;
|
||||
ip_addr_t reply_address;
|
||||
char numeric[NUMERIC_ADDRESS_CAPACITY];
|
||||
|
||||
if (esp_ping_get_profile(handle, ESP_PING_PROF_SEQNO,
|
||||
&sequence, sizeof(sequence)) != ESP_OK ||
|
||||
esp_ping_get_profile(handle, ESP_PING_PROF_SIZE,
|
||||
&reply_size, sizeof(reply_size)) != ESP_OK ||
|
||||
esp_ping_get_profile(handle, ESP_PING_PROF_TIMEGAP,
|
||||
&elapsed_ms, sizeof(elapsed_ms)) != ESP_OK ||
|
||||
esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
|
||||
&reply_address, sizeof(reply_address)) != ESP_OK ||
|
||||
ipaddr_ntoa_r(&reply_address, numeric, (int)sizeof(numeric)) == NULL) {
|
||||
printf("ping: received a reply but could not read its profile\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IP_IS_V4(&reply_address) &&
|
||||
esp_ping_get_profile(handle, ESP_PING_PROF_TTL,
|
||||
&ttl, sizeof(ttl)) == ESP_OK) {
|
||||
printf("%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
|
||||
" ttl=%u time=%" PRIu32 " ms\n",
|
||||
reply_size, numeric, sequence, (unsigned int)ttl, elapsed_ms);
|
||||
} else {
|
||||
printf("%" PRIu32 " bytes from %s: icmp_seq=%" PRIu16
|
||||
" time=%" PRIu32 " ms\n",
|
||||
reply_size, numeric, sequence, elapsed_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static void ping_on_timeout(esp_ping_handle_t handle, void *arguments)
|
||||
{
|
||||
(void)arguments;
|
||||
|
||||
uint16_t sequence = 0U;
|
||||
ip_addr_t target_address;
|
||||
char numeric[NUMERIC_ADDRESS_CAPACITY] = "?";
|
||||
|
||||
if (esp_ping_get_profile(handle, ESP_PING_PROF_SEQNO,
|
||||
&sequence, sizeof(sequence)) == ESP_OK &&
|
||||
esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
|
||||
&target_address, sizeof(target_address)) == ESP_OK) {
|
||||
(void)ipaddr_ntoa_r(&target_address, numeric, (int)sizeof(numeric));
|
||||
}
|
||||
printf("From %s: icmp_seq=%" PRIu16 " timeout\n", numeric, sequence);
|
||||
}
|
||||
|
||||
static void ping_on_end(esp_ping_handle_t handle, void *arguments)
|
||||
{
|
||||
ping_wait_context_t *context = (ping_wait_context_t *)arguments;
|
||||
uint32_t transmitted = 0U;
|
||||
uint32_t received = 0U;
|
||||
uint32_t duration_ms = 0U;
|
||||
ip_addr_t target_address;
|
||||
char numeric[NUMERIC_ADDRESS_CAPACITY] = "?";
|
||||
|
||||
esp_err_t profile_error = esp_ping_get_profile(
|
||||
handle, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
|
||||
if (profile_error == ESP_OK) {
|
||||
profile_error = esp_ping_get_profile(
|
||||
handle, ESP_PING_PROF_REPLY, &received, sizeof(received));
|
||||
}
|
||||
if (profile_error == ESP_OK) {
|
||||
profile_error = esp_ping_get_profile(
|
||||
handle, ESP_PING_PROF_DURATION, &duration_ms, sizeof(duration_ms));
|
||||
}
|
||||
if (esp_ping_get_profile(handle, ESP_PING_PROF_IPADDR,
|
||||
&target_address, sizeof(target_address)) == ESP_OK) {
|
||||
(void)ipaddr_ntoa_r(&target_address, numeric, (int)sizeof(numeric));
|
||||
}
|
||||
|
||||
if (profile_error == ESP_OK) {
|
||||
context->received_reply = received > 0U;
|
||||
context->summary_valid = true;
|
||||
uint32_t loss_percent = transmitted == 0U
|
||||
? 0U
|
||||
: ((transmitted - received) * 100U) / transmitted;
|
||||
printf("\n--- %s ping statistics ---\n", numeric);
|
||||
printf("%" PRIu32 " packets transmitted, %" PRIu32
|
||||
" received, %" PRIu32 "%% packet loss, time %" PRIu32 " ms\n",
|
||||
transmitted, received, loss_percent, duration_ms);
|
||||
} else {
|
||||
printf("ping: session ended, but summary profile retrieval failed: %s\n",
|
||||
esp_err_to_name(profile_error));
|
||||
}
|
||||
|
||||
/* Stop ping_sock's task before waking the higher-priority console caller. */
|
||||
context->delete_error = esp_ping_delete_session(handle);
|
||||
xTaskNotifyGive(context->waiting_task);
|
||||
}
|
||||
|
||||
static int execute_ping(int argc, char **argv)
|
||||
{
|
||||
if ((argc != 2 && argc != 3) || argv[1] == NULL || *argv[1] == '\0') {
|
||||
print_command_usage("ping");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t count = PING_DEFAULT_COUNT;
|
||||
if (argc == 3 &&
|
||||
!parse_bounded_u32(argv[2], PING_MIN_COUNT, PING_MAX_COUNT, &count)) {
|
||||
print_command_usage("ping");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ip_addr_t target;
|
||||
char numeric[NUMERIC_ADDRESS_CAPACITY];
|
||||
if (resolve_ping_target(argv[1], &target, numeric, sizeof(numeric)) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ping_wait_context_t context = {
|
||||
.waiting_task = xTaskGetCurrentTaskHandle(),
|
||||
.delete_error = ESP_FAIL,
|
||||
};
|
||||
/* Remove any unrelated notification before this command begins waiting. */
|
||||
(void)ulTaskNotifyTake(pdTRUE, 0U);
|
||||
|
||||
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
|
||||
config.count = count;
|
||||
config.target_addr = target;
|
||||
|
||||
const esp_ping_callbacks_t callbacks = {
|
||||
.cb_args = &context,
|
||||
.on_ping_success = ping_on_success,
|
||||
.on_ping_timeout = ping_on_timeout,
|
||||
.on_ping_end = ping_on_end,
|
||||
};
|
||||
|
||||
esp_ping_handle_t session = NULL;
|
||||
esp_err_t error = esp_ping_new_session(&config, &callbacks, &session);
|
||||
if (error != ESP_OK) {
|
||||
printf("ping: could not create session: %s\n", esp_err_to_name(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("PING %s (%s): %" PRIu32 " probes\n", argv[1], numeric, count);
|
||||
error = esp_ping_start(session);
|
||||
if (error != ESP_OK) {
|
||||
printf("ping: could not start session: %s\n", esp_err_to_name(error));
|
||||
(void)esp_ping_delete_session(session);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Finite count guarantees on_ping_end; blocking keeps console output ordered. */
|
||||
if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) == 0U) {
|
||||
printf("ping: wait for session completion failed\n");
|
||||
(void)esp_ping_stop(session);
|
||||
(void)esp_ping_delete_session(session);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool command_succeeded = context.summary_valid && context.received_reply;
|
||||
if (context.delete_error != ESP_OK) {
|
||||
printf("ping: could not delete session: %s\n",
|
||||
esp_err_to_name(context.delete_error));
|
||||
return 1;
|
||||
}
|
||||
return command_succeeded ? 0 : 1;
|
||||
}
|
||||
|
||||
static bool socket_addresses_equal(const struct addrinfo *left,
|
||||
const struct addrinfo *right)
|
||||
{
|
||||
if (left == NULL || right == NULL || left->ai_addr == NULL ||
|
||||
right->ai_addr == NULL || left->ai_family != right->ai_family) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (left->ai_family == AF_INET &&
|
||||
left->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in) &&
|
||||
right->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) {
|
||||
const struct sockaddr_in *left_address =
|
||||
(const struct sockaddr_in *)left->ai_addr;
|
||||
const struct sockaddr_in *right_address =
|
||||
(const struct sockaddr_in *)right->ai_addr;
|
||||
return left_address->sin_addr.s_addr == right_address->sin_addr.s_addr;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_LWIP_IPV6) && CONFIG_LWIP_IPV6
|
||||
if (left->ai_family == AF_INET6 &&
|
||||
left->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6) &&
|
||||
right->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in6)) {
|
||||
const struct sockaddr_in6 *left_address =
|
||||
(const struct sockaddr_in6 *)left->ai_addr;
|
||||
const struct sockaddr_in6 *right_address =
|
||||
(const struct sockaddr_in6 *)right->ai_addr;
|
||||
return memcmp(&left_address->sin6_addr, &right_address->sin6_addr,
|
||||
sizeof(left_address->sin6_addr)) == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool address_appeared_earlier(const struct addrinfo *first,
|
||||
const struct addrinfo *current)
|
||||
{
|
||||
for (const struct addrinfo *entry = first;
|
||||
entry != NULL && entry != current; entry = entry->ai_next) {
|
||||
if (socket_addresses_equal(entry, current)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int execute_nslookup(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2 || argv[1] == NULL || *argv[1] == '\0') {
|
||||
print_command_usage("nslookup");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
};
|
||||
struct addrinfo *results = NULL;
|
||||
int resolver_result = getaddrinfo(argv[1], NULL, &hints, &results);
|
||||
if (resolver_result != 0) {
|
||||
printf("nslookup: could not resolve '%s' (getaddrinfo error %d)\n",
|
||||
argv[1], resolver_result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Name: %s\n", argv[1]);
|
||||
size_t printed = 0U;
|
||||
for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) {
|
||||
if (address_appeared_earlier(results, entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char numeric[NUMERIC_ADDRESS_CAPACITY];
|
||||
if (!sockaddr_to_numeric(entry->ai_addr, entry->ai_addrlen,
|
||||
numeric, sizeof(numeric))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Address: %s (%s)\n", numeric,
|
||||
entry->ai_family == AF_INET ? "IPv4" : "IPv6");
|
||||
++printed;
|
||||
}
|
||||
freeaddrinfo(results);
|
||||
|
||||
if (printed == 0U) {
|
||||
printf("nslookup: no supported IPv4 or IPv6 addresses returned\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int resolve_traceroute_target(const char *host,
|
||||
struct sockaddr_in *target,
|
||||
char *numeric, size_t numeric_size)
|
||||
{
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_INET,
|
||||
.ai_socktype = SOCK_RAW,
|
||||
.ai_protocol = IPPROTO_ICMP,
|
||||
};
|
||||
struct addrinfo *results = NULL;
|
||||
int resolver_result = getaddrinfo(host, NULL, &hints, &results);
|
||||
if (resolver_result != 0) {
|
||||
printf("traceroute: could not resolve IPv4 host '%s' (getaddrinfo error %d)\n",
|
||||
host, resolver_result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct addrinfo *selected = NULL;
|
||||
for (const struct addrinfo *entry = results; entry != NULL; entry = entry->ai_next) {
|
||||
if (entry->ai_family == AF_INET && entry->ai_addr != NULL &&
|
||||
entry->ai_addrlen >= (socklen_t)sizeof(struct sockaddr_in)) {
|
||||
selected = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected == NULL ||
|
||||
!sockaddr_to_numeric(selected->ai_addr, selected->ai_addrlen,
|
||||
numeric, numeric_size)) {
|
||||
freeaddrinfo(results);
|
||||
printf("traceroute: '%s' did not resolve to an IPv4 address\n", host);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(target, selected->ai_addr, sizeof(*target));
|
||||
freeaddrinfo(results);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TRACE_REPLY_UNRELATED,
|
||||
TRACE_REPLY_HOP,
|
||||
TRACE_REPLY_DESTINATION,
|
||||
TRACE_REPLY_UNREACHABLE,
|
||||
} trace_reply_kind_t;
|
||||
|
||||
static trace_reply_kind_t parse_trace_reply(const uint8_t *packet, size_t length,
|
||||
uint16_t expected_id,
|
||||
uint16_t expected_sequence,
|
||||
uint32_t expected_destination,
|
||||
uint8_t *unreachable_code)
|
||||
{
|
||||
if (packet == NULL || length < IP_HLEN + sizeof(struct icmp_hdr)) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
const struct ip_hdr *outer_ip = (const struct ip_hdr *)(const void *)packet;
|
||||
size_t outer_header_length = IPH_HL_BYTES(outer_ip);
|
||||
if (IPH_V(outer_ip) != 4U || outer_header_length < IP_HLEN ||
|
||||
outer_header_length > length ||
|
||||
length - outer_header_length < sizeof(struct icmp_hdr) ||
|
||||
IPH_PROTO(outer_ip) != IPPROTO_ICMP) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
uint16_t outer_total_length = lwip_ntohs(IPH_LEN(outer_ip));
|
||||
if (outer_total_length < outer_header_length + sizeof(struct icmp_hdr)) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
size_t available = length;
|
||||
if ((size_t)outer_total_length < available) {
|
||||
available = outer_total_length;
|
||||
}
|
||||
|
||||
const uint8_t *outer_icmp_bytes = packet + outer_header_length;
|
||||
const struct icmp_hdr *outer_icmp =
|
||||
(const struct icmp_hdr *)(const void *)outer_icmp_bytes;
|
||||
|
||||
if (ICMPH_TYPE(outer_icmp) == ICMP_ER) {
|
||||
const struct icmp_echo_hdr *echo_reply =
|
||||
(const struct icmp_echo_hdr *)(const void *)outer_icmp_bytes;
|
||||
if (echo_reply->id == expected_id &&
|
||||
echo_reply->seqno == expected_sequence) {
|
||||
return TRACE_REPLY_DESTINATION;
|
||||
}
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
if (ICMPH_TYPE(outer_icmp) != ICMP_TE &&
|
||||
ICMPH_TYPE(outer_icmp) != ICMP_DUR) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
/* ICMP errors quote the original IPv4 header and at least 8 payload bytes. */
|
||||
size_t inner_offset = outer_header_length + sizeof(struct icmp_hdr);
|
||||
if (inner_offset > available || available - inner_offset < IP_HLEN) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
const struct ip_hdr *inner_ip =
|
||||
(const struct ip_hdr *)(const void *)(packet + inner_offset);
|
||||
size_t inner_header_length = IPH_HL_BYTES(inner_ip);
|
||||
if (IPH_V(inner_ip) != 4U || inner_header_length < IP_HLEN ||
|
||||
inner_header_length > available - inner_offset ||
|
||||
available - inner_offset - inner_header_length < sizeof(struct icmp_echo_hdr) ||
|
||||
IPH_PROTO(inner_ip) != IPPROTO_ICMP ||
|
||||
inner_ip->dest.addr != expected_destination ||
|
||||
lwip_ntohs(IPH_LEN(inner_ip)) <
|
||||
inner_header_length + sizeof(struct icmp_echo_hdr)) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
const struct icmp_echo_hdr *quoted_echo =
|
||||
(const struct icmp_echo_hdr *)(const void *)(
|
||||
packet + inner_offset + inner_header_length);
|
||||
if (ICMPH_TYPE((const struct icmp_hdr *)quoted_echo) != ICMP_ECHO ||
|
||||
quoted_echo->id != expected_id ||
|
||||
quoted_echo->seqno != expected_sequence) {
|
||||
return TRACE_REPLY_UNRELATED;
|
||||
}
|
||||
|
||||
if (ICMPH_TYPE(outer_icmp) == ICMP_DUR) {
|
||||
if (unreachable_code != NULL) {
|
||||
*unreachable_code = ICMPH_CODE(outer_icmp);
|
||||
}
|
||||
return TRACE_REPLY_UNREACHABLE;
|
||||
}
|
||||
return TRACE_REPLY_HOP;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TRACE_WAIT_ERROR = -1,
|
||||
TRACE_WAIT_TIMEOUT = 0,
|
||||
TRACE_WAIT_HOP,
|
||||
TRACE_WAIT_DESTINATION,
|
||||
TRACE_WAIT_UNREACHABLE,
|
||||
} trace_wait_result_t;
|
||||
|
||||
static trace_wait_result_t wait_for_trace_reply(int socket_fd,
|
||||
const struct sockaddr_in *target,
|
||||
uint16_t expected_id,
|
||||
uint16_t expected_sequence,
|
||||
int64_t sent_at_us,
|
||||
char *source_numeric,
|
||||
size_t source_numeric_size,
|
||||
int64_t *round_trip_us,
|
||||
uint8_t *unreachable_code)
|
||||
{
|
||||
int64_t deadline_us = sent_at_us + TRACEROUTE_TIMEOUT_US;
|
||||
uint8_t reply[TRACEROUTE_REPLY_CAPACITY];
|
||||
|
||||
for (;;) {
|
||||
int64_t remaining_us = deadline_us - esp_timer_get_time();
|
||||
if (remaining_us <= 0) {
|
||||
return TRACE_WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
/* Reduce the socket timeout after unrelated traffic to keep one second total. */
|
||||
struct timeval receive_timeout = {
|
||||
.tv_sec = (long)(remaining_us / INT64_C(1000000)),
|
||||
.tv_usec = (long)(remaining_us % INT64_C(1000000)),
|
||||
};
|
||||
if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&receive_timeout, sizeof(receive_timeout)) != 0) {
|
||||
return TRACE_WAIT_ERROR;
|
||||
}
|
||||
|
||||
struct sockaddr_in source = {0};
|
||||
socklen_t source_length = sizeof(source);
|
||||
ssize_t received = recvfrom(socket_fd, reply, sizeof(reply), 0,
|
||||
(struct sockaddr *)&source, &source_length);
|
||||
if (received < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ETIMEDOUT) {
|
||||
return TRACE_WAIT_TIMEOUT;
|
||||
}
|
||||
return TRACE_WAIT_ERROR;
|
||||
}
|
||||
|
||||
trace_reply_kind_t kind = parse_trace_reply(
|
||||
reply, (size_t)received, expected_id, expected_sequence,
|
||||
target->sin_addr.s_addr, unreachable_code);
|
||||
if (kind == TRACE_REPLY_UNRELATED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* An echo reply is the destination only when it came from our target. */
|
||||
if (kind == TRACE_REPLY_DESTINATION &&
|
||||
source.sin_addr.s_addr != target->sin_addr.s_addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sockaddr_to_numeric((const struct sockaddr *)&source, source_length,
|
||||
source_numeric, source_numeric_size)) {
|
||||
(void)snprintf(source_numeric, source_numeric_size, "?");
|
||||
}
|
||||
*round_trip_us = esp_timer_get_time() - sent_at_us;
|
||||
|
||||
switch (kind) {
|
||||
case TRACE_REPLY_HOP:
|
||||
return TRACE_WAIT_HOP;
|
||||
case TRACE_REPLY_DESTINATION:
|
||||
return TRACE_WAIT_DESTINATION;
|
||||
case TRACE_REPLY_UNREACHABLE:
|
||||
return TRACE_WAIT_UNREACHABLE;
|
||||
default:
|
||||
return TRACE_WAIT_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void print_trace_rtt(int64_t round_trip_us)
|
||||
{
|
||||
if (round_trip_us < 0) {
|
||||
round_trip_us = 0;
|
||||
}
|
||||
printf("%" PRId64 ".%03" PRId64 " ms",
|
||||
round_trip_us / INT64_C(1000), round_trip_us % INT64_C(1000));
|
||||
}
|
||||
|
||||
static int execute_traceroute(int argc, char **argv)
|
||||
{
|
||||
if ((argc != 2 && argc != 3) || argv[1] == NULL || *argv[1] == '\0') {
|
||||
print_command_usage("traceroute");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t max_hops = TRACEROUTE_DEFAULT_HOPS;
|
||||
if (argc == 3 &&
|
||||
!parse_bounded_u32(argv[2], TRACEROUTE_MIN_HOPS,
|
||||
TRACEROUTE_MAX_HOPS, &max_hops)) {
|
||||
print_command_usage("traceroute");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("traceroute: IPv4 only (one ICMP echo probe per hop)\n");
|
||||
|
||||
struct sockaddr_in target = {0};
|
||||
char target_numeric[NUMERIC_ADDRESS_CAPACITY];
|
||||
if (resolve_traceroute_target(argv[1], &target,
|
||||
target_numeric, sizeof(target_numeric)) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int socket_fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
if (socket_fd < 0) {
|
||||
printf("traceroute: could not create raw ICMP socket: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct timeval one_second = {
|
||||
.tv_sec = 1,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
if (setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&one_second, sizeof(one_second)) != 0) {
|
||||
printf("traceroute: could not set 1 s receive timeout: %s\n", strerror(errno));
|
||||
close(socket_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("traceroute to %s (%s), %" PRIu32 " hops max\n",
|
||||
argv[1], target_numeric, max_hops);
|
||||
|
||||
/* A per-run ID plus one sequence per hop rejects other raw-socket traffic. */
|
||||
uint16_t identifier = lwip_htons((uint16_t)esp_timer_get_time());
|
||||
bool stopped = false;
|
||||
bool reached = false;
|
||||
uint32_t final_hop = 0U;
|
||||
|
||||
for (uint32_t hop = 1U; hop <= max_hops; ++hop) {
|
||||
int ttl = (int)hop;
|
||||
if (setsockopt(socket_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) {
|
||||
printf("traceroute: could not set TTL for hop %" PRIu32 ": %s\n",
|
||||
hop, strerror(errno));
|
||||
close(socket_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct icmp_echo_hdr probe = {
|
||||
.type = ICMP_ECHO,
|
||||
.code = 0U,
|
||||
.chksum = 0U,
|
||||
.id = identifier,
|
||||
.seqno = lwip_htons((uint16_t)hop),
|
||||
};
|
||||
probe.chksum = inet_chksum(&probe, (u16_t)sizeof(probe));
|
||||
|
||||
int64_t sent_at_us = esp_timer_get_time();
|
||||
ssize_t sent = sendto(socket_fd, &probe, sizeof(probe), 0,
|
||||
(const struct sockaddr *)&target, sizeof(target));
|
||||
if (sent != (ssize_t)sizeof(probe)) {
|
||||
printf("traceroute: probe send failed at hop %" PRIu32 ": %s\n",
|
||||
hop, strerror(errno));
|
||||
close(socket_fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char source_numeric[NUMERIC_ADDRESS_CAPACITY];
|
||||
int64_t round_trip_us = 0;
|
||||
uint8_t unreachable_code = 0U;
|
||||
trace_wait_result_t result = wait_for_trace_reply(
|
||||
socket_fd, &target, probe.id, probe.seqno, sent_at_us,
|
||||
source_numeric, sizeof(source_numeric), &round_trip_us,
|
||||
&unreachable_code);
|
||||
|
||||
if (result == TRACE_WAIT_ERROR) {
|
||||
printf("traceroute: receive failed at hop %" PRIu32 ": %s\n",
|
||||
hop, strerror(errno));
|
||||
close(socket_fd);
|
||||
return 1;
|
||||
}
|
||||
if (result == TRACE_WAIT_TIMEOUT) {
|
||||
printf("%2" PRIu32 " *\n", hop);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("%2" PRIu32 " %-15s ", hop, source_numeric);
|
||||
print_trace_rtt(round_trip_us);
|
||||
if (result == TRACE_WAIT_UNREACHABLE) {
|
||||
printf(" !U (ICMP code %u)", (unsigned int)unreachable_code);
|
||||
}
|
||||
putchar('\n');
|
||||
|
||||
if (result == TRACE_WAIT_DESTINATION || result == TRACE_WAIT_UNREACHABLE) {
|
||||
stopped = true;
|
||||
reached = result == TRACE_WAIT_DESTINATION;
|
||||
final_hop = hop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close(socket_fd);
|
||||
if (reached) {
|
||||
printf("Trace complete: destination reached at hop %" PRIu32 ".\n", final_hop);
|
||||
} else if (stopped) {
|
||||
printf("Trace stopped: destination unreachable at hop %" PRIu32 ".\n", final_hop);
|
||||
} else {
|
||||
printf("Trace complete: destination not reached within %" PRIu32 " hops.\n",
|
||||
max_hops);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool network_console_is_command(const char *name)
|
||||
{
|
||||
return name != NULL &&
|
||||
(strcmp(name, "ping") == 0 ||
|
||||
strcmp(name, "nslookup") == 0 ||
|
||||
strcmp(name, "traceroute") == 0);
|
||||
}
|
||||
|
||||
int network_console_execute(int argc, char **argv)
|
||||
{
|
||||
if (argc <= 0 || argv == NULL || argv[0] == NULL) {
|
||||
print_command_usage(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[0], "ping") == 0) {
|
||||
return execute_ping(argc, argv);
|
||||
}
|
||||
if (strcmp(argv[0], "nslookup") == 0) {
|
||||
return execute_nslookup(argc, argv);
|
||||
}
|
||||
if (strcmp(argv[0], "traceroute") == 0) {
|
||||
return execute_traceroute(argc, argv);
|
||||
}
|
||||
|
||||
printf("Unknown network command '%s'.\n", argv[0]);
|
||||
print_command_usage(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t network_console_register_root_commands(void)
|
||||
{
|
||||
/* All aliases intentionally point at the public dispatcher. */
|
||||
static const esp_console_cmd_t commands[] = {
|
||||
{
|
||||
.command = "ping",
|
||||
.help = "ping <host> [count] (count 1..20, default 4)",
|
||||
.hint = NULL,
|
||||
.func = &network_console_execute,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "nslookup",
|
||||
.help = "nslookup <host> (print unique numeric IPv4/IPv6 addresses)",
|
||||
.hint = NULL,
|
||||
.func = &network_console_execute,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "traceroute",
|
||||
.help = "traceroute <host> [max-hops] (IPv4 only; 1..30, default 16)",
|
||||
.hint = NULL,
|
||||
.func = &network_console_execute,
|
||||
.argtable = NULL,
|
||||
},
|
||||
};
|
||||
|
||||
for (size_t index = 0U; index < sizeof(commands) / sizeof(commands[0]); ++index) {
|
||||
esp_err_t error = esp_console_cmd_register(&commands[index]);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Register ping, nslookup, and traceroute as root-level console commands. */
|
||||
esp_err_t network_console_register_root_commands(void);
|
||||
|
||||
/* Return true only for a root command handled by network_console_execute(). */
|
||||
bool network_console_is_command(const char *name);
|
||||
|
||||
/* Execute ping, nslookup, or traceroute; argv[0] selects the operation. */
|
||||
int network_console_execute(int argc, char **argv);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+54
-59
@@ -1575,6 +1575,57 @@ DEFINE_OWNED_COMMAND(uart_suite)
|
||||
DEFINE_OWNED_COMMAND(cts_flow_test)
|
||||
DEFINE_OWNED_COMMAND(rts_flow_test)
|
||||
|
||||
static void print_debug_usage(void)
|
||||
{
|
||||
printf("Usage:\n");
|
||||
printf(" debug transceiver <enable|disable>\n");
|
||||
printf(" debug drivers <TX 0|1> <DTR 0|1> <RTS 0|1>\n");
|
||||
printf(" debug loopback-a|loopback-b|valid-test\n");
|
||||
printf(" debug uart-loopback <baud> [format] [bytes]\n");
|
||||
printf(" debug uart-suite|cts-flow-test|rts-flow-test\n");
|
||||
}
|
||||
|
||||
static int command_debug(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
print_debug_usage();
|
||||
return argc == 1 ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Existing handlers expect their own command name in argv[0]. */
|
||||
if (strcmp(argv[1], "transceiver") == 0) {
|
||||
return command_transceiver(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "drivers") == 0) {
|
||||
return command_drivers(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "loopback-a") == 0) {
|
||||
return command_loopback_a(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "loopback-b") == 0) {
|
||||
return command_loopback_b(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "valid-test") == 0) {
|
||||
return command_valid_test(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "uart-loopback") == 0) {
|
||||
return command_uart_loopback(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "uart-suite") == 0) {
|
||||
return command_uart_suite(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "cts-flow-test") == 0) {
|
||||
return command_cts_flow_test(argc - 1, argv + 1);
|
||||
}
|
||||
if (strcmp(argv[1], "rts-flow-test") == 0) {
|
||||
return command_rts_flow_test(argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
printf("Unknown debug command '%s'.\n", argv[1]);
|
||||
print_debug_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
esp_err_t rs232_hw_test_init(void)
|
||||
{
|
||||
s_transceiver_enabled = true;
|
||||
@@ -1598,66 +1649,10 @@ esp_err_t rs232_hw_test_register_console_commands(void)
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "transceiver",
|
||||
.help = "Control active-low OFF: transceiver <enable|disable>",
|
||||
.command = "debug",
|
||||
.help = "Low-level RS-232 hardware diagnostics; run 'debug' for subcommands",
|
||||
.hint = NULL,
|
||||
.func = &command_transceiver,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "drivers",
|
||||
.help = "Set static logic levels: drivers <TX 0|1> <DTR 0|1> <RTS 0|1>",
|
||||
.hint = NULL,
|
||||
.func = &command_drivers,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "loopback-a",
|
||||
.help = "Test TX->RX, DTR->DSR, RTS->CTS for all eight patterns",
|
||||
.hint = NULL,
|
||||
.func = &command_loopback_a,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "loopback-b",
|
||||
.help = "Test TX->DCD, DTR->RI, RTS->RX for all eight patterns",
|
||||
.hint = NULL,
|
||||
.func = &command_loopback_b,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "valid-test",
|
||||
.help = "Verify VLD while enabled, shut down, and re-enabled",
|
||||
.hint = NULL,
|
||||
.func = &command_valid_test,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "uart-loopback",
|
||||
.help = "Run one UART1 test: uart-loopback <baud> [format] [bytes]",
|
||||
.hint = NULL,
|
||||
.func = &command_uart_loopback,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "uart-suite",
|
||||
.help = "Run the predefined baud-rate and frame-format loopback suite",
|
||||
.hint = NULL,
|
||||
.func = &command_uart_suite,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "cts-flow-test",
|
||||
.help = "Verify that UART1 CTS blocks and resumes an exact transmission",
|
||||
.hint = NULL,
|
||||
.func = &command_cts_flow_test,
|
||||
.argtable = NULL,
|
||||
},
|
||||
{
|
||||
.command = "rts-flow-test",
|
||||
.help = "Verify automatic UART1 RTS backpressure with a UART2 generator",
|
||||
.hint = NULL,
|
||||
.func = &command_rts_flow_test,
|
||||
.func = &command_debug,
|
||||
.argtable = NULL,
|
||||
},
|
||||
};
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@
|
||||
/* Configure all MAX3243 logic-side signals in their safe static-test state. */
|
||||
esp_err_t rs232_hw_test_init(void);
|
||||
|
||||
/* Register the Phase 0 hardware-characterization commands with esp_console. */
|
||||
/* Register top-level status and the Phase 0 `debug` submenu. */
|
||||
esp_err_t rs232_hw_test_register_console_commands(void);
|
||||
|
||||
+10
-3
@@ -140,12 +140,19 @@ static int queue_writer_request(bool request)
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
printf("Usage: usb status|counters|clear-counters|request-writer|release-writer\n");
|
||||
printf("Usage:\n");
|
||||
printf(" usb status\n");
|
||||
printf(" usb counters|clear-counters\n");
|
||||
printf(" usb request-writer|release-writer\n");
|
||||
}
|
||||
|
||||
static int command_usb(int argc, char **argv)
|
||||
{
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "help") == 0)) {
|
||||
print_usage();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "status") == 0) {
|
||||
return show_status();
|
||||
}
|
||||
if (argc == 2 && strcmp(argv[1], "counters") == 0) {
|
||||
@@ -175,7 +182,7 @@ esp_err_t usb_console_register_commands(void)
|
||||
{
|
||||
const esp_console_cmd_t command = {
|
||||
.command = "usb",
|
||||
.help = "Inspect native USB CDC and manage its broker writer request",
|
||||
.help = "Inspect native USB CDC and manage writer ownership; run 'usb' for subcommands",
|
||||
.hint = NULL,
|
||||
.func = &command_usb,
|
||||
.argtable = NULL,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "esp_wifi_types.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "network_console.h"
|
||||
#include "wifi_config.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
@@ -35,6 +36,9 @@ static void print_usage(void)
|
||||
printf(" wifi ap channel <1..11>\n");
|
||||
printf(" wifi ap secret|show-secret\n");
|
||||
printf(" wifi save|load|defaults|reset\n");
|
||||
printf(" wifi ping <host> [count]\n");
|
||||
printf(" wifi nslookup <host>\n");
|
||||
printf(" wifi traceroute <host> [max-hops]\n");
|
||||
}
|
||||
|
||||
static bool parse_u32(const char *text, uint32_t maximum, uint32_t *value)
|
||||
@@ -589,6 +593,10 @@ static int queue_lifecycle(const char *operation)
|
||||
|
||||
static int command_wifi(int argc, char **argv)
|
||||
{
|
||||
if (argc >= 2 && network_console_is_command(argv[1])) {
|
||||
/* Shift `wifi` away so aliases and subcommands share one implementation. */
|
||||
return network_console_execute(argc - 1, argv + 1);
|
||||
}
|
||||
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||
return show_status();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user