Add Network Tools And Nested Command Completion
This commit is contained in:
@@ -66,7 +66,9 @@ pio run --target upload
|
|||||||
pio device monitor -b 115200
|
pio device monitor -b 115200
|
||||||
```
|
```
|
||||||
|
|
||||||
The firmware starts an interactive console on UART0 with the prompt `serial-tool>`. Type `help` to display command descriptions. This USB-to-UART device normally appears as `/dev/ttyUSB*`; it is separate from the native USB CDC serial transport described below.
|
The firmware starts an interactive console on UART0 with the prompt `serial-tool>`. Type `help` to display concise root-command descriptions. This USB-to-UART device normally appears as `/dev/ttyUSB*`; it is separate from the native USB CDC serial transport described below.
|
||||||
|
|
||||||
|
The console provides line editing, history for the current session, terminal-aware cursor movement, and Tab completion. ESP-IDF supplies completion for root commands; the project extends it to nested `debug`, `serial`, `broker`, `usb`, and `wifi` subcommands plus safe fixed values such as AP policy and serial framing. Password values are intentionally never completion candidates.
|
||||||
|
|
||||||
### Phase 1 serial service
|
### Phase 1 serial service
|
||||||
|
|
||||||
@@ -137,6 +139,8 @@ The ESP32-S3's native USB OTG peripheral presents one CDC-ACM serial interface t
|
|||||||
The UART0 development console provides these diagnostics and controls:
|
The UART0 development console provides these diagnostics and controls:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
usb
|
||||||
|
usb help
|
||||||
usb status
|
usb status
|
||||||
usb counters
|
usb counters
|
||||||
usb clear-counters
|
usb clear-counters
|
||||||
@@ -144,6 +148,8 @@ usb request-writer
|
|||||||
usb release-writer
|
usb release-writer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Both `usb` and `usb help` print the same multi-line command summary and return successfully; runtime state is shown explicitly with `usb status`.
|
||||||
|
|
||||||
Opening the CDC port with DTR asserted automatically starts UART1, connects a broker client named `usb-cdc`, and requests the writer lease. If another client already owns the lease, USB remains connected as a read-only observer; `usb status` reports its current role. Closing the port or unplugging native USB disconnects that broker client and discards transport-local pending data. The serial service itself remains running until it is stopped explicitly with `serial stop`.
|
Opening the CDC port with DTR asserted automatically starts UART1, connects a broker client named `usb-cdc`, and requests the writer lease. If another client already owns the lease, USB remains connected as a read-only observer; `usb status` reports its current role. Closing the port or unplugging native USB disconnects that broker client and discards transport-local pending data. The serial service itself remains running until it is stopped explicitly with `serial stop`.
|
||||||
|
|
||||||
The data path is binary-transparent. UTF-8 bytes, NUL bytes, terminal escape sequences, and color sequences are passed unchanged; interpretation remains the terminal application's responsibility. USB output is bounded and nonblocking, so a host that stops reading can lose only its own observer data rather than stall UART1 or another client.
|
The data path is binary-transparent. UTF-8 bytes, NUL bytes, terminal escape sequences, and color sequences are passed unchanged; interpretation remains the terminal application's responsibility. USB output is bounded and nonblocking, so a host that stops reading can lose only its own observer data rather than stall UART1 or another client.
|
||||||
@@ -219,8 +225,13 @@ wifi ap channel <1..11>
|
|||||||
wifi ap secret|show-secret
|
wifi ap secret|show-secret
|
||||||
wifi save|load|defaults|reset
|
wifi save|load|defaults|reset
|
||||||
wifi counters|clear-counters
|
wifi counters|clear-counters
|
||||||
|
wifi ping <host> [count]
|
||||||
|
wifi nslookup <host>
|
||||||
|
wifi traceroute <host> [max-hops]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The network diagnostics are also registered as root aliases, so `ping`, `nslookup`, and `traceroute` are equivalent to their `wifi`-prefixed forms. `ping` accepts 1–20 probes and supports IPv4 or IPv6. `nslookup` prints unique numeric IPv4/IPv6 results. `traceroute` is currently IPv4-only, sends one ICMP Echo probe per hop, accepts 1–30 hops, and uses a one-second timeout per hop; routers that suppress ICMP replies appear as `*`.
|
||||||
|
|
||||||
Ordinary status and profile output never displays passwords. `wifi profile secret` and `wifi ap secret` read through a dedicated no-echo UART0 prompt, keeping credentials out of the command line and its history. SSIDs containing spaces can be quoted. Profile and AP edits apply to the working RAM configuration and restart Wi-Fi asynchronously if it is running; use `wifi save` explicitly to persist them. `wifi start` and `wifi stop` also change the working `enabled-at-boot` setting, which becomes persistent only after `wifi save`.
|
Ordinary status and profile output never displays passwords. `wifi profile secret` and `wifi ap secret` read through a dedicated no-echo UART0 prompt, keeping credentials out of the command line and its history. SSIDs containing spaces can be quoted. Profile and AP edits apply to the working RAM configuration and restart Wi-Fi asynchronously if it is running; use `wifi save` explicitly to persist them. `wifi start` and `wifi stop` also change the working `enabled-at-boot` setting, which becomes persistent only after `wifi save`.
|
||||||
|
|
||||||
A typical station setup is:
|
A typical station setup is:
|
||||||
@@ -241,7 +252,7 @@ Wi-Fi credentials currently reside as plaintext in the application-owned `wifi_a
|
|||||||
#### Wi-Fi validation
|
#### Wi-Fi validation
|
||||||
|
|
||||||
1. Boot with no station profiles. `wifi status` should report `ap-only`, and the generated SSID should be visible from another device.
|
1. Boot with no station profiles. `wifi status` should report `ap-only`, and the generated SSID should be visible from another device.
|
||||||
2. Use `wifi ap show-secret`, join the AP, confirm a `192.168.4.x` lease, and ping `192.168.4.1`. No web page is expected yet.
|
2. Use `wifi ap show-secret`, join the AP, confirm a `192.168.4.x` lease, and run `wifi ping 192.168.4.1`. No web page is expected yet.
|
||||||
3. Configure and enable a WPA2/WPA3 station profile using the example above. `wifi status` should progress through `connecting`, `waiting-ip`, and `online` and display the acquired address, channel, RSSI, and negotiated authentication.
|
3. Configure and enable a WPA2/WPA3 station profile using the example above. `wifi status` should progress through `connecting`, `waiting-ip`, and `online` and display the acquired address, channel, RSSI, and negotiated authentication.
|
||||||
4. Reboot and verify profile and AP credential persistence.
|
4. Reboot and verify profile and AP credential persistence.
|
||||||
5. Configure two profiles with different priorities, make the first unavailable, and verify failover to the second after its timeout.
|
5. Configure two profiles with different priorities, make the first unavailable, and verify failover to the second after its timeout.
|
||||||
@@ -252,22 +263,22 @@ Wi-Fi credentials currently reside as plaintext in the application-owned `wifi_a
|
|||||||
|
|
||||||
### Phase 0 diagnostics
|
### Phase 0 diagnostics
|
||||||
|
|
||||||
The retained hardware-characterization commands are:
|
The top-level `status` command retains quick MAX3243 signal-state inspection. Potentially disruptive hardware-characterization operations are grouped below `debug` so the primary help page stays concise:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
status
|
status
|
||||||
transceiver <enable|disable>
|
debug transceiver <enable|disable>
|
||||||
drivers <tx 0|1> <dtr 0|1> <rts 0|1>
|
debug drivers <tx 0|1> <dtr 0|1> <rts 0|1>
|
||||||
loopback-a
|
debug loopback-a
|
||||||
loopback-b
|
debug loopback-b
|
||||||
valid-test
|
debug valid-test
|
||||||
uart-loopback <baud> [8N1|8E1|8O1|8N2|7E1|7O1] [bytes]
|
debug uart-loopback <baud> [8N1|8E1|8O1|8N2|7E1|7O1] [bytes]
|
||||||
uart-suite
|
debug uart-suite
|
||||||
cts-flow-test
|
debug cts-flow-test
|
||||||
rts-flow-test
|
debug rts-flow-test
|
||||||
```
|
```
|
||||||
|
|
||||||
`uart-loopback` defaults to `8N1` and 256 bytes. Its accepted payload range is 1–512 bytes. `uart-suite` covers 300 through 250000 baud and all supported frame formats. `cts-flow-test` verifies transmit gating and exact resumption, while `rts-flow-test` uses UART2 as an internal traffic generator to verify automatic receive backpressure. Follow the command-specific loopback wiring in [`wiring.md`](wiring.md) before invoking any test.
|
Run `debug` without a subcommand for its usage summary. `debug uart-loopback` defaults to `8N1` and 256 bytes. Its accepted payload range is 1–512 bytes. `debug uart-suite` covers 300 through 250000 baud and all supported frame formats. `debug cts-flow-test` verifies transmit gating and exact resumption, while `debug rts-flow-test` uses UART2 as an internal traffic generator to verify automatic receive backpressure. Follow the command-specific loopback wiring in [`wiring.md`](wiring.md) before invoking any test.
|
||||||
|
|
||||||
A mutex-protected port lease prevents diagnostics, UART1 service startup, and future clients from reconfiguring the same GPIOs concurrently. If a UART driver cannot be removed during cleanup, the firmware keeps the MAX3243 shut down and marks the port faulted until reboot rather than exposing an ambiguous hardware state.
|
A mutex-protected port lease prevents diagnostics, UART1 service startup, and future clients from reconfiguring the same GPIOs concurrently. If a UART driver cannot be removed during cleanup, the firmware keeps the MAX3243 shut down and marks the port faulted until reboot rather than exposing an ambiguous hardware state.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS
|
SRCS
|
||||||
"main.c"
|
"main.c"
|
||||||
|
"console_completion.c"
|
||||||
|
"network_console.c"
|
||||||
"status_led.c"
|
"status_led.c"
|
||||||
"rs232_hw_test.c"
|
"rs232_hw_test.c"
|
||||||
"rs232_port_owner.c"
|
"rs232_port_owner.c"
|
||||||
@@ -28,6 +30,7 @@ idf_component_register(
|
|||||||
esp_wifi
|
esp_wifi
|
||||||
freertos
|
freertos
|
||||||
led_strip
|
led_strip
|
||||||
|
lwip
|
||||||
mbedtls
|
mbedtls
|
||||||
nvs_flash
|
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 "driver/uart.h"
|
||||||
|
#include "console_completion.h"
|
||||||
#include "esp_console.h"
|
#include "esp_console.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_psram.h"
|
#include "esp_psram.h"
|
||||||
|
#include "network_console.h"
|
||||||
#include "rs232_hw_test.h"
|
#include "rs232_hw_test.h"
|
||||||
#include "rs232_port_owner.h"
|
#include "rs232_port_owner.h"
|
||||||
#include "serial_config.h"
|
#include "serial_config.h"
|
||||||
@@ -129,6 +131,9 @@ void app_main(void)
|
|||||||
ESP_ERROR_CHECK(session_console_register_commands());
|
ESP_ERROR_CHECK(session_console_register_commands());
|
||||||
ESP_ERROR_CHECK(usb_console_register_commands());
|
ESP_ERROR_CHECK(usb_console_register_commands());
|
||||||
ESP_ERROR_CHECK(wifi_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_ERROR_CHECK(esp_console_start_repl(repl));
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Interactive test console ready at %d baud", CONSOLE_BAUD_RATE);
|
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(cts_flow_test)
|
||||||
DEFINE_OWNED_COMMAND(rts_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)
|
esp_err_t rs232_hw_test_init(void)
|
||||||
{
|
{
|
||||||
s_transceiver_enabled = true;
|
s_transceiver_enabled = true;
|
||||||
@@ -1598,66 +1649,10 @@ esp_err_t rs232_hw_test_register_console_commands(void)
|
|||||||
.argtable = NULL,
|
.argtable = NULL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.command = "transceiver",
|
.command = "debug",
|
||||||
.help = "Control active-low OFF: transceiver <enable|disable>",
|
.help = "Low-level RS-232 hardware diagnostics; run 'debug' for subcommands",
|
||||||
.hint = NULL,
|
.hint = NULL,
|
||||||
.func = &command_transceiver,
|
.func = &command_debug,
|
||||||
.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,
|
|
||||||
.argtable = NULL,
|
.argtable = NULL,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@
|
|||||||
/* Configure all MAX3243 logic-side signals in their safe static-test state. */
|
/* Configure all MAX3243 logic-side signals in their safe static-test state. */
|
||||||
esp_err_t rs232_hw_test_init(void);
|
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);
|
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)
|
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)
|
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();
|
return show_status();
|
||||||
}
|
}
|
||||||
if (argc == 2 && strcmp(argv[1], "counters") == 0) {
|
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 = {
|
const esp_console_cmd_t command = {
|
||||||
.command = "usb",
|
.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,
|
.hint = NULL,
|
||||||
.func = &command_usb,
|
.func = &command_usb,
|
||||||
.argtable = NULL,
|
.argtable = NULL,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "esp_wifi_types.h"
|
#include "esp_wifi_types.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "network_console.h"
|
||||||
#include "wifi_config.h"
|
#include "wifi_config.h"
|
||||||
#include "wifi_manager.h"
|
#include "wifi_manager.h"
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ static void print_usage(void)
|
|||||||
printf(" wifi ap channel <1..11>\n");
|
printf(" wifi ap channel <1..11>\n");
|
||||||
printf(" wifi ap secret|show-secret\n");
|
printf(" wifi ap secret|show-secret\n");
|
||||||
printf(" wifi save|load|defaults|reset\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)
|
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)
|
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)) {
|
if (argc == 1 || (argc == 2 && strcmp(argv[1], "status") == 0)) {
|
||||||
return show_status();
|
return show_status();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,13 +126,13 @@ DE-9 pin 4 DTR ─────> pin 6 DSR
|
|||||||
DE-9 pin 7 RTS ─────> pin 8 CTS
|
DE-9 pin 7 RTS ─────> pin 8 CTS
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `loopback-a`. The firmware cycles all eight TX/DTR/RTS logic combinations, waits for the MAX3243 outputs and receivers to settle, and verifies all three receiver states plus `VLD`. Two inversions occur—once in the driver and once in the receiver—so the final ESP32 logic levels must match.
|
Run `debug loopback-a`. The firmware cycles all eight TX/DTR/RTS logic combinations, waits for the MAX3243 outputs and receivers to settle, and verifies all three receiver states plus `VLD`. Two inversions occur—once in the driver and once in the receiver—so the final ESP32 logic levels must match.
|
||||||
|
|
||||||
Configuration A can also be used for:
|
Configuration A can also be used for:
|
||||||
|
|
||||||
- `valid-test`, with no external RS-232 peer connected.
|
- `debug valid-test`, with no external RS-232 peer connected.
|
||||||
- `uart-loopback <baud> [format] [bytes]`, although only the pin 3 to pin 2 link is needed by that command.
|
- `debug uart-loopback <baud> [format] [bytes]`, although only the pin 3 to pin 2 link is needed by that command.
|
||||||
- `uart-suite`, again using only the pin 3 to pin 2 data link.
|
- `debug uart-suite`, again using only the pin 3 to pin 2 data link.
|
||||||
|
|
||||||
### Configuration B: remaining receivers
|
### Configuration B: remaining receivers
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ DE-9 pin 4 DTR ─────> pin 9 RI
|
|||||||
DE-9 pin 7 RTS ─────> pin 2 RX
|
DE-9 pin 7 RTS ─────> pin 2 RX
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `loopback-b`. Together, configurations A and B exercise all three MAX3243 drivers and all five receivers.
|
Run `debug loopback-b`. Together, configurations A and B exercise all three MAX3243 drivers and all five receivers.
|
||||||
|
|
||||||
### Configuration C: CTS transmit gating
|
### Configuration C: CTS transmit gating
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ DE-9 pin 3 TX ─────> pin 2 RX
|
|||||||
DE-9 pin 4 DTR ─────> pin 8 CTS
|
DE-9 pin 4 DTR ─────> pin 8 CTS
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `cts-flow-test`. The test operates UART1 at 115200 baud with hardware CTS enabled and performs two phases:
|
Run `debug cts-flow-test`. The test operates UART1 at 115200 baud with hardware CTS enabled and performs two phases:
|
||||||
|
|
||||||
1. DTR logic 1 produces CTS logic 1, the inactive/high state. The firmware queues 512 bytes and verifies that transmission does not complete and no byte reaches RX during a 250 ms observation period.
|
1. DTR logic 1 produces CTS logic 1, the inactive/high state. The firmware queues 512 bytes and verifies that transmission does not complete and no byte reaches RX during a 250 ms observation period.
|
||||||
2. DTR changes to logic 0, producing active/low CTS. The queued transmission must resume automatically, and all 512 bytes must return through RX without missing, extra, or corrupted data and without UART errors.
|
2. DTR changes to logic 0, producing active/low CTS. The queued transmission must resume automatically, and all 512 bytes must return through RX without missing, extra, or corrupted data and without UART errors.
|
||||||
@@ -187,7 +187,7 @@ UART2 TX / DE-9 pin 4 DTR ─────> pin 2 RX / UART1 RX
|
|||||||
UART1 RTS / DE-9 pin 7 RTS ─────> pin 1 DCD / UART2 CTS
|
UART1 RTS / DE-9 pin 7 RTS ─────> pin 1 DCD / UART2 CTS
|
||||||
```
|
```
|
||||||
|
|
||||||
Run `rts-flow-test`. GPIO7 is temporarily routed from UART2 TX through the MAX3243 DTR driver. GPIO4 receives UART1 RTS through the DCD receiver and is simultaneously routed to UART2 CTS. This creates a complete hardware-controlled flow loop without an external serial peer.
|
Run `debug rts-flow-test`. GPIO7 is temporarily routed from UART2 TX through the MAX3243 DTR driver. GPIO4 receives UART1 RTS through the DCD receiver and is simultaneously routed to UART2 CTS. This creates a complete hardware-controlled flow loop without an external serial peer.
|
||||||
|
|
||||||
The test queues 4096 bytes from UART2 but initially does not read UART1. UART1's receive ring eventually fills, its hardware FIFO crosses the configured threshold, and UART1 automatically deasserts RTS. That state passes through the physical pin 7 to pin 1 jumper and blocks UART2 through CTS. The firmware then drains UART1, which must automatically reassert RTS and allow UART2 to finish.
|
The test queues 4096 bytes from UART2 but initially does not read UART1. UART1's receive ring eventually fills, its hardware FIFO crosses the configured threshold, and UART1 automatically deasserts RTS. That state passes through the physical pin 7 to pin 1 jumper and blocks UART2 through CTS. The firmware then drains UART1, which must automatically reassert RTS and allow UART2 to finish.
|
||||||
|
|
||||||
@@ -195,16 +195,16 @@ The test queues 4096 bytes from UART2 but initially does not read UART1. UART1's
|
|||||||
|
|
||||||
### Manual voltage and polarity checks
|
### Manual voltage and polarity checks
|
||||||
|
|
||||||
With no DE-9 loopback jumpers installed, use the static `drivers` command and measure each driven output relative to DE-9 pin 5 (`GND`):
|
With no DE-9 loopback jumpers installed, use the static `debug drivers` command and measure each driven output relative to DE-9 pin 5 (`GND`):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
drivers 0 0 0
|
debug drivers 0 0 0
|
||||||
```
|
```
|
||||||
|
|
||||||
Each of pins 3, 4, and 7 should be at a positive RS-232 voltage. Then run:
|
Each of pins 3, 4, and 7 should be at a positive RS-232 voltage. Then run:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
drivers 1 1 1
|
debug drivers 1 1 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Each output should be at a negative RS-232 voltage. Exact values vary with supply, load, meter, and charge-pump behavior; polarity is the primary check. The firmware leaves all outputs at logic 1 after automated tests.
|
Each output should be at a negative RS-232 voltage. Exact values vary with supply, load, meter, and charge-pump behavior; polarity is the primary check. The firmware leaves all outputs at logic 1 after automated tests.
|
||||||
@@ -212,14 +212,14 @@ Each output should be at a negative RS-232 voltage. Exact values vary with suppl
|
|||||||
### Recommended test order
|
### Recommended test order
|
||||||
|
|
||||||
1. Start with no DE-9 jumpers and run `status`.
|
1. Start with no DE-9 jumpers and run `status`.
|
||||||
2. Use `drivers 0 0 0` and `drivers 1 1 1` for the three output-polarity measurements.
|
2. Use `debug drivers 0 0 0` and `debug drivers 1 1 1` for the three output-polarity measurements.
|
||||||
3. Power down, install configuration A, power up, and run `loopback-a`.
|
3. Power down, install configuration A, power up, and run `debug loopback-a`.
|
||||||
4. With configuration A still installed and no external peer, run `valid-test`.
|
4. With configuration A still installed and no external peer, run `debug valid-test`.
|
||||||
5. Keep only the pin 3 to pin 2 jumper and run a basic test such as `uart-loopback 9600 8N1 256`.
|
5. Keep only the pin 3 to pin 2 jumper and run a basic test such as `debug uart-loopback 9600 8N1 256`.
|
||||||
6. If that passes, run `uart-suite`.
|
6. If that passes, run `debug uart-suite`.
|
||||||
7. Power down, replace the jumpers with configuration B, power up, and run `loopback-b`.
|
7. Power down, replace the jumpers with configuration B, power up, and run `debug loopback-b`.
|
||||||
8. Power down, install configuration C, power up, and run `cts-flow-test`.
|
8. Power down, install configuration C, power up, and run `debug cts-flow-test`.
|
||||||
9. Power down, install configuration D, power up, and run `rts-flow-test`.
|
9. Power down, install configuration D, power up, and run `debug rts-flow-test`.
|
||||||
|
|
||||||
RTS and CTS remain ordinary GPIO signals during static and basic UART loopback tests. Only the two dedicated flow-control commands hand them to UART peripherals. Every test shuts the MAX3243 down while changing GPIO-matrix routing and restores all outputs to static logic 1 afterward.
|
RTS and CTS remain ordinary GPIO signals during static and basic UART loopback tests. Only the two dedicated flow-control commands hand them to UART peripherals. Every test shuts the MAX3243 down while changing GPIO-matrix routing and restores all outputs to static logic 1 afterward.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user