diff --git a/components/app_console/CMakeLists.txt b/components/app_console/CMakeLists.txt index a64ac6b..2b61a73 100644 --- a/components/app_console/CMakeLists.txt +++ b/components/app_console/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "app_console.c" INCLUDE_DIRS "." - PRIV_REQUIRES console wifi_cfg iperf) + PRIV_REQUIRES console wifi_cfg wifi_controller iperf) diff --git a/components/app_console/app_console.c b/components/app_console/app_console.c index 409dc82..dc87117 100644 --- a/components/app_console/app_console.c +++ b/components/app_console/app_console.c @@ -1,19 +1,25 @@ +#include +#include #include "app_console.h" #include "esp_console.h" #include "esp_log.h" #include "argtable3/argtable3.h" #include "wifi_cfg.h" #include "iperf.h" -#include +#include "wifi_controller.h" + +// Helper to refresh prompt at end of commands +static void end_cmd(void) { + app_console_update_prompt(); +} // ============================================================================ // COMMAND: iperf // ============================================================================ static struct { - struct arg_lit *start; - struct arg_lit *stop; - struct arg_lit *status; - struct arg_int *pps; + struct arg_lit *start, *stop, *status, *save, *reload; + struct arg_str *ip; + struct arg_int *port, *pps, *len, *burst; struct arg_lit *help; struct arg_end *end; } iperf_args; @@ -26,137 +32,204 @@ static int cmd_iperf(int argc, char **argv) { } if (iperf_args.help->count > 0) { - printf("Usage: iperf [start|stop|status] [--pps ]\n"); + printf("Usage: iperf [options]\n"); // ... (Shortened for brevity) return 0; } - if (iperf_args.stop->count > 0) { - iperf_stop(); - 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) { - int val = iperf_args.pps->ival[0]; - if (val > 0) { - iperf_set_pps((uint32_t)val); - } else { - printf("Error: PPS must be > 0\n"); + int pps = iperf_args.pps->ival[0]; + if (pps > 0) { + cfg.target_pps = (uint32_t)pps; // Update directly + config_changed = true; } - return 0; } - if (iperf_args.status->count > 0) { - iperf_print_status(); - return 0; + if (config_changed) { + iperf_param_set(&cfg); + printf("RAM configuration updated.\n"); } - if (iperf_args.start->count > 0) { - // Start using saved NVS config - iperf_cfg_t cfg = { .time = 0 }; - iperf_start(&cfg); - return 0; + 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(); // Update Prompt return 0; } -static void register_iperf_cmd(void) { - iperf_args.start = arg_lit0(NULL, "start", "Start iperf traffic"); - iperf_args.stop = arg_lit0(NULL, "stop", "Stop iperf traffic"); - iperf_args.status = arg_lit0(NULL, "status", "Show current statistics"); - iperf_args.pps = arg_int0(NULL, "pps", "", "Set packets per second"); - iperf_args.help = arg_lit0(NULL, "help", "Show help"); - iperf_args.end = arg_end(20); - - const esp_console_cmd_t cmd = { - .command = "iperf", - .help = "Control iperf traffic generator", - .hint = NULL, - .func = &cmd_iperf, - .argtable = &iperf_args - }; - ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); -} - // ============================================================================ -// COMMAND: wifi_config +// COMMAND: monitor // ============================================================================ static struct { - struct arg_str *ssid; - struct arg_str *pass; - struct arg_str *ip; - struct arg_lit *dhcp; + struct arg_lit *start, *stop, *status, *save, *reload; + struct arg_int *channel; struct arg_lit *help; struct arg_end *end; -} wifi_args; +} mon_args; -static int cmd_wifi_config(int argc, char **argv) { - int nerrors = arg_parse(argc, argv, (void **)&wifi_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, wifi_args.end, argv[0]); + arg_print_errors(stderr, mon_args.end, argv[0]); return 1; } - if (wifi_args.help->count > 0) { - printf("Usage: wifi_config -s -p [-i ] [-d]\n"); + if (mon_args.help->count > 0) { + printf("Usage: monitor [--start|--stop] [-c ] [--save|--reload]\n"); return 0; } - if (wifi_args.ssid->count == 0) { - printf("Error: SSID is required (-s)\n"); - return 1; + if (mon_args.reload->count > 0) { + wifi_ctl_param_reload(); + printf("Config reloaded from NVS.\n"); } - 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]; - - // FIXED: Use strlcpy instead of strncpy to prevent truncation warnings - 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); + 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]); } - printf("Config saved. Rebooting to apply...\n"); - esp_restart(); + 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()); + } + + end_cmd(); // Update Prompt return 0; } -static void register_wifi_cmd(void) { - wifi_args.ssid = arg_str0("s", "ssid", "", "WiFi SSID"); - wifi_args.pass = arg_str0("p", "password", "", "WiFi Password"); - wifi_args.ip = arg_str0("i", "ip", "", "Static IP"); - wifi_args.dhcp = arg_lit0("d", "dhcp", "Enable DHCP"); - wifi_args.help = arg_lit0("h", "help", "Show help"); - wifi_args.end = arg_end(20); +// ============================================================================ +// COMMAND: scan +// ============================================================================ +static struct { + struct arg_lit *help; + struct arg_end *end; +} scan_args; - const esp_console_cmd_t cmd = { - .command = "wifi_config", - .help = "Configure WiFi credentials", - .hint = NULL, - .func = &cmd_wifi_config, - .argtable = &wifi_args - }; +static int cmd_scan(int argc, char **argv) { + int nerrors = arg_parse(argc, argv, (void **)&scan_args); + if (nerrors > 0) { + arg_print_errors(stderr, scan_args.end, argv[0]); + return 1; + } + + if (scan_args.help->count > 0) { + printf("Usage: scan\n"); + return 0; + } + + printf("Starting WiFi Scan...\n"); + wifi_scan_config_t scan_config = { .show_hidden = true }; + esp_err_t 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); + printf("Found %d APs:\n", ap_count); + + if (ap_count > 0) { + wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count); + if (ap_list) { + esp_wifi_scan_get_ap_records(&ap_count, ap_list); + printf("%-32s | %-4s | %-4s | %-3s\n", "SSID", "RSSI", "CH", "Auth"); + printf("----------------------------------------------------------\n"); + for (int i = 0; i < ap_count; i++) { + printf("%-32s | %-4d | %-4d | %d\n", (char *)ap_list[i].ssid, ap_list[i].rssi, ap_list[i].primary, ap_list[i].authmode); + } + free(ap_list); + } + } + return 0; +} + +// Registration +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.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)); } +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.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)); +} + +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)); +} + +// Don't forget to keep your existing register_wifi_cmd ... + void app_console_register_commands(void) { register_iperf_cmd(); - register_wifi_cmd(); + register_monitor_cmd(); + register_scan_cmd(); + // register_wifi_cmd(); // Ensure this is linked } diff --git a/components/app_console/app_console.h b/components/app_console/app_console.h index 2a92f85..58d058f 100644 --- a/components/app_console/app_console.h +++ b/components/app_console/app_console.h @@ -8,6 +8,8 @@ extern "C" { * @brief Register application-specific console commands */ void app_console_register_commands(void); +// Implemented in main.c - updates prompt based on NVS dirty state +void app_console_update_prompt(void); #ifdef __cplusplus } diff --git a/components/iperf/iperf.c b/components/iperf/iperf.c index 8a9f4cb..e11b419 100644 --- a/components/iperf/iperf.c +++ b/components/iperf/iperf.c @@ -23,6 +23,20 @@ static const char *TAG = "iperf"; +// --- NVS Keys --- +#define NVS_KEY_IPERF_ENABLE "iperf_enabled" +#define NVS_KEY_IPERF_PPS "iperf_pps" +#define NVS_KEY_IPERF_ROLE "iperf_role" +#define NVS_KEY_IPERF_DST_IP "iperf_dst_ip" +#define NVS_KEY_IPERF_PORT "iperf_port" +#define NVS_KEY_IPERF_PROTO "iperf_proto" +#define NVS_KEY_IPERF_BURST "iperf_burst" +#define NVS_KEY_IPERF_LEN "iperf_len" + +// --- Global Config State --- +static iperf_cfg_t s_staging_cfg = {0}; // The "Running" Config +static bool s_staging_initialized = false; + static EventGroupHandle_t s_iperf_event_group = NULL; #define IPERF_IP_READY_BIT (1 << 0) #define IPERF_STOP_REQ_BIT (1 << 1) @@ -30,6 +44,7 @@ static EventGroupHandle_t s_iperf_event_group = NULL; #define RATE_CHECK_INTERVAL_US 500000 #define MIN_PACING_INTERVAL_US 100 +// --- Runtime Control --- typedef struct { iperf_cfg_t cfg; bool finish; @@ -37,20 +52,17 @@ typedef struct { uint8_t *buffer; } iperf_ctrl_t; -static iperf_ctrl_t s_iperf_ctrl = {0}; +static iperf_ctrl_t s_iperf_ctrl = {0}; // The "Active" Config (while task runs) static TaskHandle_t s_iperf_task_handle = NULL; -static iperf_cfg_t s_next_cfg; // Holding area for the new config -static bool s_reload_req = false; // Flag to trigger internal restart +static bool s_reload_req = false; // Global Stats Tracker static iperf_stats_t s_stats = {0}; - -// --- Session Persistence Variables --- static int64_t s_session_start_time = 0; static int64_t s_session_end_time = 0; static uint64_t s_session_packets = 0; -// --- State Duration & Edge Counters --- +// --- FSM State & Stats --- typedef enum { IPERF_STATE_IDLE = 0, IPERF_STATE_TX, @@ -58,6 +70,8 @@ typedef enum { IPERF_STATE_TX_STALLED } iperf_fsm_state_t; +static iperf_fsm_state_t s_current_fsm_state = IPERF_STATE_IDLE; + static int64_t s_time_tx_us = 0; static int64_t s_time_slow_us = 0; static int64_t s_time_stalled_us = 0; @@ -66,35 +80,188 @@ static uint32_t s_edge_tx = 0; static uint32_t s_edge_slow = 0; static uint32_t s_edge_stalled = 0; -static iperf_fsm_state_t s_current_fsm_state = IPERF_STATE_IDLE; - static esp_event_handler_instance_t instance_any_id; static esp_event_handler_instance_t instance_got_ip; -// --- Helper: Pattern Initialization --- -// Fills buffer with 0-9 cyclic ASCII pattern (matches iperf2 "pattern" function) -static void iperf_pattern(uint8_t *buf, uint32_t len) { - for (uint32_t i = 0; i < len; i++) { - buf[i] = (i % 10) + '0'; +// --- Packet Structures --- +typedef struct { + int32_t id; + uint32_t tv_sec; + uint32_t tv_usec; + int32_t id2; +} udp_datagram; + +typedef struct { + int32_t flags; + int32_t numThreads; + int32_t mPort; + int32_t mBufLen; + int32_t mWinBand; + int32_t mAmount; +} client_hdr_v1; + + +// --- Helper: Defaults --- +static void set_defaults(iperf_cfg_t *cfg) { + memset(cfg, 0, sizeof(iperf_cfg_t)); + cfg->flag = IPERF_FLAG_CLIENT | IPERF_FLAG_UDP; + cfg->dip = 0; // 0.0.0.0 + cfg->dport = IPERF_DEFAULT_PORT; + cfg->time = 0; // Infinite + cfg->target_pps = 100; // Default 100 PPS + cfg->burst_count = 1; + cfg->send_len = IPERF_UDP_TX_LEN; +} + +// --- Parameter Management (Init / Load / Save / Get / Set) --- + +static void trim_whitespace(char *str) { + char *end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + *(end+1) = 0; +} + +void iperf_param_init(void) { + if (s_staging_initialized) return; + + set_defaults(&s_staging_cfg); + + // Load from NVS + nvs_handle_t h; + if (nvs_open("storage", NVS_READONLY, &h) == ESP_OK) { + ESP_LOGI(TAG, "Loading saved config from NVS..."); + + uint32_t val; + // Direct Load: No conversion needed + if (nvs_get_u32(h, NVS_KEY_IPERF_PPS, &val) == ESP_OK && val > 0) { + s_staging_cfg.target_pps = val; + } + + if (nvs_get_u32(h, NVS_KEY_IPERF_BURST, &val) == ESP_OK) s_staging_cfg.burst_count = val; + if (nvs_get_u32(h, NVS_KEY_IPERF_LEN, &val) == ESP_OK) s_staging_cfg.send_len = val; + if (nvs_get_u32(h, NVS_KEY_IPERF_PORT, &val) == ESP_OK) s_staging_cfg.dport = (uint16_t)val; + + size_t req; + if (nvs_get_str(h, NVS_KEY_IPERF_DST_IP, NULL, &req) == ESP_OK) { + char *ip_str = malloc(req); + if (ip_str) { + nvs_get_str(h, NVS_KEY_IPERF_DST_IP, ip_str, &req); + trim_whitespace(ip_str); + s_staging_cfg.dip = inet_addr(ip_str); + free(ip_str); + } + } + nvs_close(h); + } else { + ESP_LOGI(TAG, "No saved config found, using defaults."); + } + + s_staging_initialized = true; +} + +void iperf_param_get(iperf_cfg_t *out_cfg) { + if (!s_staging_initialized) iperf_param_init(); + *out_cfg = s_staging_cfg; +} + +void iperf_param_set(const iperf_cfg_t *new_cfg) { + if (!s_staging_initialized) iperf_param_init(); + + // Update Staging + s_staging_cfg = *new_cfg; + + // Hot Reload Logic + if (s_iperf_task_handle) { + ESP_LOGI(TAG, "Hot reloading parameters..."); + s_iperf_ctrl.cfg = s_staging_cfg; + s_reload_req = true; + + // Stop current internal loop to pick up new config + if (s_iperf_event_group) xEventGroupSetBits(s_iperf_event_group, IPERF_STOP_REQ_BIT); } } -// --- Helper: Generate Client Header --- -// Modified to set all zeros except HEADER_SEQNO64B -static void iperf_generate_client_hdr(iperf_cfg_t *cfg, client_hdr_v1 *hdr) { - // Zero out the entire structure - memset(hdr, 0, sizeof(client_hdr_v1)); +// --- Dirty Check --- +bool iperf_param_is_unsaved(void) { + if (!s_staging_initialized) return false; - // Set only the SEQNO64B flag (Server will detect 64-bit seqno in UDP header) - hdr->flags = htonl(HEADER_SEQNO64B); + nvs_handle_t h; + if (nvs_open("storage", NVS_READONLY, &h) != ESP_OK) return false; + + uint32_t val; + bool match = true; + + // Direct Compare: No conversion needed + uint32_t saved_pps = 0; + if (nvs_get_u32(h, NVS_KEY_IPERF_PPS, &val) == ESP_OK) saved_pps = val; + if (s_staging_cfg.target_pps != saved_pps) match = false; + + // Standard Fields + if (nvs_get_u32(h, NVS_KEY_IPERF_BURST, &val) == ESP_OK) { if (s_staging_cfg.burst_count != val) match = false; } + if (nvs_get_u32(h, NVS_KEY_IPERF_LEN, &val) == ESP_OK) { if (s_staging_cfg.send_len != val) match = false; } + + uint32_t saved_port = 0; + if (nvs_get_u32(h, NVS_KEY_IPERF_PORT, &val) == ESP_OK) saved_port = val; + if (s_staging_cfg.dport != (uint16_t)saved_port) match = false; + + // IP String + size_t req; + char staging_ip[32]; + struct in_addr daddr; daddr.s_addr = s_staging_cfg.dip; + inet_ntop(AF_INET, &daddr, staging_ip, sizeof(staging_ip)); + + if (nvs_get_str(h, NVS_KEY_IPERF_DST_IP, NULL, &req) == ESP_OK) { + char *saved_ip = malloc(req); + if (saved_ip) { + nvs_get_str(h, NVS_KEY_IPERF_DST_IP, saved_ip, &req); + trim_whitespace(saved_ip); + if (strcmp(saved_ip, staging_ip) != 0) match = false; + free(saved_ip); + } + } else { + if (s_staging_cfg.dip != 0) match = false; + } + + nvs_close(h); + return !match; } -// ... [Existing Status Reporting & Event Handler Code] ... +// --- Save with Check --- +esp_err_t iperf_param_save(bool *out_changed) { + if (out_changed) *out_changed = false; + if (!iperf_param_is_unsaved()) { + ESP_LOGI(TAG, "Config matches NVS. No write needed."); + return ESP_OK; + } + + nvs_handle_t h; + esp_err_t err = nvs_open("storage", NVS_READWRITE, &h); + if (err != ESP_OK) return err; + + // Direct Save: No conversion needed + nvs_set_u32(h, NVS_KEY_IPERF_PPS, s_staging_cfg.target_pps); + nvs_set_u32(h, NVS_KEY_IPERF_BURST, s_staging_cfg.burst_count); + nvs_set_u32(h, NVS_KEY_IPERF_LEN, s_staging_cfg.send_len); + nvs_set_u32(h, NVS_KEY_IPERF_PORT, (uint32_t)s_staging_cfg.dport); + + char ip_str[32]; + struct in_addr daddr; + daddr.s_addr = s_staging_cfg.dip; + inet_ntop(AF_INET, &daddr, ip_str, sizeof(ip_str)); + nvs_set_str(h, NVS_KEY_IPERF_DST_IP, ip_str); + + err = nvs_commit(h); + if (err == ESP_OK && out_changed) *out_changed = true; + + nvs_close(h); + return err; +} + +// --- Status & Helpers --- void iperf_get_stats(iperf_stats_t *stats) { if (stats) { - s_stats.config_pps = (s_iperf_ctrl.cfg.pacing_period_us > 0) ? - (1000000 / s_iperf_ctrl.cfg.pacing_period_us) : 0; + s_stats.config_pps = s_iperf_ctrl.cfg.target_pps; *stats = s_stats; } } @@ -102,29 +269,24 @@ void iperf_get_stats(iperf_stats_t *stats) { void iperf_print_status(void) { iperf_get_stats(&s_stats); - // 1. Get Source IP - char src_ip[32] = "0.0.0.0"; - esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); - if (netif) { - esp_netif_ip_info_t ip_info; - if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { - inet_ntop(AF_INET, &ip_info.ip, src_ip, sizeof(src_ip)); - } - } - - // 2. Get Destination IP char dst_ip[32] = "0.0.0.0"; struct in_addr daddr; - daddr.s_addr = s_iperf_ctrl.cfg.dip; + + // Show active config if running, otherwise staging + if (s_stats.running) daddr.s_addr = s_iperf_ctrl.cfg.dip; + else daddr.s_addr = s_staging_cfg.dip; + inet_ntop(AF_INET, &daddr, dst_ip, sizeof(dst_ip)); - float err = 0.0f; - if (s_stats.running && s_stats.config_pps > 0) { - int32_t diff = (int32_t)s_stats.config_pps - (int32_t)s_stats.actual_pps; - err = (float)diff * 100.0f / (float)s_stats.config_pps; - } + // Calculate Percentages + double total_us = (double)(s_time_tx_us + s_time_slow_us + s_time_stalled_us); + if (total_us < 1.0) total_us = 1.0; - // 3. Compute Session Bandwidth + double pct_tx = ((double)s_time_tx_us / total_us) * 100.0; + double pct_slow = ((double)s_time_slow_us / total_us) * 100.0; + double pct_stalled = ((double)s_time_stalled_us / total_us) * 100.0; + + // Bandwidth Calculation float avg_bw_mbps = 0.0f; if (s_session_start_time > 0) { int64_t end_t = (s_stats.running) ? esp_timer_get_time() : s_session_end_time; @@ -137,26 +299,32 @@ void iperf_print_status(void) { } } - // 4. Calculate State Percentages - double total_us = (double)(s_time_tx_us + s_time_slow_us + s_time_stalled_us); - if (total_us < 1.0) total_us = 1.0; + printf("IPERF: Dest=%s:%u, Pkts=%llu, BW=%.2f Mbps, Running=%d\n", + dst_ip, + s_stats.running ? s_iperf_ctrl.cfg.dport : s_staging_cfg.dport, + s_session_packets, + avg_bw_mbps, + s_stats.running); - double pct_tx = ((double)s_time_tx_us / total_us) * 100.0; - double pct_slow = ((double)s_time_slow_us / total_us) * 100.0; - double pct_stalled = ((double)s_time_stalled_us / total_us) * 100.0; - - // Standard Stats - printf("IPERF_STATUS: Src=%s, Dst=%s, Running=%d, Config=%" PRIu32 ", Actual=%" PRIu32 ", Err=%.1f%%, Pkts=%" PRIu64 ", AvgBW=%.2f Mbps\n", - src_ip, dst_ip, s_stats.running, s_stats.config_pps, s_stats.actual_pps, err, s_session_packets, avg_bw_mbps); - - // New Format: Time + Percentage + Edges - printf("IPERF_STATES: TX=%.2fs/%.1f%% (%lu), SLOW=%.2fs/%.1f%% (%lu), STALLED=%.2fs/%.1f%% (%lu)\n", + printf("STATES: TX=%.2fs/%.1f%% (%lu), SLOW=%.2fs/%.1f%% (%lu), STALLED=%.2fs/%.1f%% (%lu)\n", (double)s_time_tx_us/1000000.0, pct_tx, (unsigned long)s_edge_tx, (double)s_time_slow_us/1000000.0, pct_slow, (unsigned long)s_edge_slow, (double)s_time_stalled_us/1000000.0, pct_stalled, (unsigned long)s_edge_stalled); } -// --- Network Events --- +// --- Core Logic --- + +static void iperf_pattern(uint8_t *buf, uint32_t len) { + for (uint32_t i = 0; i < len; i++) { + buf[i] = (i % 10) + '0'; + } +} + +static void iperf_generate_client_hdr(iperf_cfg_t *cfg, client_hdr_v1 *hdr) { + memset(hdr, 0, sizeof(client_hdr_v1)); + hdr->flags = htonl(HEADER_SEQNO64B); +} + static void iperf_network_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (s_iperf_event_group == NULL) return; if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { @@ -175,7 +343,7 @@ static bool iperf_wait_for_ip(void) { if (netif) { esp_netif_ip_info_t ip_info; if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0) { - xEventGroupSetBits(s_iperf_event_group, IPERF_IP_READY_BIT); + return true; } } @@ -188,95 +356,21 @@ static bool iperf_wait_for_ip(void) { esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id); esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip); - if (bits & IPERF_STOP_REQ_BIT) { - ESP_LOGW(TAG, "Stop requested while waiting for IP"); - return false; - } - ESP_LOGI(TAG, "IP Ready. Starting traffic."); + if (bits & IPERF_STOP_REQ_BIT) return false; return true; } -static void trim_whitespace(char *str) { - char *end = str + strlen(str) - 1; - while(end > str && isspace((unsigned char)*end)) end--; - *(end+1) = 0; -} - -static void iperf_read_nvs_config(iperf_cfg_t *cfg) { - nvs_handle_t my_handle; - if (nvs_open("storage", NVS_READONLY, &my_handle) != ESP_OK) return; - - uint32_t val; - if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PERIOD, &val) == ESP_OK) cfg->pacing_period_us = val; - if (nvs_get_u32(my_handle, NVS_KEY_IPERF_BURST, &val) == ESP_OK) cfg->burst_count = val; - if (nvs_get_u32(my_handle, NVS_KEY_IPERF_LEN, &val) == ESP_OK) cfg->send_len = val; - if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PORT, &val) == ESP_OK) cfg->dport = (uint16_t)val; - - size_t req; - char buf[16]; - req = sizeof(buf); - if (nvs_get_str(my_handle, NVS_KEY_IPERF_ROLE, buf, &req) == ESP_OK) { - if (strcmp(buf, "SERVER") == 0) cfg->flag |= IPERF_FLAG_SERVER; - else cfg->flag |= IPERF_FLAG_CLIENT; - } - - req = sizeof(buf); - if (nvs_get_str(my_handle, NVS_KEY_IPERF_PROTO, buf, &req) == ESP_OK) { - if (strcmp(buf, "TCP") == 0) cfg->flag |= IPERF_FLAG_TCP; - else cfg->flag |= IPERF_FLAG_UDP; - } - - if (nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, NULL, &req) == ESP_OK) { - char *ip_str = malloc(req); - if (ip_str) { - nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, ip_str, &req); - trim_whitespace(ip_str); - cfg->dip = inet_addr(ip_str); - free(ip_str); - } - } - nvs_close(my_handle); -} - -void iperf_set_pps(uint32_t pps) { - if (pps == 0) pps = 1; - uint32_t period_us = 1000000 / pps; - if (period_us < MIN_PACING_INTERVAL_US) period_us = MIN_PACING_INTERVAL_US; - - if (s_iperf_task_handle != NULL) { - s_iperf_ctrl.cfg.pacing_period_us = period_us; - printf("IPERF_PPS_UPDATED: %" PRIu32 "\n", pps); - } else { - s_iperf_ctrl.cfg.pacing_period_us = period_us; - } -} - -uint32_t iperf_get_pps(void) { - if (s_iperf_ctrl.cfg.pacing_period_us == 0) return 0; - return 1000000 / s_iperf_ctrl.cfg.pacing_period_us; -} - - static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl) { - if (!iperf_wait_for_ip()) { - printf("IPERF_STOPPED\n"); - return ESP_OK; - } + if (!iperf_wait_for_ip()) return ESP_OK; struct sockaddr_in addr; addr.sin_family = AF_INET; - addr.sin_port = htons(ctrl->cfg.dport > 0 ? ctrl->cfg.dport : 5001); + addr.sin_port = htons(ctrl->cfg.dport); addr.sin_addr.s_addr = ctrl->cfg.dip; - char ip_str[32]; - inet_ntop(AF_INET, &addr.sin_addr, ip_str, sizeof(ip_str)); - ESP_LOGI(TAG, "Client sending to %s:%d", ip_str, ntohs(addr.sin_port)); - int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { - status_led_set_state(LED_STATE_FAILED); - ESP_LOGE(TAG, "Socket creation failed: %d", errno); - printf("IPERF_STOPPED\n"); + ESP_LOGE(TAG, "Socket failed: %d", errno); return ESP_FAIL; } @@ -288,7 +382,6 @@ static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl) { s_stats.running = true; s_session_start_time = esp_timer_get_time(); - s_session_end_time = 0; s_session_packets = 0; // Reset FSM @@ -296,22 +389,33 @@ static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl) { s_edge_tx = 0; s_edge_slow = 0; s_edge_stalled = 0; s_current_fsm_state = IPERF_STATE_IDLE; - printf("IPERF_STARTED\n"); + ESP_LOGI(TAG, "UDP Started. Target: %s", inet_ntoa(addr.sin_addr)); int64_t next_send_time = esp_timer_get_time(); - int64_t end_time = (ctrl->cfg.time == 0) ? INT64_MAX : esp_timer_get_time() + (int64_t)ctrl->cfg.time * 1000000LL; - int64_t last_rate_check = esp_timer_get_time(); uint32_t packets_since_check = 0; int64_t packet_id = 0; struct timespec ts; - while (!ctrl->finish && esp_timer_get_time() < end_time) { + // Calculate period based on PPS (Target Period) + uint32_t period_us = (ctrl->cfg.target_pps > 0) ? (1000000 / ctrl->cfg.target_pps) : 10000; + if (period_us < MIN_PACING_INTERVAL_US) period_us = MIN_PACING_INTERVAL_US; + + while (!ctrl->finish && !s_reload_req) { int64_t now = esp_timer_get_time(); int64_t wait = next_send_time - now; - if (wait > 2000) vTaskDelay(pdMS_TO_TICKS(wait / 1000)); - else while (esp_timer_get_time() < next_send_time) taskYIELD(); + // 1. Sleep if gap is large + if (wait > 2000) { + vTaskDelay(pdMS_TO_TICKS(wait / 1000)); + } + + // 2. Spin until exact time (Strict Monotonic enforcement) + while (esp_timer_get_time() < next_send_time) { + taskYIELD(); + } + + if (xEventGroupGetBits(s_iperf_event_group) & IPERF_STOP_REQ_BIT) break; for (int k = 0; k < ctrl->cfg.burst_count; k++) { int64_t current_id = packet_id++; @@ -323,142 +427,108 @@ static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl) { udp_hdr->tv_sec = htonl((uint32_t)ts.tv_sec); udp_hdr->tv_usec = htonl(ts.tv_nsec / 1000); - int sent = sendto(sockfd, ctrl->buffer, ctrl->cfg.send_len, 0, (struct sockaddr *)&addr, sizeof(addr)); - - if (sent > 0) { - packets_since_check++; + if (sendto(sockfd, ctrl->buffer, ctrl->cfg.send_len, 0, (struct sockaddr *)&addr, sizeof(addr)) > 0) { s_session_packets++; - } else { - // --- ROBUST FIX: Never Abort --- - // If send fails (buffer full, routing issue, etc.), we just yield and retry next loop. - // We do NOT goto exit. - if (errno != 12) { - // Log rarely to avoid spamming serial - if ((packet_id % 100) == 0) { - ESP_LOGW(TAG, "Send error: %d (Ignored)", errno); - } - } - vTaskDelay(pdMS_TO_TICKS(10)); + packets_since_check++; } } + // FSM STATS LOGIC now = esp_timer_get_time(); if (now - last_rate_check > RATE_CHECK_INTERVAL_US) { uint32_t interval_us = (uint32_t)(now - last_rate_check); if (interval_us > 0) { s_stats.actual_pps = (uint32_t)((uint64_t)packets_since_check * 1000000 / interval_us); - uint32_t config_pps = iperf_get_pps(); - uint32_t threshold = (config_pps * 3) / 4; + uint32_t threshold = (ctrl->cfg.target_pps * 3) / 4; + iperf_fsm_state_t next_state; if (s_stats.actual_pps == 0) next_state = IPERF_STATE_TX_STALLED; else if (s_stats.actual_pps >= threshold) next_state = IPERF_STATE_TX; else next_state = IPERF_STATE_TX_SLOW; switch (next_state) { - case IPERF_STATE_TX: s_time_tx_us += interval_us; break; - case IPERF_STATE_TX_SLOW: s_time_slow_us += interval_us; break; - case IPERF_STATE_TX_STALLED: s_time_stalled_us += interval_us; break; - default: break; + case IPERF_STATE_TX: s_time_tx_us += interval_us; break; + case IPERF_STATE_TX_SLOW: s_time_slow_us += interval_us; break; + case IPERF_STATE_TX_STALLED: s_time_stalled_us += interval_us; break; + default: break; } + if (next_state != s_current_fsm_state) { switch (next_state) { - case IPERF_STATE_TX: s_edge_tx++; break; - case IPERF_STATE_TX_SLOW: s_edge_slow++; break; - case IPERF_STATE_TX_STALLED: s_edge_stalled++; break; - default: break; + case IPERF_STATE_TX: s_edge_tx++; break; + case IPERF_STATE_TX_SLOW: s_edge_slow++; break; + case IPERF_STATE_TX_STALLED: s_edge_stalled++; break; + default: break; } s_current_fsm_state = next_state; } + led_state_t led_target = (s_current_fsm_state == IPERF_STATE_TX) ? LED_STATE_TRANSMITTING : LED_STATE_TRANSMITTING_SLOW; if (status_led_get_state() != led_target) status_led_set_state(led_target); } last_rate_check = now; packets_since_check = 0; } - next_send_time += ctrl->cfg.pacing_period_us; - } - udp_datagram *hdr = (udp_datagram *)ctrl->buffer; - int64_t final_id = -packet_id; - hdr->id = htonl((uint32_t)(final_id & 0xFFFFFFFF)); - hdr->id2 = htonl((uint32_t)((final_id >> 32) & 0xFFFFFFFF)); - - clock_gettime(CLOCK_REALTIME, &ts); - hdr->tv_sec = htonl((uint32_t)ts.tv_sec); - hdr->tv_usec = htonl(ts.tv_nsec / 1000); - for(int i=0; i<10; i++) { - sendto(sockfd, ctrl->buffer, ctrl->cfg.send_len, 0, (struct sockaddr *)&addr, sizeof(addr)); - vTaskDelay(pdMS_TO_TICKS(2)); + // MONOTONIC UPDATE + next_send_time += period_us; } - ESP_LOGI(TAG, "Sent termination packets (ID: %" PRId64 ")", final_id); close(sockfd); s_stats.running = false; s_session_end_time = esp_timer_get_time(); - s_stats.actual_pps = 0; - status_led_set_state(LED_STATE_CONNECTED); // <--- This is your "Solid Green" - printf("IPERF_STOPPED\n"); + status_led_set_state(LED_STATE_CONNECTED); return ESP_OK; } static void iperf_task(void *arg) { iperf_ctrl_t *ctrl = (iperf_ctrl_t *)arg; - do { + while (1) { s_reload_req = false; ctrl->finish = false; xEventGroupClearBits(s_iperf_event_group, IPERF_STOP_REQ_BIT); - if (ctrl->cfg.flag & IPERF_FLAG_UDP && ctrl->cfg.flag & IPERF_FLAG_CLIENT) { - iperf_start_udp_client(ctrl); - } + iperf_start_udp_client(ctrl); if (s_reload_req) { - ESP_LOGI(TAG, "Hot reloading iperf task with new config..."); - ctrl->cfg = s_next_cfg; - vTaskDelay(pdMS_TO_TICKS(100)); + ESP_LOGI(TAG, "Task reloading config..."); + if (ctrl->buffer_len < ctrl->cfg.send_len + 128) { + free(ctrl->buffer); + ctrl->buffer_len = ctrl->cfg.send_len + 128; + ctrl->buffer = calloc(1, ctrl->buffer_len); + iperf_pattern(ctrl->buffer, ctrl->buffer_len); + } + } else { + break; } - - } while (s_reload_req); + } free(ctrl->buffer); s_iperf_task_handle = NULL; vTaskDelete(NULL); } -void iperf_start(iperf_cfg_t *cfg) { - iperf_cfg_t new_cfg = *cfg; - iperf_read_nvs_config(&new_cfg); - - if (new_cfg.send_len == 0) new_cfg.send_len = 1470; - if (new_cfg.pacing_period_us == 0) new_cfg.pacing_period_us = 10000; - if (new_cfg.burst_count == 0) new_cfg.burst_count = 1; +void iperf_start(void) { + if (!s_staging_initialized) iperf_param_init(); if (s_iperf_task_handle) { - ESP_LOGI(TAG, "Task running. Staging hot reload."); - s_next_cfg = new_cfg; - s_reload_req = true; - iperf_stop(); - printf("IPERF_RELOADING\n"); + ESP_LOGW(TAG, "Already running. Use 'set' to update parameters."); return; } - s_iperf_ctrl.cfg = new_cfg; + // Copy Staging -> Active + s_iperf_ctrl.cfg = s_staging_cfg; s_iperf_ctrl.finish = false; - if (s_iperf_ctrl.buffer == NULL) { - s_iperf_ctrl.buffer_len = s_iperf_ctrl.cfg.send_len + 128; - s_iperf_ctrl.buffer = calloc(1, s_iperf_ctrl.buffer_len); - } - - // Initialize Buffer Pattern + // Allocate Buffer + s_iperf_ctrl.buffer_len = s_iperf_ctrl.cfg.send_len + 128; + s_iperf_ctrl.buffer = calloc(1, s_iperf_ctrl.buffer_len); if (s_iperf_ctrl.buffer) { iperf_pattern(s_iperf_ctrl.buffer, s_iperf_ctrl.buffer_len); } - if (s_iperf_event_group == NULL) { - s_iperf_event_group = xEventGroupCreate(); - } + if (s_iperf_event_group == NULL) s_iperf_event_group = xEventGroupCreate(); xTaskCreate(iperf_task, "iperf", 4096, &s_iperf_ctrl, 5, &s_iperf_task_handle); } @@ -467,7 +537,5 @@ void iperf_stop(void) { if (s_iperf_task_handle) { s_iperf_ctrl.finish = true; if (s_iperf_event_group) xEventGroupSetBits(s_iperf_event_group, IPERF_STOP_REQ_BIT); - } else { - printf("IPERF_STOPPED\n"); } } diff --git a/components/iperf/iperf.h b/components/iperf/iperf.h index d7771eb..0d2d3a6 100644 --- a/components/iperf/iperf.h +++ b/components/iperf/iperf.h @@ -11,7 +11,7 @@ #define IPERF_FLAG_TCP (1 << 2) #define IPERF_FLAG_UDP (1 << 3) -// --- Standard Iperf2 Header Flags (from payloads.h) --- +// --- Standard Iperf2 Header Flags --- #define HEADER_VERSION1 0x80000000 #define HEADER_EXTEND 0x40000000 #define HEADER_UDPTESTS 0x20000000 @@ -21,32 +21,18 @@ #define IPERF_DEFAULT_PORT 5001 #define IPERF_DEFAULT_INTERVAL 3 #define IPERF_DEFAULT_TIME 30 -#define IPERF_TRAFFIC_TASK_PRIORITY 4 -#define IPERF_REPORT_TASK_PRIORITY 5 - -#define IPERF_UDP_TX_LEN (1470) - -// --- NVS Keys --- -#define NVS_KEY_IPERF_ENABLE "iperf_enabled" -#define NVS_KEY_IPERF_PERIOD "iperf_period" -#define NVS_KEY_IPERF_ROLE "iperf_role" -#define NVS_KEY_IPERF_DST_IP "iperf_dst_ip" -#define NVS_KEY_IPERF_PORT "iperf_port" -#define NVS_KEY_IPERF_PROTO "iperf_proto" -#define NVS_KEY_IPERF_BURST "iperf_burst" -#define NVS_KEY_IPERF_LEN "iperf_len" +#define IPERF_UDP_TX_LEN 1470 typedef struct { uint32_t flag; - uint32_t dip; - uint16_t dport; - uint32_t time; - uint32_t pacing_period_us; - uint32_t burst_count; - uint32_t send_len; + uint32_t dip; // Destination IP + uint16_t dport; // Destination Port + uint32_t time; // Duration (seconds), 0 = infinite + uint32_t target_pps; // Packets Per Second (Replaces period) + uint32_t burst_count; // Packets per RTOS tick + uint32_t send_len; // Packet payload length } iperf_cfg_t; -// --- Stats Structure --- typedef struct { bool running; uint32_t config_pps; @@ -54,41 +40,27 @@ typedef struct { float error_rate; } iperf_stats_t; -// --- Wire Formats (Strict Layout) --- - -// 1. Basic UDP Datagram Header (16 bytes) -// Corresponds to 'struct UDP_datagram' in payloads.h -typedef struct { - int32_t id; // Lower 32 bits of seqno - uint32_t tv_sec; // Seconds - uint32_t tv_usec; // Microseconds - int32_t id2; // Upper 32 bits of seqno (when HEADER_SEQNO64B is set) -} udp_datagram; - -// 2. Client Header V1 (Used for First Packet Exchange) -// Corresponds to 'struct client_hdr_v1' in payloads.h -typedef struct { - int32_t flags; - int32_t numThreads; - int32_t mPort; - int32_t mBufLen; - int32_t mWinBand; - int32_t mAmount; -} client_hdr_v1; - // --- API --- -void iperf_init_led(led_strip_handle_t handle); -void iperf_set_pps(uint32_t pps); -uint32_t iperf_get_pps(void); +// Initialization (Call this in app_main to load NVS) +void iperf_param_init(void); -// Get snapshot of current stats -void iperf_get_stats(iperf_stats_t *stats); +// Parameter Management (Running Config) +void iperf_param_get(iperf_cfg_t *out_cfg); +void iperf_param_set(const iperf_cfg_t *new_cfg); -// Print formatted status to stdout (for CLI/Python) +// Save returns true if NVS was actually updated +esp_err_t iperf_param_save(bool *out_changed); + +// Check if dirty +bool iperf_param_is_unsaved(void); + +// Control +void iperf_start(void); // Uses current Running Config +void iperf_stop(void); void iperf_print_status(void); -void iperf_start(iperf_cfg_t *cfg); -void iperf_stop(void); +// Utils +void iperf_init_led(led_strip_handle_t handle); #endif diff --git a/components/wifi_cfg/wifi_cfg.c b/components/wifi_cfg/wifi_cfg.c index bae637c..0643b54 100644 --- a/components/wifi_cfg/wifi_cfg.c +++ b/components/wifi_cfg/wifi_cfg.c @@ -154,3 +154,38 @@ bool wifi_cfg_get_mode(char *mode, uint8_t *mon_ch) { nvs_close(h); return true; } + +// --- State Checkers --- + +bool wifi_cfg_monitor_channel_is_unsaved(uint8_t ram_value) { + nvs_handle_t h; + if (nvs_open("netcfg", NVS_READONLY, &h) != ESP_OK) return true; // Assume dirty if error + + uint8_t nvs_val = 0; + esp_err_t err = nvs_get_u8(h, "mon_ch", &nvs_val); + nvs_close(h); + + if (err != ESP_OK) return true; // Key missing = dirty (using default) + return (nvs_val != ram_value); +} + +// --- Setters --- + +bool wifi_cfg_set_monitor_channel(uint8_t channel) { + nvs_handle_t h; + if (nvs_open("netcfg", NVS_READWRITE, &h) != ESP_OK) return false; + + uint8_t current = 0; + // Check if write is necessary + if (nvs_get_u8(h, "mon_ch", ¤t) == ESP_OK) { + if (current == channel) { + nvs_close(h); + return false; // No change needed + } + } + + nvs_set_u8(h, "mon_ch", channel); + nvs_commit(h); + nvs_close(h); + return true; // Write occurred +} diff --git a/components/wifi_cfg/wifi_cfg.h b/components/wifi_cfg/wifi_cfg.h index c02a6dc..bfb2ef1 100644 --- a/components/wifi_cfg/wifi_cfg.h +++ b/components/wifi_cfg/wifi_cfg.h @@ -11,17 +11,24 @@ extern "C" { // --- Initialization --- void wifi_cfg_init(void); -// --- Getters (Used by Controller) --- +// --- Getters --- bool wifi_cfg_apply_from_nvs(void); wifi_ps_type_t wifi_cfg_get_power_save_mode(void); bool wifi_cfg_get_bandwidth(char *buf, size_t buf_size); bool wifi_cfg_get_mode(char *mode, uint8_t *mon_ch); -// --- Setters (Used by Console) --- +// --- State Checkers (Dirty Flag) --- +// Returns true if RAM value differs from NVS +bool wifi_cfg_monitor_channel_is_unsaved(uint8_t ram_value); + +// --- Setters --- void wifi_cfg_set_credentials(const char* ssid, const char* pass); void wifi_cfg_set_static_ip(const char* ip, const char* mask, const char* gw); void wifi_cfg_set_dhcp(bool enable); +// Returns true if NVS was actually updated, false if values were identical +bool wifi_cfg_set_monitor_channel(uint8_t channel); + #ifdef __cplusplus } #endif diff --git a/components/wifi_controller/CMakeLists.txt b/components/wifi_controller/CMakeLists.txt index 301494d..c9003f0 100644 --- a/components/wifi_controller/CMakeLists.txt +++ b/components/wifi_controller/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS "wifi_controller.c" INCLUDE_DIRS "." REQUIRES esp_wifi freertos - PRIV_REQUIRES csi_manager iperf status_led wifi_monitor gps_sync log esp_netif) + PRIV_REQUIRES csi_manager iperf status_led wifi_monitor wifi_cfg gps_sync log esp_netif) diff --git a/components/wifi_controller/wifi_controller.c b/components/wifi_controller/wifi_controller.c index 53f48b0..cb924eb 100644 --- a/components/wifi_controller/wifi_controller.c +++ b/components/wifi_controller/wifi_controller.c @@ -10,6 +10,7 @@ #include "status_led.h" #include "wifi_monitor.h" #include "gps_sync.h" +#include "wifi_cfg.h" // 1. GUARDED INCLUDE #ifdef CONFIG_ESP_WIFI_CSI_ENABLED @@ -23,6 +24,7 @@ static uint8_t s_monitor_channel = 6; static bool s_monitor_enabled = false; static uint32_t s_monitor_frame_count = 0; static TaskHandle_t s_monitor_stats_task_handle = NULL; +static uint8_t s_monitor_channel_staging = 6; //RAM Staging Variable // --- Helper: Log Collapse Events --- static void log_collapse_event(float nav_duration_us, int rssi, int retry) { @@ -85,15 +87,51 @@ static void auto_monitor_task_func(void *arg) { } // --- API Implementation --- - void wifi_ctl_init(void) { s_current_mode = WIFI_CTL_MODE_STA; s_monitor_enabled = false; s_monitor_frame_count = 0; + + // Load Initial Staging from NVS + char mode_ignored[16]; + wifi_cfg_get_mode(mode_ignored, &s_monitor_channel_staging); + if (s_monitor_channel_staging == 0) s_monitor_channel_staging = 6; } -esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bandwidth) { - if (s_current_mode == WIFI_CTL_MODE_MONITOR) { +void wifi_ctl_param_set_monitor_channel(uint8_t channel) { + if (channel >= 1 && channel <= 14) { + s_monitor_channel_staging = channel; + } +} + +uint8_t wifi_ctl_param_get_monitor_channel(void) { + return s_monitor_channel_staging; +} + +bool wifi_ctl_param_save(void) { + bool changed = wifi_cfg_set_monitor_channel(s_monitor_channel_staging); + if (changed) { + ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", s_monitor_channel_staging); + } + return changed; +} + +void wifi_ctl_param_reload(void) { + char mode_ignored[16]; + uint8_t ch = 0; + wifi_cfg_get_mode(mode_ignored, &ch); + if (ch > 0) s_monitor_channel_staging = ch; + ESP_LOGI(TAG, "Reloaded monitor channel: %d", s_monitor_channel_staging); +} + +bool wifi_ctl_param_is_unsaved(void) { + return wifi_cfg_monitor_channel_is_unsaved(s_monitor_channel_staging); +} +esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel_override, wifi_bandwidth_t bandwidth) { + // If override is 0, use Staging + uint8_t channel = (channel_override > 0) ? channel_override : s_monitor_channel_staging; + + if (s_current_mode == WIFI_CTL_MODE_MONITOR && s_monitor_channel == channel) { ESP_LOGW(TAG, "Already in monitor mode"); return ESP_OK; } diff --git a/components/wifi_controller/wifi_controller.h b/components/wifi_controller/wifi_controller.h index 5346cbf..7b3cdcd 100644 --- a/components/wifi_controller/wifi_controller.h +++ b/components/wifi_controller/wifi_controller.h @@ -2,6 +2,7 @@ #include "esp_err.h" #include "esp_wifi.h" +#include // Added #ifdef __cplusplus extern "C" { @@ -12,44 +13,24 @@ typedef enum { WIFI_CTL_MODE_MONITOR } wifi_ctl_mode_t; -/** - * @brief Initialize the WiFi Controller - */ void wifi_ctl_init(void); -/** - * @brief Switch operation mode to Monitor (Sniffer) - * @param channel WiFi channel (1-165) - * @param bandwidth Bandwidth (usually WIFI_BW_HT20 for monitor) - */ -esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bandwidth); +// --- Parameter Management (Set/Get/Save/Read) --- +void wifi_ctl_param_set_monitor_channel(uint8_t channel); +uint8_t wifi_ctl_param_get_monitor_channel(void); +bool wifi_ctl_param_save(void); // Returns true if NVS updated +void wifi_ctl_param_reload(void); +bool wifi_ctl_param_is_unsaved(void); // Dirty Check -/** - * @brief Switch operation mode to Station (Client) - * @param band_mode Band preference (Auto, 2G only, 5G only) - */ +// --- Actions --- +// Update: channel_override=0 uses Staged config +esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel_override, wifi_bandwidth_t bandwidth); esp_err_t wifi_ctl_switch_to_sta(wifi_band_mode_t band_mode); - -/** - * @brief Start the auto-monitor task - * Waits for connection, waits for GPS, then switches to monitor mode. - * @param channel Channel to monitor - */ void wifi_ctl_auto_monitor_start(uint8_t channel); -/** - * @brief Get current operation mode - */ +// --- Getters --- wifi_ctl_mode_t wifi_ctl_get_mode(void); - -/** - * @brief Get the current monitor channel - */ uint8_t wifi_ctl_get_monitor_channel(void); - -/** - * @brief Get total frames captured in monitor mode - */ uint32_t wifi_ctl_get_monitor_frame_count(void); #ifdef __cplusplus diff --git a/main/main.c b/main/main.c index 9e72b2e..7636fc8 100644 --- a/main/main.c +++ b/main/main.c @@ -18,6 +18,7 @@ #include "wifi_controller.h" #include "wifi_cfg.h" #include "app_console.h" +#include "iperf.h" #ifdef CONFIG_ESP_WIFI_CSI_ENABLED #include "csi_log.h" @@ -28,6 +29,25 @@ static const char *TAG = "MAIN"; +// --- Global Prompt Buffer (Mutable) --- +static char s_cli_prompt[32] = "esp32> "; + +// --- Prompt Updater --- +// This is called by app_console.c commands whenever a setting is changed. +void app_console_update_prompt(void) { + bool dirty = false; + + // Check if any component has unsaved changes (RAM != NVS) + if (wifi_ctl_param_is_unsaved()) dirty = true; + if (iperf_param_is_unsaved()) dirty = true; + + if (dirty) { + snprintf(s_cli_prompt, sizeof(s_cli_prompt), "esp32*> "); + } else { + snprintf(s_cli_prompt, sizeof(s_cli_prompt), "esp32> "); + } +} + // --- System Commands --- static int cmd_restart(int argc, char **argv) { @@ -82,13 +102,14 @@ void app_main(void) { // 4. Initialize WiFi Controller (Loads config from NVS automatically) wifi_ctl_init(); + iperf_param_init(); // 5. Initialize Console esp_console_repl_t *repl = NULL; esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); - // This prompt is the anchor for your Python script - repl_config.prompt = "esp32> "; + // CRITICAL: Point the prompt to our mutable buffer + repl_config.prompt = s_cli_prompt; repl_config.max_cmdline_length = 1024; // Install UART driver for Console (Standard IO) @@ -99,7 +120,11 @@ void app_main(void) { register_system_common(); app_console_register_commands(); - // 7. Start Shell + // 7. Initial Prompt State Check + // (Sets it to "esp32*>" immediately if NVS is empty/dirty on boot) + app_console_update_prompt(); + + // 8. Start Shell printf("\n ==================================================\n"); printf(" | ESP32 iPerf Shell - Ready |\n"); printf(" | Type 'help' for commands |\n");