diff --git a/components/app_console/CMakeLists.txt b/components/app_console/CMakeLists.txt index a58fe32..2881992 100644 --- a/components/app_console/CMakeLists.txt +++ b/components/app_console/CMakeLists.txt @@ -1,3 +1,11 @@ idf_component_register(SRCS "app_console.c" + "cmd_system.c" + "cmd_wifi.c" + "cmd_iperf.c" + "cmd_nvs.c" + "cmd_gps.c" + "cmd_ping.c" + "cmd_monitor.c" + "cmd_ip.c" INCLUDE_DIRS "." - PRIV_REQUIRES console iperf wifi_cfg wifi_controller nvs_flash gps_sync lwip) + PRIV_REQUIRES console iperf wifi_cfg wifi_controller nvs_flash gps_sync lwip driver) diff --git a/components/app_console/app_console.c b/components/app_console/app_console.c index 9202609..7eaea31 100644 --- a/components/app_console/app_console.c +++ b/components/app_console/app_console.c @@ -30,712 +30,25 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +/* + * app_console.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + */ #include "app_console.h" #include "esp_console.h" #include "esp_log.h" -#include "argtable3/argtable3.h" -#include "wifi_cfg.h" -#include "iperf.h" -#include "gps_sync.h" -#include "wifi_controller.h" -#include "esp_wifi.h" -#include "nvs.h" -#include "nvs_flash.h" -#include "ping/ping_sock.h" -#include -#include -#include -#include -#include - -// --- Helper: Prompt Update --- -// Updates the "esp32>" vs "esp32*>" prompt based on dirty state -static void end_cmd(void) { - app_console_update_prompt(); -} - -// ============================================================================ -// COMMAND: nvs (Storage Management) -// ============================================================================ -static struct { - struct arg_lit *dump; - struct arg_lit *clear_all; - struct arg_lit *help; - struct arg_end *end; -} nvs_args; - -static void print_nvs_key_str(nvs_handle_t h, const char *key, const char *label) { - char buf[64] = {0}; - size_t len = sizeof(buf); - if (nvs_get_str(h, key, buf, &len) == ESP_OK) { - printf(" %-12s : %s\n", label, buf); - } else { - printf(" %-12s : \n", label); - } -} - -static void print_nvs_key_u32(nvs_handle_t h, const char *key, const char *label) { - uint32_t val = 0; - if (nvs_get_u32(h, key, &val) == ESP_OK) { - printf(" %-12s : %" PRIu32 "\n", label, val); - } else { - printf(" %-12s : \n", label); - } -} - -static void print_nvs_key_u8(nvs_handle_t h, const char *key, const char *label) { - uint8_t val = 0; - if (nvs_get_u8(h, key, &val) == ESP_OK) { - printf(" %-12s : %u\n", label, val); - } else { - printf(" %-12s : \n", label); - } -} - -static int cmd_nvs(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&nvs_args); - if (nerrors > 0) { - arg_print_errors(stderr, nvs_args.end, argv[0]); - return 1; - } - - if (nvs_args.help->count > 0) { - printf("Usage: nvs [--dump] [--clear-all]\n"); - return 0; - } - - // --- CLEAR ALL --- - if (nvs_args.clear_all->count > 0) { - printf("Erasing ALL settings from NVS...\n"); - wifi_cfg_clear_credentials(); - wifi_cfg_clear_monitor_channel(); - iperf_param_clear(); - printf("Done. Please reboot.\n"); - end_cmd(); - return 0; - } - - // --- DUMP --- - printf("\n--- [WiFi Config (netcfg)] ---\n"); - nvs_handle_t h; - if (nvs_open("netcfg", NVS_READONLY, &h) == ESP_OK) { - print_nvs_key_str(h, "ssid", "SSID"); - print_nvs_key_str(h, "pass", "Password"); - print_nvs_key_str(h, "ip", "Static IP"); - print_nvs_key_str(h, "mask", "Netmask"); - print_nvs_key_str(h, "gw", "Gateway"); - print_nvs_key_u8 (h, "dhcp", "DHCP"); - print_nvs_key_u8 (h, "mon_ch", "Monitor Ch"); - nvs_close(h); - } else { - printf("Failed to open 'netcfg' namespace.\n"); - } - - printf("\n--- [iPerf Config (storage)] ---\n"); - if (nvs_open("storage", NVS_READONLY, &h) == ESP_OK) { - print_nvs_key_str(h, "iperf_dst_ip", "Dest IP"); - print_nvs_key_u32(h, "iperf_port", "Port"); - print_nvs_key_u32(h, "iperf_pps", "Target PPS"); - print_nvs_key_u32(h, "iperf_len", "Packet Len"); - print_nvs_key_u32(h, "iperf_burst", "Burst"); - nvs_close(h); - } else { - printf("Failed to open 'storage' namespace.\n"); - } - printf("\n"); - end_cmd(); - return 0; -} - -static void register_nvs_cmd(void) { - nvs_args.dump = arg_lit0(NULL, "dump", "Show all"); - nvs_args.clear_all = arg_lit0(NULL, "clear-all", "Factory Reset"); - nvs_args.help = arg_lit0("h", "help", "Help"); - nvs_args.end = arg_end(1); - - const esp_console_cmd_t cmd = { - .command = "nvs", - .help = "Storage Management", - .func = &cmd_nvs, - .argtable = &nvs_args - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -// ============================================================================ -// COMMAND: gps (Configure & Status) -// ============================================================================ -static struct { - struct arg_lit *enable; - struct arg_lit *disable; - struct arg_lit *status; - struct arg_lit *help; - struct arg_end *end; -} gps_args; - -static int cmd_gps(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&gps_args); - if (nerrors > 0) { - arg_print_errors(stderr, gps_args.end, argv[0]); - return 1; - } - - if (gps_args.help->count > 0) { - printf("Usage: gps [--enable|--disable|--status]\n"); - return 0; - } - - nvs_handle_t h; - esp_err_t err = nvs_open("storage", NVS_READWRITE, &h); - if (err != ESP_OK) { - printf("Error opening NVS: %s\n", esp_err_to_name(err)); - return 1; - } - - // --- HANDLE SETTERS --- - bool changed = false; - if (gps_args.enable->count > 0) { - nvs_set_u8(h, "gps_enabled", 1); - printf("GPS set to ENABLED. (Reboot required)\n"); - changed = true; - } else if (gps_args.disable->count > 0) { - nvs_set_u8(h, "gps_enabled", 0); - printf("GPS set to DISABLED. (Reboot required)\n"); - changed = true; - } - - if (changed) nvs_commit(h); - - // --- DISPLAY STATUS --- - // 1. NVS State - uint8_t val = 1; - if (nvs_get_u8(h, "gps_enabled", &val) != ESP_OK) val = 1; - printf("GPS NVS State: %s\n", val ? "ENABLED" : "DISABLED"); - nvs_close(h); - - // 2. Runtime Status - if (val) { - gps_timestamp_t ts = gps_get_timestamp(); - int64_t pps_age = gps_get_pps_age_ms(); - - printf("Status: %s\n", ts.synced ? "SYNCED" : "SEARCHING"); - - // Print Raw NMEA --- - char nmea_buf[128]; - gps_get_last_nmea(nmea_buf, sizeof(nmea_buf)); - - // Remove trailing \r\n for cleaner printing - size_t len = strlen(nmea_buf); - if (len > 0 && (nmea_buf[len-1] == '\r' || nmea_buf[len-1] == '\n')) nmea_buf[len-1] = 0; - if (len > 1 && (nmea_buf[len-2] == '\r' || nmea_buf[len-2] == '\n')) nmea_buf[len-2] = 0; - - printf("Last NMEA: [%s]\n", nmea_buf); - // --------------------------- - - if (pps_age < 0) { - printf("PPS Signal: NEVER DETECTED\n"); - } else if (pps_age > 1100) { - printf("PPS Signal: LOST (Last seen %" PRId64 " ms ago)\n", pps_age); - } else { - printf("PPS Signal: ACTIVE (Age: %" PRId64 " ms)\n", pps_age); - } - - if (ts.gps_us > 0) { - time_t now_sec = ts.gps_us / 1000000; - struct tm tm_info; - gmtime_r(&now_sec, &tm_info); - char time_buf[64]; - strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S UTC", &tm_info); - printf("Current Time: %s\n", time_buf); - } else { - printf("Current Time: (System Epoch)\n"); - } - } - - end_cmd(); - return 0; -} - -static void register_gps_cmd(void) { - gps_args.enable = arg_lit0(NULL, "enable", "Enable GPS"); - gps_args.disable = arg_lit0(NULL, "disable", "Disable GPS"); - gps_args.status = arg_lit0(NULL, "status", "Show Status"); - gps_args.help = arg_lit0("h", "help", "Help"); - gps_args.end = arg_end(2); - - const esp_console_cmd_t cmd = { - .command = "gps", - .help = "Configure GPS", - .func = &cmd_gps, - .argtable = &gps_args - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -// ============================================================================ -// COMMAND: iperf -// ============================================================================ -static struct { - struct arg_lit *start, *stop, *status, *save, *reload; - struct arg_lit *clear_nvs; - struct arg_str *ip; - struct arg_int *port, *pps, *len, *burst; - struct arg_lit *help; - struct arg_end *end; -} iperf_args; - -static int cmd_iperf(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&iperf_args); - if (nerrors > 0) { - arg_print_errors(stderr, iperf_args.end, argv[0]); - return 1; - } - - if (iperf_args.help->count > 0) { - printf("Usage: iperf [options]\n"); - printf(" --start Start traffic\n"); - printf(" --stop Stop traffic\n"); - printf(" --save Save config to NVS\n"); - printf(" --clear-nvs Reset to defaults\n"); - printf(" -c Set Dest IP\n"); - printf(" --pps Set Packets Per Sec\n"); - return 0; - } - - if (iperf_args.clear_nvs->count > 0) { - iperf_param_clear(); - printf("iPerf Configuration cleared (Reset to defaults).\n"); - end_cmd(); - return 0; - } - - if (iperf_args.reload->count > 0) { - iperf_param_init(); - printf("Configuration reloaded from NVS.\n"); - } - - bool config_changed = false; - iperf_cfg_t cfg; - iperf_param_get(&cfg); - - if (iperf_args.ip->count > 0) { cfg.dip = inet_addr(iperf_args.ip->sval[0]); config_changed = true; } - if (iperf_args.port->count > 0) { cfg.dport = (uint16_t)iperf_args.port->ival[0]; config_changed = true; } - if (iperf_args.len->count > 0) { cfg.send_len = (uint32_t)iperf_args.len->ival[0]; config_changed = true; } - if (iperf_args.burst->count > 0) { cfg.burst_count = (uint32_t)iperf_args.burst->ival[0]; config_changed = true; } - if (iperf_args.pps->count > 0) { - if (iperf_args.pps->ival[0] > 0) { - cfg.target_pps = (uint32_t)iperf_args.pps->ival[0]; - config_changed = true; - } - } - - if (config_changed) { - iperf_param_set(&cfg); - printf("RAM configuration updated.\n"); - } - - if (iperf_args.save->count > 0) { - bool changed = false; - if (iperf_param_save(&changed) == ESP_OK) { - printf(changed ? "Configuration saved to NVS.\n" : "No changes to save (NVS matches RAM).\n"); - } else { - printf("Error saving to NVS.\n"); - } - } - - if (iperf_args.stop->count > 0) iperf_stop(); - if (iperf_args.start->count > 0) iperf_start(); - if (iperf_args.status->count > 0) iperf_print_status(); - - end_cmd(); - return 0; -} - -static void register_iperf_cmd(void) { - iperf_args.start = arg_lit0(NULL, "start", "Start"); - iperf_args.stop = arg_lit0(NULL, "stop", "Stop"); - iperf_args.status = arg_lit0(NULL, "status", "Status"); - iperf_args.save = arg_lit0(NULL, "save", "Save"); - iperf_args.reload = arg_lit0(NULL, "reload", "Reload"); - iperf_args.clear_nvs = arg_lit0(NULL, "clear-nvs", "Clear NVS"); - iperf_args.ip = arg_str0("c", "client", "", "IP"); - iperf_args.port = arg_int0("p", "port", "", "Port"); - iperf_args.pps = arg_int0(NULL, "pps", "", "PPS"); - iperf_args.len = arg_int0(NULL, "len", "", "Len"); - iperf_args.burst = arg_int0(NULL, "burst", "", "Burst"); - iperf_args.help = arg_lit0("h", "help", "Help"); - iperf_args.end = arg_end(20); - - const esp_console_cmd_t cmd = { .command = "iperf", .help = "Traffic Gen", .func = &cmd_iperf, .argtable = &iperf_args }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -// ============================================================================ -// COMMAND: monitor -// ============================================================================ -static struct { - struct arg_lit *start, *stop, *status, *save, *reload; - struct arg_lit *clear_nvs; - struct arg_int *channel; - struct arg_lit *help; - struct arg_end *end; -} mon_args; - -static int cmd_monitor(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&mon_args); - if (nerrors > 0) { - arg_print_errors(stderr, mon_args.end, argv[0]); - return 1; - } - - if (mon_args.help->count > 0) { - printf("Usage: monitor [--start|--stop] [-c ] [--save|--reload]\n"); - return 0; - } - - if (mon_args.clear_nvs->count > 0) { - wifi_ctl_param_clear(); - printf("Monitor config cleared (Defaulting to Ch 6).\n"); - end_cmd(); - return 0; - } - - if (mon_args.reload->count > 0) { - wifi_ctl_param_reload(); - printf("Config reloaded from NVS.\n"); - } - - if (mon_args.channel->count > 0) { - wifi_ctl_param_set_monitor_channel((uint8_t)mon_args.channel->ival[0]); - printf("Channel set to %d (RAM).\n", mon_args.channel->ival[0]); - } - - if (mon_args.save->count > 0) { - if (wifi_ctl_param_save()) printf("Configuration saved to NVS.\n"); - else printf("No changes to save (NVS matches RAM).\n"); - } - - if (mon_args.stop->count > 0) { - wifi_ctl_switch_to_sta(WIFI_BW_HT20); - printf("Switched to Station Mode.\n"); - } - - if (mon_args.start->count > 0) { - if (wifi_ctl_switch_to_monitor(0, WIFI_BW_HT20) == ESP_OK) printf("Monitor Mode Started.\n"); - else printf("Failed to start Monitor Mode.\n"); - } - - if (mon_args.status->count > 0) { - wifi_ctl_mode_t mode = wifi_ctl_get_mode(); - printf("MONITOR STATUS:\n"); - printf(" Mode: %s\n", (mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" : "STATION"); - printf(" Active: Ch %d\n", (mode == WIFI_CTL_MODE_MONITOR) ? wifi_ctl_get_monitor_channel() : 0); - printf(" Staged: Ch %d\n", wifi_ctl_param_get_monitor_channel()); - printf(" Frames: %" PRIu32 "\n", wifi_ctl_get_monitor_frame_count()); - } - - end_cmd(); - return 0; -} - -static void register_monitor_cmd(void) { - mon_args.start = arg_lit0(NULL, "start", "Start"); - mon_args.stop = arg_lit0(NULL, "stop", "Stop"); - mon_args.status = arg_lit0(NULL, "status", "Status"); - mon_args.save = arg_lit0(NULL, "save", "Save"); - mon_args.reload = arg_lit0(NULL, "reload", "Reload"); - mon_args.clear_nvs = arg_lit0(NULL, "clear-nvs", "Clear NVS"); - mon_args.channel = arg_int0("c", "channel", "", "Chan"); - mon_args.help = arg_lit0("h", "help", "Help"); - mon_args.end = arg_end(20); - - const esp_console_cmd_t cmd = { .command = "monitor", .help = "Monitor Mode", .func = &cmd_monitor, .argtable = &mon_args }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -// ============================================================================ -// COMMAND: scan -// ============================================================================ -static struct { - struct arg_lit *help; - struct arg_end *end; -} scan_args; - -static int cmd_scan(int argc, char **argv) { - wifi_scan_config_t scan_config = {0}; - scan_config.show_hidden = true; - - printf("Starting Wi-Fi Scan...\n"); - esp_err_t err = esp_wifi_scan_start(&scan_config, true); - - // --- SMART RETRY LOGIC --- - if (err == ESP_ERR_WIFI_STATE) { - printf("WARN: WiFi is busy connecting. Forcing disconnect to allow scan...\n"); - esp_wifi_disconnect(); // Stop the connection attempt - vTaskDelay(pdMS_TO_TICKS(100)); // Give the driver a moment to reset state - - // Retry scan - err = esp_wifi_scan_start(&scan_config, true); - } - // ------------------------- - - if (err != ESP_OK) { - printf("Error starting scan: %s\n", esp_err_to_name(err)); - return 1; - } - - uint16_t ap_count = 0; - esp_wifi_scan_get_ap_num(&ap_count); - if (ap_count == 0) { - printf("No APs found.\n"); - return 0; - } - - wifi_ap_record_t *ap_info = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count); - if (!ap_info) { - printf("Failed to allocate memory for scan results.\n"); - return 1; - } - - ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_info)); - printf("Found %u unique BSSIDs\n", ap_count); - printf("------------------------------------------------------------------------------------------------\n"); - printf("SSID | BSSID | RSSI | Channel | Auth Mode\n"); - printf("------------------------------------------------------------------------------------------------\n"); - - for (int i = 0; i < ap_count; i++) { - char *authmode; - switch (ap_info[i].authmode) { - case WIFI_AUTH_OPEN: authmode = "OPEN"; break; - case WIFI_AUTH_WEP: authmode = "WEP"; break; - case WIFI_AUTH_WPA_PSK: authmode = "WPA-PSK"; break; - case WIFI_AUTH_WPA2_PSK: authmode = "WPA2-PSK"; break; - case WIFI_AUTH_WPA_WPA2_PSK: authmode = "WPA/WPA2"; break; - case WIFI_AUTH_WPA2_ENTERPRISE: authmode = "WPA2-ENT"; break; - case WIFI_AUTH_WPA3_PSK: authmode = "WPA3-PSK"; break; - case WIFI_AUTH_WPA2_WPA3_PSK: authmode = "WPA2/WPA3"; break; - default: authmode = "UNKNOWN"; break; - } - - printf("%-32.32s | %02x:%02x:%02x:%02x:%02x:%02x | %4d | %7d | %s\n", - ap_info[i].ssid, - ap_info[i].bssid[0], ap_info[i].bssid[1], ap_info[i].bssid[2], - ap_info[i].bssid[3], ap_info[i].bssid[4], ap_info[i].bssid[5], - ap_info[i].rssi, - ap_info[i].primary, - authmode); - } - printf("------------------------------------------------------------------------------------------------\n"); - - free(ap_info); - return 0; -} - -static void register_scan_cmd(void) { - scan_args.help = arg_lit0("h", "help", "Help"); - scan_args.end = arg_end(1); - const esp_console_cmd_t cmd = { .command = "scan", .help = "WiFi Scan", .func = &cmd_scan, .argtable = &scan_args }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -// ============================================================================ -// COMMAND: wifi_config -// ============================================================================ -static struct { - struct arg_str *ssid; - struct arg_str *pass; - struct arg_str *ip; - struct arg_lit *dhcp; - struct arg_lit *clear_nvs; - struct arg_lit *help; - struct arg_end *end; -} wifi_args; - -static int cmd_wifi_config(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&wifi_args); - if (nerrors > 0) { - arg_print_errors(stderr, wifi_args.end, argv[0]); - return 1; - } - - if (wifi_args.help->count > 0) { - printf("Usage: wifi_config -s [-p ] [--clear-nvs]\n"); - return 0; - } - - if (wifi_args.clear_nvs->count > 0) { - wifi_cfg_clear_credentials(); - printf("WiFi Credentials CLEARED from NVS.\n"); - return 0; - } - - if (wifi_args.ssid->count == 0) { - printf("Error: SSID is required (-s)\n"); - return 1; - } - - const char* ssid = wifi_args.ssid->sval[0]; - const char* pass = (wifi_args.pass->count > 0) ? wifi_args.pass->sval[0] : ""; - const char* ip = (wifi_args.ip->count > 0) ? wifi_args.ip->sval[0] : NULL; - bool dhcp = (wifi_args.dhcp->count > 0); - - printf("Saving WiFi Config: SSID='%s' DHCP=%d\n", ssid, dhcp); - - wifi_cfg_set_credentials(ssid, pass); - - if (ip) { - char mask[] = "255.255.255.0"; - char gw[32]; - strlcpy(gw, ip, sizeof(gw)); - char *last_dot = strrchr(gw, '.'); - if (last_dot) strcpy(last_dot, ".1"); - - wifi_cfg_set_static_ip(ip, mask, gw); - wifi_cfg_set_dhcp(false); - } else { - wifi_cfg_set_dhcp(dhcp); - } - - printf("Config saved. Rebooting to apply...\n"); - esp_restart(); - return 0; -} - -static void register_wifi_cmd(void) { - wifi_args.ssid = arg_str0("s", "ssid", "", "SSID"); - wifi_args.pass = arg_str0("p", "password", "", "Pass"); - wifi_args.ip = arg_str0("i", "ip", "", "Static IP"); - wifi_args.dhcp = arg_lit0("d", "dhcp", "Enable DHCP"); - wifi_args.clear_nvs = arg_lit0(NULL, "clear-nvs", "Clear NVS"); - wifi_args.help = arg_lit0("h", "help", "Help"); - wifi_args.end = arg_end(20); - - const esp_console_cmd_t cmd = { .command = "wifi_config", .help = "Configure WiFi", .func = &cmd_wifi_config, .argtable = &wifi_args }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - -static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args) { - uint8_t ttl; - uint16_t seqno; - uint32_t elapsed_time, recv_len; - ip_addr_t target_addr; - esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); - esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); - esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); - esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); - esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); - printf("%" PRIu32 " bytes from %s: icmp_seq=%u ttl=%u time=%" PRIu32 " ms\n", - recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); -} - -static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args) { - uint16_t seqno; - ip_addr_t target_addr; - esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); - esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); - printf("From %s: icmp_seq=%u timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); -} - -static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) { - uint32_t transmitted; - uint32_t received; - uint32_t total_time_ms; - esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); - esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); - esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); - printf("\n--- ping statistics ---\n"); - printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n", - transmitted, received, (transmitted - received) * 100 / transmitted, total_time_ms); - esp_ping_delete_session(hdl); -} - -static struct { - struct arg_str *host; - struct arg_int *count; - struct arg_int *interval; - struct arg_end *end; -} ping_args; - -static int cmd_ping(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&ping_args); - if (nerrors != 0) { - arg_print_errors(stderr, ping_args.end, argv[0]); - return 1; - } - - esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); - - // Parse Args - if (ping_args.count->count > 0) { - config.count = ping_args.count->ival[0]; - } - if (ping_args.interval->count > 0) { - config.interval_ms = ping_args.interval->ival[0] * 1000; - } - - // Parse Target IP - ip_addr_t target_addr; - struct addrinfo hint; - struct addrinfo *res = NULL; - memset(&hint, 0, sizeof(hint)); - memset(&target_addr, 0, sizeof(target_addr)); - - // Check if simple IP string - if (inet_aton(ping_args.host->sval[0], &target_addr.u_addr.ip4)) { - target_addr.type = IPADDR_TYPE_V4; - } else { - // Resolve Hostname - printf("Resolving %s...\n", ping_args.host->sval[0]); - if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) { - printf("ping: unknown host %s\n", ping_args.host->sval[0]); - return 1; - } - struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr; - inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4); - target_addr.type = IPADDR_TYPE_V4; - freeaddrinfo(res); - } - - config.target_addr = target_addr; - config.task_stack_size = 4096; // Ensure enough stack - - esp_ping_callbacks_t cbs = { - .on_ping_success = cmd_ping_on_ping_success, - .on_ping_timeout = cmd_ping_on_ping_timeout, - .on_ping_end = cmd_ping_on_ping_end, - .cb_args = NULL - }; - - esp_ping_handle_t ping; - esp_ping_new_session(&config, &cbs, &ping); - esp_ping_start(ping); - - return 0; -} - -static void register_ping(void) { - ping_args.host = arg_str1(NULL, NULL, "", "Host address or name"); - ping_args.count = arg_int0("c", "count", "", "Stop after replies"); - ping_args.interval = arg_int0("i", "interval", "", "Wait interval"); - ping_args.end = arg_end(1); - - const esp_console_cmd_t cmd = { - .command = "ping", - .help = "Send ICMP ECHO_REQUEST to network hosts", - .hint = NULL, - .func = &cmd_ping, - .argtable = &ping_args - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} +// --- Registration --- void app_console_register_commands(void) { - register_iperf_cmd(); - register_monitor_cmd(); - register_scan_cmd(); - register_wifi_cmd(); + register_system_cmd(); register_nvs_cmd(); + register_wifi_cmd(); + register_iperf_cmd(); register_gps_cmd(); - register_ping(); + register_ping_cmd(); + register_monitor_cmd(); + register_ip_cmd(); } diff --git a/components/app_console/app_console.h b/components/app_console/app_console.h index ec0f104..8130ab0 100644 --- a/components/app_console/app_console.h +++ b/components/app_console/app_console.h @@ -30,20 +30,28 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - #pragma once #ifdef __cplusplus extern "C" { #endif -/** - * @brief Register application-specific console commands - */ +// This matches the call in main.c void app_console_register_commands(void); -// Implemented in main.c - updates prompt based on NVS dirty state + +// Helper for prompt updates void app_console_update_prompt(void); +// Sub-module registers +void register_system_cmd(void); +void register_wifi_cmd(void); +void register_iperf_cmd(void); +void register_nvs_cmd(void); +void register_gps_cmd(void); +void register_ping_cmd(void); +void register_monitor_cmd(void); +void register_ip_cmd(void); + #ifdef __cplusplus } #endif diff --git a/components/app_console/cmd_gps.c b/components/app_console/cmd_gps.c new file mode 100644 index 0000000..706ed39 --- /dev/null +++ b/components/app_console/cmd_gps.c @@ -0,0 +1,154 @@ +/* + * cmd_gps.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "gps_sync.h" +#include "app_console.h" + +// ============================================================================ +// COMMAND: gps (Configure & Status) +// ============================================================================ + +static struct { + struct arg_lit *enable; + struct arg_lit *disable; + struct arg_lit *status; + struct arg_lit *help; + struct arg_end *end; +} gps_args; + +static int cmd_gps(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&gps_args); + if (nerrors > 0) { + arg_print_errors(stderr, gps_args.end, argv[0]); + return 1; + } + + if (gps_args.help->count > 0) { + printf("Usage: gps [--enable|--disable|--status]\n"); + return 0; + } + + nvs_handle_t h; + esp_err_t err = nvs_open("storage", NVS_READWRITE, &h); + if (err != ESP_OK) { + printf("Error opening NVS: %s\n", esp_err_to_name(err)); + return 1; + } + + // --- HANDLE SETTERS --- + bool changed = false; + if (gps_args.enable->count > 0) { + nvs_set_u8(h, "gps_enabled", 1); + printf("GPS set to ENABLED. (Reboot required)\n"); + changed = true; + } else if (gps_args.disable->count > 0) { + nvs_set_u8(h, "gps_enabled", 0); + printf("GPS set to DISABLED. (Reboot required)\n"); + changed = true; + } + + if (changed) nvs_commit(h); + + // --- DISPLAY STATUS --- + // 1. NVS State + uint8_t val = 1; + if (nvs_get_u8(h, "gps_enabled", &val) != ESP_OK) val = 1; // Default true + printf("GPS NVS State: %s\n", val ? "ENABLED" : "DISABLED"); + nvs_close(h); + + // 2. Runtime Status + if (val) { + gps_timestamp_t ts = gps_get_timestamp(); + int64_t pps_age = gps_get_pps_age_ms(); + + printf("Status: %s\n", ts.synced ? "SYNCED" : "SEARCHING"); + + // Print Raw NMEA + char nmea_buf[128]; + gps_get_last_nmea(nmea_buf, sizeof(nmea_buf)); + + // Cleanup CR/LF for printing + size_t len = strlen(nmea_buf); + if (len > 0 && (nmea_buf[len-1] == '\r' || nmea_buf[len-1] == '\n')) nmea_buf[len-1] = 0; + if (len > 1 && (nmea_buf[len-2] == '\r' || nmea_buf[len-2] == '\n')) nmea_buf[len-2] = 0; + + printf("Last NMEA: [%s]\n", nmea_buf); + + if (pps_age < 0) { + printf("PPS Signal: NEVER DETECTED\n"); + } else if (pps_age > 1100) { + printf("PPS Signal: LOST (Last seen %" PRId64 " ms ago)\n", pps_age); + } else { + printf("PPS Signal: ACTIVE (Age: %" PRId64 " ms)\n", pps_age); + } + + if (ts.gps_us > 0) { + time_t now_sec = ts.gps_us / 1000000; + struct tm tm_info; + gmtime_r(&now_sec, &tm_info); + char time_buf[64]; + strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S UTC", &tm_info); + printf("Current Time: %s\n", time_buf); + } else { + printf("Current Time: (System Epoch)\n"); + } + } + + app_console_update_prompt(); + return 0; +} + +void register_gps_cmd(void) { + gps_args.enable = arg_lit0(NULL, "enable", "Enable GPS"); + gps_args.disable = arg_lit0(NULL, "disable", "Disable GPS"); + gps_args.status = arg_lit0(NULL, "status", "Show Status"); + gps_args.help = arg_lit0("h", "help", "Help"); + gps_args.end = arg_end(2); + + const esp_console_cmd_t cmd = { + .command = "gps", + .help = "Configure GPS", + .func = &cmd_gps, + .argtable = &gps_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_ip.c b/components/app_console/cmd_ip.c new file mode 100644 index 0000000..79555f6 --- /dev/null +++ b/components/app_console/cmd_ip.c @@ -0,0 +1,106 @@ +/* + * cmd_ip.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "esp_netif.h" +#include "esp_mac.h" +#include "lwip/inet.h" +#include "app_console.h" + +static struct { + struct arg_lit *addr; + struct arg_lit *help; + struct arg_end *end; +} ip_args; + +static void print_iface_details(const char* key) { + esp_netif_t *netif = esp_netif_get_handle_from_ifkey(key); + if (!netif) return; + + const char *desc = esp_netif_get_desc(netif); + bool is_up = esp_netif_is_netif_up(netif); + + uint8_t mac[6] = {0}; + esp_netif_get_mac(netif, mac); + + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(netif, &ip_info); + + printf("%s (%s): <%s>\n", key, desc ? desc : "auth", is_up ? "UP" : "DOWN"); + printf(" link/ether %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + if (is_up && ip_info.ip.addr != 0) { + printf(" inet " IPSTR " netmask " IPSTR " broadcast " IPSTR "\n", + IP2STR(&ip_info.ip), + IP2STR(&ip_info.netmask), + IP2STR(&ip_info.gw)); + } +} + +static int cmd_ip(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&ip_args); + if (nerrors > 0) { + arg_print_errors(stderr, ip_args.end, argv[0]); + return 1; + } + + if (ip_args.help->count > 0) { + printf("Usage: ip [addr]\n"); + return 0; + } + + // Explicitly check standard interfaces + print_iface_details("WIFI_STA_DEF"); + print_iface_details("WIFI_AP_DEF"); + print_iface_details("ETH_DEF"); + + return 0; +} + +void register_ip_cmd(void) { + ip_args.addr = arg_lit0("a", "addr", "Show addresses"); + ip_args.help = arg_lit0("h", "help", "Help"); + ip_args.end = arg_end(1); + + const esp_console_cmd_t cmd = { + .command = "ip", + .help = "Show network interfaces", + .func = &cmd_ip, + .argtable = &ip_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_iperf.c b/components/app_console/cmd_iperf.c new file mode 100644 index 0000000..7d0fc90 --- /dev/null +++ b/components/app_console/cmd_iperf.c @@ -0,0 +1,151 @@ +/* + * cmd_iperf.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "arpa/inet.h" +#include "iperf.h" +#include "app_console.h" + +// ============================================================================ +// COMMAND: iperf +// ============================================================================ + +static struct { + struct arg_lit *start, *stop, *status, *save, *reload; + struct arg_lit *clear_nvs; + struct arg_str *ip; + struct arg_int *port, *pps, *len, *burst; + struct arg_lit *help; + struct arg_end *end; +} iperf_args; + +static int cmd_iperf(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&iperf_args); + if (nerrors > 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 1; + } + + if (iperf_args.help->count > 0) { + printf("Usage: iperf [options]\n"); + printf(" --start Start traffic\n"); + printf(" --stop Stop traffic\n"); + printf(" --save Save config to NVS\n"); + printf(" --reload Reload config from NVS\n"); + printf(" --clear-nvs Reset to defaults\n"); + printf(" -c Set Dest IP\n"); + printf(" --pps Set Packets Per Sec\n"); + return 0; + } + + // --- Actions: NVS Management --- + if (iperf_args.clear_nvs->count > 0) { + iperf_param_clear(); + printf("iPerf Configuration cleared (Reset to defaults).\n"); + app_console_update_prompt(); + return 0; + } + + if (iperf_args.reload->count > 0) { + iperf_param_init(); // Force re-read + printf("Configuration reloaded from NVS.\n"); + } + + // --- Actions: Parameter Updates --- + bool config_changed = false; + iperf_cfg_t cfg; + iperf_param_get(&cfg); // Get current staging + + if (iperf_args.ip->count > 0) { cfg.dip = inet_addr(iperf_args.ip->sval[0]); config_changed = true; } + if (iperf_args.port->count > 0) { cfg.dport = (uint16_t)iperf_args.port->ival[0]; config_changed = true; } + if (iperf_args.len->count > 0) { cfg.send_len = (uint32_t)iperf_args.len->ival[0]; config_changed = true; } + if (iperf_args.burst->count > 0) { cfg.burst_count = (uint32_t)iperf_args.burst->ival[0]; config_changed = true; } + if (iperf_args.pps->count > 0) { + if (iperf_args.pps->ival[0] > 0) { + cfg.target_pps = (uint32_t)iperf_args.pps->ival[0]; + config_changed = true; + } + } + + if (config_changed) { + iperf_param_set(&cfg); + printf("RAM configuration updated.\n"); + } + + if (iperf_args.save->count > 0) { + bool changed = false; + if (iperf_param_save(&changed) == ESP_OK) { + printf(changed ? "Configuration saved to NVS.\n" : "No changes to save (NVS matches RAM).\n"); + } else { + printf("Error saving to NVS.\n"); + } + } + + // --- Actions: Execution --- + if (iperf_args.stop->count > 0) iperf_stop(); + if (iperf_args.start->count > 0) iperf_start(); + if (iperf_args.status->count > 0) iperf_print_status(); + + app_console_update_prompt(); + return 0; +} + +void register_iperf_cmd(void) { + iperf_args.start = arg_lit0(NULL, "start", "Start"); + iperf_args.stop = arg_lit0(NULL, "stop", "Stop"); + iperf_args.status = arg_lit0(NULL, "status", "Status"); + iperf_args.save = arg_lit0(NULL, "save", "Save"); + iperf_args.reload = arg_lit0(NULL, "reload", "Reload"); + iperf_args.clear_nvs = arg_lit0(NULL, "clear-nvs", "Clear NVS"); + + iperf_args.ip = arg_str0("c", "client", "", "IP"); + iperf_args.port = arg_int0("p", "port", "", "Port"); + iperf_args.pps = arg_int0(NULL, "pps", "", "PPS"); + iperf_args.len = arg_int0(NULL, "len", "", "Len"); + iperf_args.burst = arg_int0(NULL, "burst", "", "Burst"); + + iperf_args.help = arg_lit0("h", "help", "Help"); + iperf_args.end = arg_end(20); + + const esp_console_cmd_t cmd = { + .command = "iperf", + .help = "Traffic Gen", + .func = &cmd_iperf, + .argtable = &iperf_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_monitor.c b/components/app_console/cmd_monitor.c new file mode 100644 index 0000000..6bac8f8 --- /dev/null +++ b/components/app_console/cmd_monitor.c @@ -0,0 +1,134 @@ +/* + * cmd_monitor.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "wifi_controller.h" +#include "iperf.h" +#include "app_console.h" + +// ============================================================================ +// COMMAND: monitor +// ============================================================================ + +static struct { + struct arg_lit *start, *stop, *status, *save, *reload; + struct arg_lit *clear_nvs; + struct arg_int *channel; + struct arg_lit *help; + struct arg_end *end; +} mon_args; + +static int cmd_monitor(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&mon_args); + if (nerrors > 0) { + arg_print_errors(stderr, mon_args.end, argv[0]); + return 1; + } + + if (mon_args.help->count > 0) { + printf("Usage: monitor [--start|--stop] [-c ] [--save|--reload]\n"); + return 0; + } + + // --- Actions: NVS Management --- + if (mon_args.clear_nvs->count > 0) { + wifi_ctl_param_clear(); + printf("Monitor config cleared (Defaulting to Ch 6).\n"); + app_console_update_prompt(); + return 0; + } + + if (mon_args.reload->count > 0) { + wifi_ctl_param_reload(); + printf("Config reloaded from NVS.\n"); + } + + if (mon_args.channel->count > 0) { + wifi_ctl_param_set_monitor_channel((uint8_t)mon_args.channel->ival[0]); + printf("Channel set to %d (RAM).\n", mon_args.channel->ival[0]); + } + + if (mon_args.save->count > 0) { + if (wifi_ctl_param_save()) printf("Configuration saved to NVS.\n"); + else printf("No changes to save (NVS matches RAM).\n"); + } + + // --- Actions: Mode Switching --- + if (mon_args.stop->count > 0) { + wifi_ctl_switch_to_sta(WIFI_BW_HT20); + printf("Switched to Station Mode.\n"); + } + + if (mon_args.start->count > 0) { + if (wifi_ctl_switch_to_monitor(0, WIFI_BW_HT20) == ESP_OK) printf("Monitor Mode Started.\n"); + else printf("Failed to start Monitor Mode.\n"); + } + + // --- Status --- + if (mon_args.status->count > 0) { + wifi_ctl_mode_t mode = wifi_ctl_get_mode(); + printf("MONITOR STATUS:\n"); + printf(" Mode: %s\n", (mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" : "STATION"); + printf(" Active: Ch %d\n", (mode == WIFI_CTL_MODE_MONITOR) ? wifi_ctl_get_monitor_channel() : 0); + printf(" Staged: Ch %d\n", wifi_ctl_param_get_monitor_channel()); + printf(" Frames: %" PRIu32 "\n", wifi_ctl_get_monitor_frame_count()); + } + + app_console_update_prompt(); + return 0; +} + +void register_monitor_cmd(void) { + mon_args.start = arg_lit0(NULL, "start", "Start"); + mon_args.stop = arg_lit0(NULL, "stop", "Stop"); + mon_args.status = arg_lit0(NULL, "status", "Status"); + mon_args.save = arg_lit0(NULL, "save", "Save"); + mon_args.reload = arg_lit0(NULL, "reload", "Reload"); + mon_args.clear_nvs = arg_lit0(NULL, "clear-nvs", "Clear NVS"); + mon_args.channel = arg_int0("c", "channel", "", "Chan"); + mon_args.help = arg_lit0("h", "help", "Help"); + mon_args.end = arg_end(20); + + const esp_console_cmd_t cmd = { + .command = "monitor", + .help = "Monitor Mode", + .func = &cmd_monitor, + .argtable = &mon_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_nvs.c b/components/app_console/cmd_nvs.c new file mode 100644 index 0000000..3c99dab --- /dev/null +++ b/components/app_console/cmd_nvs.c @@ -0,0 +1,159 @@ +/* + * cmd_nvs.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "wifi_cfg.h" +#include "iperf.h" +#include "app_console.h" + +// ============================================================================ +// COMMAND: nvs (Storage Management) +// ============================================================================ + +static struct { + struct arg_lit *dump; + struct arg_lit *clear_all; + struct arg_lit *help; + struct arg_end *end; +} nvs_args; + +// --- Helper Functions for Dumping --- + +static void print_nvs_key_str(nvs_handle_t h, const char *key, const char *label) { + char buf[64] = {0}; + size_t len = sizeof(buf); + if (nvs_get_str(h, key, buf, &len) == ESP_OK) { + printf(" %-12s : %s\n", label, buf); + } else { + printf(" %-12s : \n", label); + } +} + +static void print_nvs_key_u32(nvs_handle_t h, const char *key, const char *label) { + uint32_t val = 0; + if (nvs_get_u32(h, key, &val) == ESP_OK) { + printf(" %-12s : %" PRIu32 "\n", label, val); + } else { + printf(" %-12s : \n", label); + } +} + +static void print_nvs_key_u8(nvs_handle_t h, const char *key, const char *label) { + uint8_t val = 0; + if (nvs_get_u8(h, key, &val) == ESP_OK) { + printf(" %-12s : %u\n", label, val); + } else { + printf(" %-12s : \n", label); + } +} + +static int cmd_nvs(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&nvs_args); + if (nerrors > 0) { + arg_print_errors(stderr, nvs_args.end, argv[0]); + return 1; + } + + if (nvs_args.help->count > 0) { + printf("Usage: nvs [--dump] [--clear-all]\n"); + return 0; + } + + // --- CLEAR ALL --- + if (nvs_args.clear_all->count > 0) { + printf("Erasing ALL settings from NVS...\n"); + wifi_cfg_clear_credentials(); + wifi_cfg_clear_monitor_channel(); + iperf_param_clear(); + printf("Done. Please reboot.\n"); + app_console_update_prompt(); + return 0; + } + + // --- DUMP --- + // Note: NVS iterators are complex in ESP-IDF; explicit key listing is often simpler for known keys. + + printf("\n--- [WiFi Config (netcfg)] ---\n"); + nvs_handle_t h; + if (nvs_open("netcfg", NVS_READONLY, &h) == ESP_OK) { + print_nvs_key_str(h, "ssid", "SSID"); + print_nvs_key_str(h, "pass", "Password"); + print_nvs_key_str(h, "ip", "Static IP"); + print_nvs_key_str(h, "mask", "Netmask"); + print_nvs_key_str(h, "gw", "Gateway"); + print_nvs_key_u8 (h, "dhcp", "DHCP"); + print_nvs_key_u8 (h, "mon_ch", "Monitor Ch"); + nvs_close(h); + } else { + printf("Failed to open 'netcfg' namespace (not initialized?).\n"); + } + + printf("\n--- [iPerf Config (storage)] ---\n"); + if (nvs_open("storage", NVS_READONLY, &h) == ESP_OK) { + print_nvs_key_str(h, "iperf_dst_ip", "Dest IP"); + print_nvs_key_u32(h, "iperf_port", "Port"); + print_nvs_key_u32(h, "iperf_pps", "Target PPS"); + print_nvs_key_u32(h, "iperf_len", "Packet Len"); + print_nvs_key_u32(h, "iperf_burst", "Burst"); + print_nvs_key_u8 (h, "gps_enabled", "GPS En"); + nvs_close(h); + } else { + printf("Failed to open 'storage' namespace.\n"); + } + printf("\n"); + + app_console_update_prompt(); + return 0; +} + +void register_nvs_cmd(void) { + nvs_args.dump = arg_lit0(NULL, "dump", "Show all"); + nvs_args.clear_all = arg_lit0(NULL, "clear-all", "Factory Reset"); + nvs_args.help = arg_lit0("h", "help", "Help"); + nvs_args.end = arg_end(1); + + const esp_console_cmd_t cmd = { + .command = "nvs", + .help = "Storage Management", + .func = &cmd_nvs, + .argtable = &nvs_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_ping.c b/components/app_console/cmd_ping.c new file mode 100644 index 0000000..a6fd270 --- /dev/null +++ b/components/app_console/cmd_ping.c @@ -0,0 +1,173 @@ +/* + * cmd_ping.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "ping/ping_sock.h" +#include "lwip/inet.h" +#include "lwip/netdb.h" +#include "app_console.h" + +// ============================================================================ +// COMMAND: ping (ICMP Echo) +// ============================================================================ + +static struct { + struct arg_str *host; + struct arg_int *count; + struct arg_int *interval; + struct arg_end *end; +} ping_args; + +static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args) { + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + + printf("%" PRIu32 " bytes from %s: icmp_seq=%u ttl=%u time=%" PRIu32 " ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args) { + uint16_t seqno; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + + printf("From %s: icmp_seq=%u timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); +} + +static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) { + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + + printf("\n--- ping statistics ---\n"); + printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n", + transmitted, received, (transmitted - received) * 100 / transmitted, total_time_ms); + + esp_ping_delete_session(hdl); +} + +static int cmd_ping(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&ping_args); + if (nerrors != 0) { + arg_print_errors(stderr, ping_args.end, argv[0]); + return 1; + } + + esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); + + // Parse Args + if (ping_args.count->count > 0) { + config.count = ping_args.count->ival[0]; + } + if (ping_args.interval->count > 0) { + config.interval_ms = ping_args.interval->ival[0] * 1000; + } + + // Parse Target IP or Hostname + ip_addr_t target_addr; + struct addrinfo hint; + struct addrinfo *res = NULL; + memset(&hint, 0, sizeof(hint)); + memset(&target_addr, 0, sizeof(target_addr)); + + // Check if simple IP string + if (inet_aton(ping_args.host->sval[0], &target_addr.u_addr.ip4)) { + target_addr.type = IPADDR_TYPE_V4; + } else { + // Resolve Hostname + printf("Resolving %s...\n", ping_args.host->sval[0]); + // Set hint to prefer IPv4 if desired, or leave 0 for ANY + hint.ai_family = AF_INET; + + if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) { + printf("ping: unknown host %s\n", ping_args.host->sval[0]); + return 1; + } + + // Convert struct sockaddr_in to ip_addr_t + struct sockaddr_in *sa = (struct sockaddr_in *)res->ai_addr; + inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &sa->sin_addr); + target_addr.type = IPADDR_TYPE_V4; + + freeaddrinfo(res); + } + + config.target_addr = target_addr; + config.task_stack_size = 4096; // Ensure enough stack for callbacks + + esp_ping_callbacks_t cbs = { + .on_ping_success = cmd_ping_on_ping_success, + .on_ping_timeout = cmd_ping_on_ping_timeout, + .on_ping_end = cmd_ping_on_ping_end, + .cb_args = NULL + }; + + esp_ping_handle_t ping; + esp_ping_new_session(&config, &cbs, &ping); + esp_ping_start(ping); + + return 0; +} + +void register_ping_cmd(void) { + ping_args.host = arg_str1(NULL, NULL, "", "Host address or name"); + ping_args.count = arg_int0("c", "count", "", "Stop after replies"); + ping_args.interval = arg_int0("i", "interval", "", "Wait interval"); + ping_args.end = arg_end(1); + + const esp_console_cmd_t cmd = { + .command = "ping", + .help = "Send ICMP ECHO_REQUEST to network hosts", + .hint = NULL, + .func = &cmd_ping, + .argtable = &ping_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_system.c b/components/app_console/cmd_system.c new file mode 100644 index 0000000..5109bb0 --- /dev/null +++ b/components/app_console/cmd_system.c @@ -0,0 +1,164 @@ +/* + * cmd_system.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "esp_system.h" +#include "esp_chip_info.h" +#include "esp_mac.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "app_console.h" + +static const char *TAG = "CMD_SYS"; + +// ============================================================================ +// COMMAND: system (Reboot, Info, Heap) +// ============================================================================ + +static struct { + struct arg_lit *reboot; + struct arg_lit *info; + struct arg_lit *heap; + struct arg_lit *help; + struct arg_end *end; +} sys_args; + +static const char* get_chip_model_string(esp_chip_model_t model) { + switch (model) { + case CHIP_ESP32: return "ESP32"; + case CHIP_ESP32S2: return "ESP32-S2"; + case CHIP_ESP32S3: return "ESP32-S3"; + case CHIP_ESP32C3: return "ESP32-C3"; + case CHIP_ESP32C2: return "ESP32-C2"; + case CHIP_ESP32C6: return "ESP32-C6"; + case CHIP_ESP32H2: return "ESP32-H2"; +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)) + case CHIP_ESP32C5: return "ESP32-C5"; // Requires recent IDF + case CHIP_ESP32P4: return "ESP32-P4"; +#endif + default: return "Unknown"; + } +} + +static int cmd_system(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&sys_args); + if (nerrors > 0) { + arg_print_errors(stderr, sys_args.end, argv[0]); + return 1; + } + + if (sys_args.help->count > 0) { + printf("Usage: system [--reboot|--info|--heap]\n"); + printf(" --reboot Restart the device immediately\n"); + printf(" --info Show chip model, revision, and MAC\n"); + printf(" --heap Show current memory statistics\n"); + return 0; + } + + // --- HEAP STATS --- + if (sys_args.heap->count > 0) { + uint32_t free_heap = esp_get_free_heap_size(); + uint32_t min_heap = esp_get_minimum_free_heap_size(); + + printf("Heap Summary:\n"); + printf(" Free Heap: %" PRIu32 " bytes\n", free_heap); + printf(" Min Free Ever: %" PRIu32 " bytes\n", min_heap); + + uint32_t internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + uint32_t dma = heap_caps_get_free_size(MALLOC_CAP_DMA); + printf(" Internal (SRAM): %" PRIu32 " bytes\n", internal); + printf(" DMA Capable: %" PRIu32 " bytes\n", dma); + } + + // --- CHIP INFO --- + if (sys_args.info->count > 0) { + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + + printf("Hardware Info:\n"); + printf(" Model: %s\n", get_chip_model_string(chip_info.model)); + printf(" Cores: %d\n", chip_info.cores); + // Revision format is typically MXX (Major, Minor) + printf(" Revision: v%d.%02d\n", chip_info.revision / 100, chip_info.revision % 100); + + printf(" Features: "); + if (chip_info.features & CHIP_FEATURE_WIFI_BGN) printf("WiFi-BGN "); + if (chip_info.features & CHIP_FEATURE_BLE) printf("BLE "); + if (chip_info.features & CHIP_FEATURE_BT) printf("BT "); + if (chip_info.features & CHIP_FEATURE_EMB_FLASH) printf("Emb-Flash "); + if (chip_info.features & CHIP_FEATURE_IEEE802154) printf("802.15.4 (Zigbee/Thread) "); + printf("\n"); + + uint8_t mac[6]; + if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) { + printf(" MAC (STA): %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + + if (esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP) == ESP_OK) { + printf(" MAC (AP): %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + } + + // --- REBOOT --- + if (sys_args.reboot->count > 0) { + ESP_LOGW(TAG, "Reboot requested by user."); + printf("Rebooting system in 1 second...\n"); + vTaskDelay(pdMS_TO_TICKS(1000)); + esp_restart(); + } + + return 0; +} + +void register_system_cmd(void) { + sys_args.reboot = arg_lit0(NULL, "reboot", "Reboot device"); + sys_args.info = arg_lit0(NULL, "info", "Chip Info"); + sys_args.heap = arg_lit0(NULL, "heap", "Memory Info"); + sys_args.help = arg_lit0("h", "help", "Help"); + sys_args.end = arg_end(1); + + const esp_console_cmd_t cmd = { + .command = "system", + .help = "System Management (Reboot, Info, Heap)", + .func = &cmd_system, + .argtable = &sys_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +} diff --git a/components/app_console/cmd_wifi.c b/components/app_console/cmd_wifi.c new file mode 100644 index 0000000..6c07228 --- /dev/null +++ b/components/app_console/cmd_wifi.c @@ -0,0 +1,270 @@ +/* + * cmd_wifi.c + * + * Copyright (c) 2025 Umber Networks & Robert McMahon + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "wifi_cfg.h" +#include "wifi_controller.h" +#include "app_console.h" + +// --- Forward Declarations for Sub-Command Handlers --- +static int wifi_do_scan(int argc, char **argv); +static int wifi_do_connect(int argc, char **argv); +static int wifi_do_disconnect(int argc, char **argv); +static int wifi_do_link(int argc, char **argv); + +// ============================================================================ +// COMMAND: wifi (Dispatcher) +// ============================================================================ + +static int cmd_wifi(int argc, char **argv) { + if (argc < 2) { + printf("Usage: wifi [args]\n"); + printf("Subcommands:\n"); + printf(" link Show connection status\n"); + printf(" scan Scan for networks\n"); + printf(" connect Connect to an AP\n"); + printf(" disconnect Disconnect from AP\n"); + return 0; + } + + // Dispatch based on subcommand + if (strcmp(argv[1], "scan") == 0) return wifi_do_scan(argc - 1, &argv[1]); + if (strcmp(argv[1], "connect") == 0) return wifi_do_connect(argc - 1, &argv[1]); + if (strcmp(argv[1], "disconnect") == 0) return wifi_do_disconnect(argc - 1, &argv[1]); + if (strcmp(argv[1], "link") == 0) return wifi_do_link(argc - 1, &argv[1]); + + printf("Unknown subcommand '%s'. See 'wifi --help'.\n", argv[1]); + return 1; +} + +// ---------------------------------------------------------------------------- +// Sub-command: wifi link +// ---------------------------------------------------------------------------- +static struct { + struct arg_lit *help; + struct arg_end *end; +} link_args; + +static int wifi_do_link(int argc, char **argv) { + // Parse args mainly to support --help + if (arg_parse(argc, argv, (void **)&link_args) == 0) { + if (link_args.help->count > 0) { + printf("Usage: wifi link\nDescription: Show connection status (RSSI, BSSID, SSID, IP).\n"); + return 0; + } + } + + wifi_ap_record_t ap_info; + if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { + printf("Connected to %02x:%02x:%02x:%02x:%02x:%02x (on esp32)\n", + ap_info.bssid[0], ap_info.bssid[1], ap_info.bssid[2], + ap_info.bssid[3], ap_info.bssid[4], ap_info.bssid[5]); + printf("\tSSID: %s\n", ap_info.ssid); + printf("\tfreq: %d\n", ap_info.primary); + printf("\tsignal: %d dBm\n", ap_info.rssi); + + esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + esp_netif_ip_info_t ip; + if (netif && esp_netif_get_ip_info(netif, &ip) == ESP_OK) { + printf("\tIP: " IPSTR "\n", IP2STR(&ip.ip)); + printf("\tMask: " IPSTR "\n", IP2STR(&ip.netmask)); + printf("\tGW: " IPSTR "\n", IP2STR(&ip.gw)); + } + } else { + printf("Not connected.\n"); + } + return 0; +} + +// ---------------------------------------------------------------------------- +// Sub-command: wifi scan +// ---------------------------------------------------------------------------- +static struct { + struct arg_lit *help; + struct arg_end *end; +} scan_args; + +static int wifi_do_scan(int argc, char **argv) { + if (arg_parse(argc, argv, (void **)&scan_args) == 0) { + if (scan_args.help->count > 0) { + printf("Usage: wifi scan\n"); + return 0; + } + } + + // Prevent scanning in Monitor Mode (Hardware limitation) + if (wifi_ctl_get_mode() == WIFI_CTL_MODE_MONITOR) { + printf("Error: Cannot scan while in Monitor Mode.\n"); + printf("Run 'monitor --stop' first, or 'wifi disconnect'.\n"); + return 1; + } + + wifi_scan_config_t scan_config = {0}; + scan_config.show_hidden = true; + + printf("Scanning...\n"); + esp_err_t err = esp_wifi_scan_start(&scan_config, true); + + // Smart Retry: If busy connecting, force disconnect momentarily + if (err == ESP_ERR_WIFI_STATE) { + printf("WARN: Interface busy. Forcing disconnect for scan...\n"); + esp_wifi_disconnect(); + vTaskDelay(pdMS_TO_TICKS(100)); + err = esp_wifi_scan_start(&scan_config, true); + } + + if (err != ESP_OK) { + printf("Scan failed: %s\n", esp_err_to_name(err)); + return 1; + } + + uint16_t ap_count = 0; + esp_wifi_scan_get_ap_num(&ap_count); + + if (ap_count == 0) { + printf("No networks found.\n"); + return 0; + } + + wifi_ap_record_t *ap_info = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count); + if (!ap_info) { + printf("Out of memory for scan list.\n"); + return 1; + } + + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_info)); + + printf("Found %u unique BSSIDs\n", ap_count); + printf("BSS | SSID | RSSI | Ch | Auth\n"); + printf("-------------------------------------------------------------------------------------\n"); + for (int i = 0; i < ap_count; i++) { + char *authmode = "UNK"; + switch (ap_info[i].authmode) { + case WIFI_AUTH_OPEN: authmode = "OPEN"; break; + case WIFI_AUTH_WEP: authmode = "WEP"; break; + case WIFI_AUTH_WPA_PSK: authmode = "WPA"; break; + case WIFI_AUTH_WPA2_PSK: authmode = "WPA2"; break; + case WIFI_AUTH_WPA_WPA2_PSK: authmode = "WPA/2"; break; + case WIFI_AUTH_WPA3_PSK: authmode = "WPA3"; break; + default: break; + } + + printf("%02x:%02x:%02x:%02x:%02x:%02x | %-32.32s | %4d | %2d | %s\n", + ap_info[i].bssid[0], ap_info[i].bssid[1], ap_info[i].bssid[2], + ap_info[i].bssid[3], ap_info[i].bssid[4], ap_info[i].bssid[5], + ap_info[i].ssid, ap_info[i].rssi, ap_info[i].primary, authmode); + } + printf("-------------------------------------------------------------------------------------\n"); + free(ap_info); + return 0; +} + +// ---------------------------------------------------------------------------- +// Sub-command: wifi connect +// ---------------------------------------------------------------------------- +static struct { + struct arg_str *ssid; + struct arg_str *password; + struct arg_lit *help; + struct arg_end *end; +} connect_args; + +static int wifi_do_connect(int argc, char **argv) { + // Re-init pointers to be safe with static structs + connect_args.ssid = arg_str1(NULL, NULL, "", "SSID"); + connect_args.password = arg_str0(NULL, NULL, "", "Password"); + connect_args.help = arg_lit0("h", "help", "Help"); + connect_args.end = arg_end(2); + + int nerrors = arg_parse(argc, argv, (void **)&connect_args); + if (nerrors > 0) { + arg_print_errors(stderr, connect_args.end, argv[0]); + return 1; + } + + if (connect_args.help->count > 0) { + printf("Usage: wifi connect [password]\n"); + return 0; + } + + const char *ssid = connect_args.ssid->sval[0]; + const char *pass = (connect_args.password->count > 0) ? connect_args.password->sval[0] : ""; + + printf("Connecting to '%s'...\n", ssid); + + // Save to NVS + wifi_cfg_set_credentials(ssid, pass); + wifi_cfg_set_dhcp(true); // Default to DHCP for simple connect + + // Trigger Reconnect + // The most robust way to switch networks completely is to restart, + // ensuring the WiFi Controller state machine resets cleanly from NVS. + printf("Credentials saved. Rebooting to apply...\n"); + esp_restart(); + + return 0; +} + +// ---------------------------------------------------------------------------- +// Sub-command: wifi disconnect +// ---------------------------------------------------------------------------- +static int wifi_do_disconnect(int argc, char **argv) { + printf("Disconnecting...\n"); + esp_wifi_disconnect(); + + // Optionally clear credentials if you want "disconnect" to mean "forget" + // wifi_cfg_clear_credentials(); + // printf("Credentials forgotten.\n"); + + return 0; +} + +// ---------------------------------------------------------------------------- +// Registration +// ---------------------------------------------------------------------------- + +void register_wifi_cmd(void) { + const esp_console_cmd_t cmd = { + .command = "wifi", + .help = "Wi-Fi Configuration Tool (iw style)", + .func = &cmd_wifi, + .argtable = NULL // Handled internally by sub-dispatchers + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); +}