iperf 2 control support
This commit is contained in:
parent
f9bcb0f7c4
commit
cab824265d
|
|
@ -1,4 +1,4 @@
|
||||||
idf_component_register(SRCS "app_console.c"
|
idf_component_register(SRCS "app_console.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES console
|
REQUIRES console
|
||||||
PRIV_REQUIRES wifi_controller csi_manager status_led gps_sync esp_wifi)
|
PRIV_REQUIRES wifi_controller csi_manager status_led gps_sync esp_wifi iperf)
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,59 @@
|
||||||
#include "wifi_controller.h"
|
#include "wifi_controller.h"
|
||||||
#include "status_led.h"
|
#include "status_led.h"
|
||||||
#include "gps_sync.h"
|
#include "gps_sync.h"
|
||||||
|
#include "iperf.h"
|
||||||
|
|
||||||
// 1. GUARDED INCLUDE
|
|
||||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
||||||
#include "csi_manager.h"
|
#include "csi_manager.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// --- Command Handlers ---
|
// --- Command Handlers ---
|
||||||
|
|
||||||
static int cmd_mode_monitor(int argc, char **argv) {
|
static int cmd_iperf(int argc, char **argv) {
|
||||||
// Default to current channel if not provided
|
if (argc < 2) {
|
||||||
int channel = wifi_ctl_get_monitor_channel();
|
printf("Usage: iperf <start|stop|pps|status>\n");
|
||||||
|
return 1;
|
||||||
if (argc > 1) {
|
|
||||||
channel = atoi(argv[1]);
|
|
||||||
if (channel < 1 || channel > 165) {
|
|
||||||
printf("Error: Invalid channel %d\n", channel);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default bandwidth HT20 for monitor mode
|
if (strcmp(argv[1], "start") == 0) {
|
||||||
|
iperf_cfg_t cfg = { .time = 0 }; // Infinite
|
||||||
|
iperf_start(&cfg);
|
||||||
|
printf("IPERF_STARTED\n");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} else if (strcmp(argv[1], "stop") == 0) {
|
||||||
|
iperf_stop();
|
||||||
|
printf("IPERF_STOPPED\n");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} else if (strcmp(argv[1], "pps") == 0) {
|
||||||
|
// Syntax: iperf pps 100
|
||||||
|
if (argc < 3) {
|
||||||
|
printf("Error: Missing value. Usage: iperf pps <rate>\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int pps = atoi(argv[2]);
|
||||||
|
if (pps <= 0) {
|
||||||
|
printf("Error: Invalid PPS.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
iperf_set_pps((uint32_t)pps);
|
||||||
|
printf("IPERF_PPS_UPDATED: %d\n", pps);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} else if (strcmp(argv[1], "status") == 0) {
|
||||||
|
uint32_t pps = iperf_get_pps();
|
||||||
|
printf("IPERF_STATUS: PPS=%lu\n", (unsigned long)pps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Error: Unknown subcommand '%s'.\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_mode_monitor(int argc, char **argv) {
|
||||||
|
int channel = wifi_ctl_get_monitor_channel();
|
||||||
|
if (argc > 1) channel = atoi(argv[1]);
|
||||||
if (wifi_ctl_switch_to_monitor(channel, WIFI_BW_HT20) != ESP_OK) {
|
if (wifi_ctl_switch_to_monitor(channel, WIFI_BW_HT20) != ESP_OK) {
|
||||||
printf("Failed to switch to monitor mode\n");
|
printf("Failed to switch to monitor mode\n");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -39,79 +71,38 @@ static int cmd_mode_monitor(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cmd_mode_sta(int argc, char **argv) {
|
static int cmd_mode_sta(int argc, char **argv) {
|
||||||
// Simple switch to STA, auto band
|
|
||||||
if (wifi_ctl_switch_to_sta(WIFI_BAND_MODE_AUTO) != ESP_OK) {
|
if (wifi_ctl_switch_to_sta(WIFI_BAND_MODE_AUTO) != ESP_OK) {
|
||||||
printf("Failed to switch to STA mode\n");
|
printf("Failed to switch to STA mode\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("Switching to STA mode...\n");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cmd_mode_status(int argc, char **argv) {
|
static int cmd_mode_status(int argc, char **argv) {
|
||||||
wifi_ctl_mode_t mode = wifi_ctl_get_mode();
|
wifi_ctl_mode_t mode = wifi_ctl_get_mode();
|
||||||
|
|
||||||
printf("\n=== WiFi Mode Status ===\n");
|
printf("\n=== WiFi Mode Status ===\n");
|
||||||
printf("Current mode: %s\n", mode == WIFI_CTL_MODE_STA ? "STA" : "MONITOR");
|
printf("Current mode: %s\n", mode == WIFI_CTL_MODE_STA ? "STA" : "MONITOR");
|
||||||
printf("LED state: %d\n", status_led_get_state());
|
printf("LED state: %d\n", status_led_get_state());
|
||||||
printf("GPS synced: %s\n", gps_is_synced() ? "Yes" : "No");
|
printf("GPS synced: %s\n", gps_is_synced() ? "Yes" : "No");
|
||||||
|
|
||||||
if (mode == WIFI_CTL_MODE_STA) {
|
|
||||||
// 2. GUARDED STATUS PRINT
|
|
||||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
|
||||||
printf("CSI Enabled: %s\n", csi_mgr_is_enabled() ? "Yes" : "No");
|
|
||||||
printf("CSI Packets: %lu\n", (unsigned long)csi_mgr_get_packet_count());
|
|
||||||
#else
|
|
||||||
printf("CSI Support: Disabled in build\n");
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
printf("Monitor Ch: %d\n", wifi_ctl_get_monitor_channel());
|
|
||||||
printf("Captured: %lu frames\n", (unsigned long)wifi_ctl_get_monitor_frame_count());
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cmd_csi_dump(int argc, char **argv) {
|
static int cmd_csi_dump(int argc, char **argv) {
|
||||||
if (wifi_ctl_get_mode() != WIFI_CTL_MODE_STA) {
|
|
||||||
printf("Error: CSI only available in STA mode\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. GUARDED DUMP ACTION
|
|
||||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
||||||
printf("Scheduling CSI dump...\n");
|
|
||||||
csi_mgr_schedule_dump();
|
csi_mgr_schedule_dump();
|
||||||
#else
|
#else
|
||||||
printf("Error: CSI feature is disabled in this firmware build.\n");
|
printf("Error: CSI feature is disabled in this firmware build.\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Registration ---
|
|
||||||
|
|
||||||
void app_console_register_commands(void) {
|
void app_console_register_commands(void) {
|
||||||
const esp_console_cmd_t cmds[] = {
|
const esp_console_cmd_t cmds[] = {
|
||||||
{
|
{ .command = "mode_monitor", .help = "Switch to monitor mode", .func = &cmd_mode_monitor },
|
||||||
.command = "mode_monitor",
|
{ .command = "mode_sta", .help = "Switch to STA mode", .func = &cmd_mode_sta },
|
||||||
.help = "Switch to monitor mode (Usage: mode_monitor [channel])",
|
{ .command = "mode_status", .help = "Show device status", .func = &cmd_mode_status },
|
||||||
.func = &cmd_mode_monitor
|
{ .command = "csi_dump", .help = "Dump collected CSI data", .func = &cmd_csi_dump },
|
||||||
},
|
{ .command = "iperf", .help = "Control iperf (start, stop, pps, status)", .func = &cmd_iperf },
|
||||||
{
|
|
||||||
.command = "mode_sta",
|
|
||||||
.help = "Switch to STA mode",
|
|
||||||
.func = &cmd_mode_sta
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.command = "mode_status",
|
|
||||||
.help = "Show device status",
|
|
||||||
.func = &cmd_mode_status
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.command = "csi_dump",
|
|
||||||
.help = "Dump collected CSI data to UART",
|
|
||||||
.func = &cmd_csi_dump
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(cmds)/sizeof(cmds[0]); i++) {
|
for (int i = 0; i < sizeof(cmds)/sizeof(cmds[0]); i++) {
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,16 @@ static EventGroupHandle_t s_iperf_event_group = NULL;
|
||||||
#define IPERF_IP_READY_BIT (1 << 0)
|
#define IPERF_IP_READY_BIT (1 << 0)
|
||||||
#define IPERF_STOP_REQ_BIT (1 << 1)
|
#define IPERF_STOP_REQ_BIT (1 << 1)
|
||||||
|
|
||||||
|
// Check rate every 500ms
|
||||||
|
#define RATE_CHECK_INTERVAL_US 500000
|
||||||
|
// Minimum gap (Safety Limit)
|
||||||
|
#define MIN_PACING_INTERVAL_US 100
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
iperf_cfg_t cfg;
|
iperf_cfg_t cfg;
|
||||||
bool finish;
|
bool finish;
|
||||||
uint32_t total_len;
|
|
||||||
uint32_t buffer_len;
|
uint32_t buffer_len;
|
||||||
uint8_t *buffer;
|
uint8_t *buffer;
|
||||||
uint32_t sockfd;
|
|
||||||
} iperf_ctrl_t;
|
} iperf_ctrl_t;
|
||||||
|
|
||||||
static iperf_ctrl_t s_iperf_ctrl = {0};
|
static iperf_ctrl_t s_iperf_ctrl = {0};
|
||||||
|
|
@ -42,16 +45,12 @@ static esp_event_handler_instance_t instance_got_ip;
|
||||||
|
|
||||||
static void iperf_network_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
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 (s_iperf_event_group == NULL) return;
|
||||||
|
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
|
|
||||||
// Do nothing, wait for IP
|
|
||||||
}
|
|
||||||
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
||||||
xEventGroupSetBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
xEventGroupSetBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
||||||
}
|
}
|
||||||
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||||
xEventGroupClearBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
xEventGroupClearBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
||||||
status_led_set_state(LED_STATE_FAILED); // Red Blink on disconnect
|
status_led_set_state(LED_STATE_NO_CONFIG); // Yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,12 +66,10 @@ static bool iperf_wait_for_ip(void) {
|
||||||
xEventGroupSetBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
xEventGroupSetBits(s_iperf_event_group, IPERF_IP_READY_BIT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "Waiting for IP address...");
|
|
||||||
EventBits_t bits = xEventGroupWaitBits(s_iperf_event_group, IPERF_IP_READY_BIT | IPERF_STOP_REQ_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
EventBits_t bits = xEventGroupWaitBits(s_iperf_event_group, IPERF_IP_READY_BIT | IPERF_STOP_REQ_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id);
|
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);
|
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip);
|
||||||
if (bits & IPERF_STOP_REQ_BIT) return false;
|
return !(bits & IPERF_STOP_REQ_BIT);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void trim_whitespace(char *str) {
|
static void trim_whitespace(char *str) {
|
||||||
|
|
@ -83,216 +80,155 @@ static void trim_whitespace(char *str) {
|
||||||
|
|
||||||
static void iperf_read_nvs_config(iperf_cfg_t *cfg) {
|
static void iperf_read_nvs_config(iperf_cfg_t *cfg) {
|
||||||
nvs_handle_t my_handle;
|
nvs_handle_t my_handle;
|
||||||
esp_err_t err = nvs_open("storage", NVS_READONLY, &my_handle);
|
if (nvs_open("storage", NVS_READONLY, &my_handle) != ESP_OK) return;
|
||||||
uint32_t val = 0;
|
|
||||||
size_t required_size;
|
|
||||||
|
|
||||||
// --- DEFAULTS ---
|
uint32_t val;
|
||||||
// Change: 5ms Gap to test Media Access Contention
|
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PERIOD, &val) == ESP_OK) cfg->pacing_period_us = val;
|
||||||
cfg->pacing_period_us = 5000;
|
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_BURST, &val) == ESP_OK) cfg->burst_count = val;
|
||||||
cfg->burst_count = 1;
|
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_LEN, &val) == ESP_OK) cfg->send_len = val;
|
||||||
cfg->send_len = IPERF_UDP_TX_LEN;
|
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PORT, &val) == ESP_OK) cfg->dport = (uint16_t)val;
|
||||||
cfg->dport = 5001;
|
|
||||||
cfg->dip = inet_addr("192.168.1.50"); // Default Target if NVS missing
|
|
||||||
if (cfg->time == 0) cfg->time = UINT32_MAX;
|
|
||||||
|
|
||||||
if (err != ESP_OK) return;
|
size_t req;
|
||||||
|
if (nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, NULL, &req) == ESP_OK) {
|
||||||
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PERIOD, &val) == ESP_OK && val > 0) cfg->pacing_period_us = val;
|
char *ip_str = malloc(req);
|
||||||
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_BURST, &val) == ESP_OK && val > 0) cfg->burst_count = val;
|
|
||||||
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_LEN, &val) == ESP_OK && val > 0) cfg->send_len = val;
|
|
||||||
|
|
||||||
if (nvs_get_u32(my_handle, NVS_KEY_IPERF_PORT, &val) == ESP_OK && val > 0) {
|
|
||||||
cfg->dport = (uint16_t)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- RESTORED NVS IP LOGIC (NO OVERRIDE) ---
|
|
||||||
if (nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, NULL, &required_size) == ESP_OK) {
|
|
||||||
char *ip_str = malloc(required_size);
|
|
||||||
if (ip_str) {
|
if (ip_str) {
|
||||||
nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, ip_str, &required_size);
|
nvs_get_str(my_handle, NVS_KEY_IPERF_DST_IP, ip_str, &req);
|
||||||
trim_whitespace(ip_str);
|
trim_whitespace(ip_str);
|
||||||
|
|
||||||
// FIX: Use whatever is in NVS, do not auto-correct
|
|
||||||
cfg->dip = inet_addr(ip_str);
|
cfg->dip = inet_addr(ip_str);
|
||||||
ESP_LOGI(TAG, "NVS Target IP: %s", ip_str);
|
|
||||||
|
|
||||||
free(ip_str);
|
free(ip_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nvs_close(my_handle);
|
nvs_close(my_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... (Unused functions omitted) ...
|
void iperf_set_pps(uint32_t pps) {
|
||||||
static void __attribute__((unused)) socket_send(int sockfd, const uint8_t *buffer, int len) {}
|
if (pps == 0) pps = 1;
|
||||||
static int __attribute__((unused)) socket_recv(int sockfd, uint8_t *buffer, int len, TickType_t timeout_ticks) { return 0; }
|
uint32_t period_us = 1000000 / pps;
|
||||||
static esp_err_t iperf_start_tcp_server(iperf_ctrl_t *ctrl) { ESP_LOGW(TAG, "TCP Server not implemented"); return ESP_FAIL; }
|
|
||||||
static esp_err_t iperf_start_tcp_client(iperf_ctrl_t *ctrl) { ESP_LOGW(TAG, "TCP Client not implemented"); return ESP_FAIL; }
|
|
||||||
static esp_err_t iperf_start_udp_server(iperf_ctrl_t *ctrl) { ESP_LOGW(TAG, "UDP Server not implemented"); return ESP_FAIL; }
|
|
||||||
|
|
||||||
static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl)
|
if (period_us < MIN_PACING_INTERVAL_US) {
|
||||||
{
|
period_us = MIN_PACING_INTERVAL_US;
|
||||||
|
ESP_LOGW(TAG, "PPS %" PRIu32 " clamped to max safe rate", pps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_iperf_task_handle != NULL) {
|
||||||
|
s_iperf_ctrl.cfg.pacing_period_us = period_us;
|
||||||
|
ESP_LOGI(TAG, "Runtime pacing updated to %" PRIu32 " PPS", 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()) return ESP_FAIL;
|
if (!iperf_wait_for_ip()) return ESP_FAIL;
|
||||||
|
|
||||||
struct sockaddr_in addr;
|
struct sockaddr_in addr;
|
||||||
int sockfd;
|
|
||||||
struct timespec ts;
|
|
||||||
|
|
||||||
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
||||||
if (sockfd < 0) {
|
|
||||||
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
|
||||||
status_led_set_state(LED_STATE_FAILED);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(ctrl->cfg.dport > 0 ? ctrl->cfg.dport : 5001);
|
addr.sin_port = htons(ctrl->cfg.dport > 0 ? ctrl->cfg.dport : 5001);
|
||||||
addr.sin_addr.s_addr = ctrl->cfg.dip;
|
addr.sin_addr.s_addr = ctrl->cfg.dip;
|
||||||
|
|
||||||
char ip_str[INET_ADDRSTRLEN];
|
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);
|
if (sockfd < 0) {
|
||||||
ESP_LOGI(TAG, "Target: %s:%d", ip_str, ntohs(addr.sin_port));
|
status_led_set_state(LED_STATE_FAILED);
|
||||||
|
return ESP_FAIL;
|
||||||
uint32_t burst_count = ctrl->cfg.burst_count;
|
|
||||||
uint32_t payload_len = ctrl->cfg.send_len;
|
|
||||||
uint32_t pacing_period_us = ctrl->cfg.pacing_period_us;
|
|
||||||
|
|
||||||
double total_mbps = (double)((uint64_t)burst_count * payload_len * 8 * (1000000.0 / pacing_period_us)) / 1000000.0;
|
|
||||||
ESP_LOGI(TAG, "Pacing: %" PRIu32 " pkts every %" PRIu32 " us (Approx %.2f Mbps)", burst_count, pacing_period_us, total_mbps);
|
|
||||||
|
|
||||||
client_hdr_v1 *client_hdr = (client_hdr_v1 *)(ctrl->buffer + sizeof(udp_datagram));
|
|
||||||
client_hdr->flags = htonl(HEADER_VERSION1);
|
|
||||||
client_hdr->numThreads = htonl(1);
|
|
||||||
client_hdr->mPort = htonl(ntohs(addr.sin_port));
|
|
||||||
client_hdr->mBufLen = htonl(payload_len);
|
|
||||||
client_hdr->mWinBand = htonl(0);
|
|
||||||
client_hdr->mAmount = htonl(-(int)(10000));
|
|
||||||
|
|
||||||
status_led_set_state(LED_STATE_TRANSMITTING);
|
|
||||||
|
|
||||||
uint64_t total_len = 0;
|
|
||||||
uint32_t packet_count = 0;
|
|
||||||
int64_t start_time_us = esp_timer_get_time();
|
|
||||||
int64_t next_send_time = start_time_us;
|
|
||||||
int64_t end_time_us = (ctrl->cfg.time == UINT32_MAX) ? INT64_MAX : start_time_us + (int64_t)ctrl->cfg.time * 1000000LL;
|
|
||||||
|
|
||||||
// Error Tracking
|
|
||||||
int64_t enomem_start_time = 0;
|
|
||||||
const int64_t ENOMEM_TIMEOUT_US = 10 * 1000 * 1000; // 10 Seconds
|
|
||||||
|
|
||||||
while (!ctrl->finish && esp_timer_get_time() < end_time_us) {
|
|
||||||
|
|
||||||
int64_t current_time = esp_timer_get_time();
|
|
||||||
int64_t time_to_wait = next_send_time - current_time;
|
|
||||||
|
|
||||||
if (time_to_wait > 0) {
|
|
||||||
if (time_to_wait > 2000) {
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(time_to_wait / 1000));
|
|
||||||
} else {
|
|
||||||
while (esp_timer_get_time() < next_send_time) {
|
|
||||||
taskYIELD();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int k = 0; k < burst_count; k++) {
|
|
||||||
udp_datagram *header = (udp_datagram *)ctrl->buffer;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
||||||
header->id = htonl(packet_count);
|
|
||||||
header->tv_sec = htonl(ts.tv_sec);
|
|
||||||
header->tv_usec = htonl(ts.tv_nsec / 1000);
|
|
||||||
header->id2 = 0;
|
|
||||||
|
|
||||||
int send_len = sendto(sockfd, ctrl->buffer, payload_len, 0, (struct sockaddr *)&addr, sizeof(addr));
|
|
||||||
|
|
||||||
if (send_len > 0) {
|
|
||||||
total_len += send_len;
|
|
||||||
packet_count++;
|
|
||||||
|
|
||||||
enomem_start_time = 0;
|
|
||||||
|
|
||||||
if (status_led_get_state() != LED_STATE_TRANSMITTING) {
|
|
||||||
status_led_set_state(LED_STATE_TRANSMITTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// --- ERROR HANDLING ---
|
|
||||||
if (errno == 12) { // ENOMEM
|
|
||||||
int64_t now = esp_timer_get_time();
|
|
||||||
|
|
||||||
if (status_led_get_state() != LED_STATE_STALLED) {
|
|
||||||
status_led_set_state(LED_STATE_STALLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enomem_start_time == 0) {
|
|
||||||
enomem_start_time = now;
|
|
||||||
} else if ((now - enomem_start_time) > ENOMEM_TIMEOUT_US) {
|
|
||||||
ESP_LOGE(TAG, "UDP send ENOMEM persistent for 10s. STOPPING.");
|
|
||||||
status_led_set_state(LED_STATE_FAILED);
|
|
||||||
goto exit_client;
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "UDP send failed: %d. STOPPING.", errno);
|
|
||||||
status_led_set_state(LED_STATE_FAILED);
|
|
||||||
goto exit_client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next_send_time += pacing_period_us;
|
|
||||||
if (esp_timer_get_time() > next_send_time + 4000) next_send_time = esp_timer_get_time() + pacing_period_us;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_client:
|
status_led_set_state(LED_STATE_TRANSMITTING_SLOW); // Default state
|
||||||
|
|
||||||
|
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 enomem_start_time = 0;
|
||||||
|
|
||||||
|
while (!ctrl->finish && esp_timer_get_time() < end_time) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (int k = 0; k < ctrl->cfg.burst_count; k++) {
|
||||||
|
int sent = sendto(sockfd, ctrl->buffer, ctrl->cfg.send_len, 0, (struct sockaddr *)&addr, sizeof(addr));
|
||||||
|
|
||||||
|
if (sent > 0) {
|
||||||
|
packets_since_check++;
|
||||||
|
enomem_start_time = 0;
|
||||||
|
|
||||||
|
// --- DYNAMIC RATE HEALTH CHECK ---
|
||||||
|
if (now - last_rate_check > RATE_CHECK_INTERVAL_US) {
|
||||||
|
int64_t interval = now - last_rate_check;
|
||||||
|
double cycles = (double)interval / (double)ctrl->cfg.pacing_period_us;
|
||||||
|
uint32_t expected_pkts = (uint32_t)(cycles * ctrl->cfg.burst_count);
|
||||||
|
|
||||||
|
// Threshold: 75% of expected rate
|
||||||
|
uint32_t threshold = (expected_pkts * 3) / 4;
|
||||||
|
|
||||||
|
led_state_t target = (packets_since_check >= threshold)
|
||||||
|
? LED_STATE_TRANSMITTING // Busy (Fast Flash)
|
||||||
|
: LED_STATE_TRANSMITTING_SLOW; // Slow (Slow Pulse)
|
||||||
|
|
||||||
|
if (status_led_get_state() != target) status_led_set_state(target);
|
||||||
|
|
||||||
|
last_rate_check = now;
|
||||||
|
packets_since_check = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (errno == 12) { // ENOMEM
|
||||||
|
if (status_led_get_state() != LED_STATE_STALLED) status_led_set_state(LED_STATE_STALLED);
|
||||||
|
|
||||||
|
if (enomem_start_time == 0) enomem_start_time = now;
|
||||||
|
else if (now - enomem_start_time > 10000000) {
|
||||||
|
status_led_set_state(LED_STATE_FAILED); goto exit;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
} else {
|
||||||
|
status_led_set_state(LED_STATE_FAILED); goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_send_time += ctrl->cfg.pacing_period_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
if (status_led_get_state() != LED_STATE_FAILED) {
|
if (status_led_get_state() != LED_STATE_FAILED) {
|
||||||
status_led_set_state(LED_STATE_CONNECTED);
|
EventBits_t bits = xEventGroupGetBits(s_iperf_event_group);
|
||||||
|
status_led_set_state((bits & IPERF_IP_READY_BIT) ? LED_STATE_CONNECTED : LED_STATE_NO_CONFIG);
|
||||||
}
|
}
|
||||||
close(sockfd);
|
close(sockfd);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void iperf_task(void *arg) {
|
static void iperf_task(void *arg) {
|
||||||
iperf_ctrl_t *ctrl = (iperf_ctrl_t *)arg;
|
iperf_start_udp_client((iperf_ctrl_t *)arg);
|
||||||
iperf_start_udp_client(ctrl);
|
free(((iperf_ctrl_t *)arg)->buffer);
|
||||||
if (ctrl->buffer) { free(ctrl->buffer); ctrl->buffer = NULL; }
|
|
||||||
if (s_iperf_event_group) { vEventGroupDelete(s_iperf_event_group); s_iperf_event_group = NULL; }
|
|
||||||
s_iperf_task_handle = NULL;
|
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void iperf_start(iperf_cfg_t *cfg) {
|
void iperf_start(iperf_cfg_t *cfg) {
|
||||||
nvs_handle_t my_handle;
|
if (s_iperf_task_handle) return;
|
||||||
uint8_t enabled = 1;
|
s_iperf_ctrl.cfg = *cfg;
|
||||||
if (nvs_open("storage", NVS_READONLY, &my_handle) == ESP_OK) {
|
if (s_iperf_ctrl.cfg.send_len == 0) s_iperf_ctrl.cfg.send_len = 1470;
|
||||||
nvs_get_u8(my_handle, NVS_KEY_IPERF_ENABLE, &enabled);
|
if (s_iperf_ctrl.cfg.pacing_period_us == 0) s_iperf_ctrl.cfg.pacing_period_us = 10000;
|
||||||
nvs_close(my_handle);
|
if (s_iperf_ctrl.cfg.burst_count == 0) s_iperf_ctrl.cfg.burst_count = 1;
|
||||||
}
|
|
||||||
if (enabled == 0) return;
|
|
||||||
if (s_iperf_task_handle != NULL) return;
|
|
||||||
|
|
||||||
memcpy(&s_iperf_ctrl.cfg, cfg, sizeof(iperf_cfg_t));
|
|
||||||
s_iperf_ctrl.cfg.flag = IPERF_FLAG_CLIENT | IPERF_FLAG_UDP;
|
|
||||||
|
|
||||||
iperf_read_nvs_config(&s_iperf_ctrl.cfg);
|
iperf_read_nvs_config(&s_iperf_ctrl.cfg);
|
||||||
s_iperf_ctrl.finish = false;
|
s_iperf_ctrl.finish = false;
|
||||||
|
s_iperf_ctrl.buffer_len = s_iperf_ctrl.cfg.send_len + 128;
|
||||||
uint32_t alloc_len = s_iperf_ctrl.cfg.send_len > 0 ? s_iperf_ctrl.cfg.send_len : IPERF_UDP_TX_LEN;
|
s_iperf_ctrl.buffer = calloc(1, s_iperf_ctrl.buffer_len);
|
||||||
uint32_t min_hdr = sizeof(udp_datagram) + sizeof(client_hdr_v1);
|
|
||||||
if (alloc_len < min_hdr) alloc_len = min_hdr;
|
|
||||||
|
|
||||||
s_iperf_ctrl.buffer_len = alloc_len;
|
|
||||||
s_iperf_ctrl.buffer = malloc(s_iperf_ctrl.buffer_len);
|
|
||||||
memset(s_iperf_ctrl.buffer, 0, s_iperf_ctrl.buffer_len);
|
|
||||||
|
|
||||||
s_iperf_event_group = xEventGroupCreate();
|
s_iperf_event_group = xEventGroupCreate();
|
||||||
xTaskCreate(iperf_task, "iperf", 4096, &s_iperf_ctrl, IPERF_TRAFFIC_TASK_PRIORITY, &s_iperf_task_handle);
|
xTaskCreate(iperf_task, "iperf", 4096, &s_iperf_ctrl, 5, &s_iperf_task_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void iperf_stop(void) {
|
void iperf_stop(void) {
|
||||||
if (s_iperf_task_handle != NULL) {
|
if (s_iperf_task_handle) {
|
||||||
s_iperf_ctrl.finish = true;
|
s_iperf_ctrl.finish = true;
|
||||||
if (s_iperf_event_group) xEventGroupSetBits(s_iperf_event_group, IPERF_STOP_REQ_BIT);
|
if (s_iperf_event_group) xEventGroupSetBits(s_iperf_event_group, IPERF_STOP_REQ_BIT);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,18 @@ typedef struct {
|
||||||
*/
|
*/
|
||||||
void iperf_init_led(led_strip_handle_t handle);
|
void iperf_init_led(led_strip_handle_t handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the target pacing rate in Packets Per Second (PPS).
|
||||||
|
* Converts PPS to microsecond interval internally.
|
||||||
|
* @param pps Target rate (e.g., 100 = 100 packets/sec)
|
||||||
|
*/
|
||||||
|
void iperf_set_pps(uint32_t pps);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current target rate in PPS.
|
||||||
|
*/
|
||||||
|
uint32_t iperf_get_pps(void);
|
||||||
|
|
||||||
void iperf_start(iperf_cfg_t *cfg);
|
void iperf_start(iperf_cfg_t *cfg);
|
||||||
void iperf_stop(void);
|
void iperf_stop(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,79 +5,51 @@
|
||||||
#include "led_strip.h"
|
#include "led_strip.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
|
||||||
static const char *TAG = "STATUS_LED";
|
|
||||||
|
|
||||||
static led_strip_handle_t s_led_strip = NULL;
|
static led_strip_handle_t s_led_strip = NULL;
|
||||||
static bool s_is_rgb = false;
|
static bool s_is_rgb = false;
|
||||||
static int s_gpio_pin = -1;
|
static int s_gpio_pin = -1;
|
||||||
static volatile led_state_t s_current_state = LED_STATE_NO_CONFIG;
|
static volatile led_state_t s_current_state = LED_STATE_NO_CONFIG;
|
||||||
|
|
||||||
// Internal helper to abstract hardware differences
|
|
||||||
static void set_color(uint8_t r, uint8_t g, uint8_t b) {
|
static void set_color(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
if (s_is_rgb && s_led_strip) {
|
if (s_is_rgb && s_led_strip) {
|
||||||
// Addressable RGB (WS2812)
|
|
||||||
led_strip_set_pixel(s_led_strip, 0, r, g, b);
|
led_strip_set_pixel(s_led_strip, 0, r, g, b);
|
||||||
led_strip_refresh(s_led_strip);
|
led_strip_refresh(s_led_strip);
|
||||||
} else if (!s_is_rgb && s_gpio_pin >= 0) {
|
} else if (!s_is_rgb && s_gpio_pin >= 0) {
|
||||||
// Simple LED: Any color > 0 is treated as ON
|
gpio_set_level(s_gpio_pin, (r+g+b) > 0);
|
||||||
bool on = (r + g + b) > 0;
|
|
||||||
gpio_set_level(s_gpio_pin, on ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void led_task(void *arg) {
|
static void led_task(void *arg) {
|
||||||
int blink_toggle = 0;
|
int toggle = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
switch (s_current_state) {
|
switch (s_current_state) {
|
||||||
case LED_STATE_NO_CONFIG:
|
case LED_STATE_NO_CONFIG: // Yellow
|
||||||
if (s_is_rgb) {
|
if (s_is_rgb) { set_color(25, 25, 0); vTaskDelay(pdMS_TO_TICKS(1000)); }
|
||||||
set_color(25, 25, 0); // Yellow
|
else { set_color(1,1,1); vTaskDelay(100); set_color(0,0,0); vTaskDelay(100); }
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
} else {
|
|
||||||
set_color(255, 255, 255); vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
set_color(0, 0, 0); vTaskDelay(pdMS_TO_TICKS(100));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_WAITING: // Blue Blink
|
||||||
case LED_STATE_WAITING:
|
set_color(0, 0, toggle ? 50 : 0); toggle = !toggle;
|
||||||
// Blue Blink (Slow)
|
|
||||||
if (blink_toggle) set_color(0, 0, 50);
|
|
||||||
else set_color(0, 0, 0);
|
|
||||||
blink_toggle = !blink_toggle;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_CONNECTED: // Green Solid
|
||||||
case LED_STATE_CONNECTED:
|
set_color(0, 25, 0); vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
// Green Solid
|
|
||||||
set_color(0, 25, 0);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_MONITORING: // Blue Solid
|
||||||
case LED_STATE_MONITORING:
|
set_color(0, 0, 50); vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
// Blue Solid
|
|
||||||
set_color(0, 0, 50);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_TRANSMITTING: // Fast Purple (Busy)
|
||||||
case LED_STATE_TRANSMITTING:
|
set_color(toggle ? 50 : 0, 0, toggle ? 50 : 0); toggle = !toggle;
|
||||||
// Behavior: Purple Flash (Fast) - "Sending"
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
if (blink_toggle) set_color(50, 0, 50); // Purple
|
|
||||||
else set_color(0, 0, 0); // Off
|
|
||||||
blink_toggle = !blink_toggle;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(100)); // Fast toggle
|
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_TRANSMITTING_SLOW: // Slow Purple (Relaxed)
|
||||||
case LED_STATE_STALLED:
|
set_color(toggle ? 50 : 0, 0, toggle ? 50 : 0); toggle = !toggle;
|
||||||
// Behavior: Purple Solid - "Buffered/Error 12"
|
vTaskDelay(pdMS_TO_TICKS(250));
|
||||||
set_color(50, 0, 50);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
||||||
break;
|
break;
|
||||||
|
case LED_STATE_STALLED: // Purple Solid
|
||||||
case LED_STATE_FAILED:
|
set_color(50, 0, 50); vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
// Red Blink (Fast)
|
break;
|
||||||
if (blink_toggle) set_color(50, 0, 0);
|
case LED_STATE_FAILED: // Red Blink
|
||||||
else set_color(0, 0, 0);
|
set_color(toggle ? 50 : 0, 0, 0); toggle = !toggle;
|
||||||
blink_toggle = !blink_toggle;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(200));
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -87,27 +59,15 @@ static void led_task(void *arg) {
|
||||||
void status_led_init(int gpio_pin, bool is_rgb_strip) {
|
void status_led_init(int gpio_pin, bool is_rgb_strip) {
|
||||||
s_gpio_pin = gpio_pin;
|
s_gpio_pin = gpio_pin;
|
||||||
s_is_rgb = is_rgb_strip;
|
s_is_rgb = is_rgb_strip;
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Initializing LED on GPIO %d (Type: %s)",
|
|
||||||
gpio_pin, is_rgb_strip ? "RGB Strip" : "Simple GPIO");
|
|
||||||
|
|
||||||
if (s_is_rgb) {
|
if (s_is_rgb) {
|
||||||
led_strip_config_t strip_config = {
|
led_strip_config_t s_cfg = { .strip_gpio_num = gpio_pin, .max_leds = 1 };
|
||||||
.strip_gpio_num = gpio_pin,
|
led_strip_rmt_config_t r_cfg = { .resolution_hz = 10 * 1000 * 1000 };
|
||||||
.max_leds = 1,
|
led_strip_new_rmt_device(&s_cfg, &r_cfg, &s_led_strip);
|
||||||
};
|
|
||||||
led_strip_rmt_config_t rmt_config = {
|
|
||||||
.resolution_hz = 10 * 1000 * 1000,
|
|
||||||
.flags.with_dma = false,
|
|
||||||
};
|
|
||||||
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &s_led_strip));
|
|
||||||
led_strip_clear(s_led_strip);
|
led_strip_clear(s_led_strip);
|
||||||
} else {
|
} else {
|
||||||
gpio_reset_pin(gpio_pin);
|
gpio_reset_pin(gpio_pin);
|
||||||
gpio_set_direction(gpio_pin, GPIO_MODE_OUTPUT);
|
gpio_set_direction(gpio_pin, GPIO_MODE_OUTPUT);
|
||||||
gpio_set_level(gpio_pin, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xTaskCreate(led_task, "led_task", 2048, NULL, 5, NULL);
|
xTaskCreate(led_task, "led_task", 2048, NULL, 5, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,16 @@ typedef enum {
|
||||||
LED_STATE_WAITING,
|
LED_STATE_WAITING,
|
||||||
LED_STATE_CONNECTED,
|
LED_STATE_CONNECTED,
|
||||||
LED_STATE_MONITORING,
|
LED_STATE_MONITORING,
|
||||||
LED_STATE_TRANSMITTING, // Flashing Purple (Active)
|
LED_STATE_TRANSMITTING, // Busy / Fast Flash (Healthy)
|
||||||
LED_STATE_STALLED, // Solid Purple (Non-Fatal Error/Buffering)
|
LED_STATE_TRANSMITTING_SLOW, // Slow Pulse (Falling behind)
|
||||||
|
LED_STATE_STALLED, // Solid Purple (Blocked)
|
||||||
LED_STATE_FAILED
|
LED_STATE_FAILED
|
||||||
} led_state_t;
|
} led_state_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize the status LED driver
|
* @brief Initialize the status LED driver
|
||||||
* Supports both Addressable RGB (WS2812) and Simple GPIO LEDs.
|
* Supports both Addressable RGB (WS2812) and Simple GPIO LEDs.
|
||||||
* * @param gpio_pin The GPIO pin number
|
* @param gpio_pin The GPIO pin number
|
||||||
* @param is_rgb_strip Set true for NeoPixel/WS2812, false for simple ON/OFF LED
|
* @param is_rgb_strip Set true for NeoPixel/WS2812, false for simple ON/OFF LED
|
||||||
*/
|
*/
|
||||||
void status_led_init(int gpio_pin, bool is_rgb_strip);
|
void status_led_init(int gpio_pin, bool is_rgb_strip);
|
||||||
|
|
|
||||||
214
control_iperf.py
214
control_iperf.py
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import asyncio
|
import asyncio
|
||||||
import argparse
|
import argparse
|
||||||
|
|
@ -7,89 +6,68 @@ import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class SerialController(asyncio.Protocol):
|
class SerialController(asyncio.Protocol):
|
||||||
def __init__(self, port_name, command, loop, completion_future):
|
def __init__(self, port_name, args, loop, completion_future):
|
||||||
self.port_name = port_name
|
self.port_name = port_name
|
||||||
self.command = command
|
self.args = args
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.transport = None
|
self.transport = None
|
||||||
self.response_buffer = ""
|
self.buffer = ""
|
||||||
self.completion_future = completion_future
|
self.completion_future = completion_future
|
||||||
self.target_keyword = "IPERF_STARTED" if "start" in command else "IPERF_STOPPED"
|
|
||||||
|
# Determine command string
|
||||||
|
if args.action == 'pps':
|
||||||
|
self.cmd_str = f"iperf pps {args.value}\n"
|
||||||
|
self.target_key = "IPERF_PPS_UPDATED"
|
||||||
|
elif args.action == 'status':
|
||||||
|
self.cmd_str = "iperf status\n"
|
||||||
|
self.target_key = "IPERF_STATUS"
|
||||||
|
else:
|
||||||
|
self.cmd_str = f"iperf {args.action}\n"
|
||||||
|
self.target_key = f"IPERF_{args.action.upper()}ED" # STARTED / STOPPED
|
||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
# 1. Clear line noise
|
transport.write(b'\n') # Clear noise
|
||||||
transport.write(b'\n')
|
|
||||||
# 2. Schedule command
|
|
||||||
self.loop.create_task(self.send_command())
|
self.loop.create_task(self.send_command())
|
||||||
|
|
||||||
async def send_command(self):
|
async def send_command(self):
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
# SPACE separated subcommand: "iperf start" or "iperf stop"
|
self.transport.write(self.cmd_str.encode())
|
||||||
full_cmd = f"iperf {self.command}\n"
|
|
||||||
self.transport.write(full_cmd.encode())
|
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
text = data.decode(errors='ignore')
|
self.buffer += data.decode(errors='ignore')
|
||||||
self.response_buffer += text
|
|
||||||
|
|
||||||
# Check for confirmation keyword
|
if self.target_key in self.buffer:
|
||||||
if self.target_keyword in self.response_buffer:
|
|
||||||
if not self.completion_future.done():
|
if not self.completion_future.done():
|
||||||
self.completion_future.set_result(True)
|
if self.args.action == 'status':
|
||||||
|
m = re.search(r'PPS=(\d+)', self.buffer)
|
||||||
|
val = m.group(1) if m else "Unknown"
|
||||||
|
self.completion_future.set_result(val)
|
||||||
|
else:
|
||||||
|
self.completion_future.set_result(True)
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
def connection_lost(self, exc):
|
||||||
if not self.completion_future.done():
|
if not self.completion_future.done():
|
||||||
# If we closed it intentionally (set_result called), this is fine.
|
self.completion_future.set_exception(Exception("Closed"))
|
||||||
# If it closed unexpectedly, set exception.
|
|
||||||
self.completion_future.set_exception(exc if exc else Exception("Connection closed without confirmation"))
|
|
||||||
|
|
||||||
async def run_single_device(port, action):
|
async def run_device(port, args):
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
completion_future = loop.create_future()
|
fut = loop.create_future()
|
||||||
|
|
||||||
transport = None
|
|
||||||
try:
|
try:
|
||||||
transport, protocol = await serial_asyncio.create_serial_connection(
|
await serial_asyncio.create_serial_connection(
|
||||||
loop,
|
loop, lambda: SerialController(port, args, loop, fut), port, baudrate=115200)
|
||||||
lambda: SerialController(port, action, loop, completion_future),
|
return await asyncio.wait_for(fut, timeout=2.0)
|
||||||
port,
|
except:
|
||||||
baudrate=115200
|
return None
|
||||||
)
|
|
||||||
|
|
||||||
# Wait for success or timeout
|
|
||||||
await asyncio.wait_for(completion_future, timeout=5.0)
|
|
||||||
print(f"[{port}] {action.upper()} SUCCESS")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
print(f"[{port}] TIMEOUT (No confirmation received)")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[{port}] FAILED: {e}")
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
if transport and not transport.is_closing():
|
|
||||||
transport.close()
|
|
||||||
|
|
||||||
def expand_devices(device_str):
|
def expand_devices(device_str):
|
||||||
"""
|
|
||||||
Expands device strings like:
|
|
||||||
- "/dev/ttyUSB0, /dev/ttyUSB1" -> ['/dev/ttyUSB0', '/dev/ttyUSB1']
|
|
||||||
- "/dev/ttyUSB0-5" -> ['/dev/ttyUSB0', ... '/dev/ttyUSB5']
|
|
||||||
"""
|
|
||||||
devices = []
|
devices = []
|
||||||
parts = [d.strip() for d in device_str.split(',')]
|
parts = [d.strip() for d in device_str.split(',')]
|
||||||
|
|
||||||
for part in parts:
|
for part in parts:
|
||||||
# Check for range syntax (e.g. /dev/ttyUSB0-29)
|
|
||||||
# Matches "prefix" + "start_num" + "-" + "end_num"
|
|
||||||
match = re.match(r'^(.*?)(\d+)-(\d+)$', part)
|
match = re.match(r'^(.*?)(\d+)-(\d+)$', part)
|
||||||
if match:
|
if match:
|
||||||
prefix = match.group(1)
|
prefix, start, end = match.group(1), int(match.group(2)), int(match.group(3))
|
||||||
start = int(match.group(2))
|
|
||||||
end = int(match.group(3))
|
|
||||||
step = 1 if end >= start else -1
|
step = 1 if end >= start else -1
|
||||||
for i in range(start, end + step, step):
|
for i in range(start, end + step, step):
|
||||||
devices.append(f"{prefix}{i}")
|
devices.append(f"{prefix}{i}")
|
||||||
|
|
@ -98,119 +76,31 @@ def expand_devices(device_str):
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
parser = argparse.ArgumentParser(description='Control ESP32 iperf concurrently')
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('action', choices=['start', 'stop'], help='Action to perform')
|
parser.add_argument('action', choices=['start', 'stop', 'pps', 'status'])
|
||||||
parser.add_argument('--devices', required=True,
|
parser.add_argument('--value', type=int, help='Value for PPS command')
|
||||||
help='Device list (e.g., "/dev/ttyUSB0-29" or "/dev/ttyUSB0,/dev/ttyUSB1")')
|
parser.add_argument('--devices', required=True, help="/dev/ttyUSB0-29")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if args.action == 'pps' and not args.value:
|
||||||
|
print("Error: 'pps' action requires --value")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32': asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
||||||
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
||||||
|
|
||||||
# 1. Expand device list
|
devs = expand_devices(args.devices)
|
||||||
device_list = expand_devices(args.devices)
|
print(f"Executing '{args.action}' on {len(devs)} devices...")
|
||||||
print(f"Targeting {len(device_list)} devices for '{args.action.upper()}'...")
|
|
||||||
|
|
||||||
# 2. Create tasks for all devices
|
tasks = [run_device(d, args) for d in devs]
|
||||||
tasks = [run_single_device(dev, args.action) for dev in device_list]
|
|
||||||
|
|
||||||
# 3. Run all concurrently
|
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
# 4. Summary
|
print("\nResults:")
|
||||||
success_count = results.count(True)
|
for dev, res in zip(devs, results):
|
||||||
print(f"\nSummary: {success_count}/{len(device_list)} Succeeded")
|
if args.action == 'status':
|
||||||
|
print(f"{dev}: {res if res else 'TIMEOUT'} PPS")
|
||||||
|
else:
|
||||||
|
status = "OK" if res is True else "FAIL"
|
||||||
|
print(f"{dev}: {status}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
asyncio.run(main())
|
||||||
asyncio.run(main())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
import asyncio
|
|
||||||
import argparse
|
|
||||||
import serial_asyncio
|
|
||||||
import sys
|
|
||||||
|
|
||||||
class SerialController(asyncio.Protocol):
|
|
||||||
def __init__(self, command, loop):
|
|
||||||
self.command = command
|
|
||||||
self.loop = loop
|
|
||||||
self.transport = None
|
|
||||||
self.response_buffer = ""
|
|
||||||
# Keywords the firmware will print to confirm action
|
|
||||||
self.target_keyword = "IPERF_STARTED" if "start" in command else "IPERF_STOPPED"
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
self.transport = transport
|
|
||||||
print(f"Connected. Sending: iperf {self.command}")
|
|
||||||
|
|
||||||
# 1. Clear line noise
|
|
||||||
transport.write(b'\n')
|
|
||||||
|
|
||||||
# 2. Schedule command
|
|
||||||
self.loop.create_task(self.send_command())
|
|
||||||
|
|
||||||
async def send_command(self):
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
# SPACE separated subcommand: "iperf start" or "iperf stop"
|
|
||||||
full_cmd = f"iperf {self.command}\n"
|
|
||||||
self.transport.write(full_cmd.encode())
|
|
||||||
|
|
||||||
def data_received(self, data):
|
|
||||||
text = data.decode(errors='ignore')
|
|
||||||
sys.stdout.write(text) # Echo firmware output to console
|
|
||||||
self.response_buffer += text
|
|
||||||
|
|
||||||
# Check for confirmation keyword
|
|
||||||
if self.target_keyword in self.response_buffer:
|
|
||||||
print(f"\n[SUCCESS] Confirmed: {self.target_keyword}")
|
|
||||||
self.transport.close()
|
|
||||||
self.loop.stop()
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
if exc:
|
|
||||||
print(f"Serial connection lost: {exc}")
|
|
||||||
self.loop.stop()
|
|
||||||
|
|
||||||
async def run_control(port, action):
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
|
|
||||||
try:
|
|
||||||
transport, protocol = await serial_asyncio.create_serial_connection(
|
|
||||||
loop,
|
|
||||||
lambda: SerialController(action, loop),
|
|
||||||
port,
|
|
||||||
baudrate=115200
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to open port {port}: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Safety timeout: 5 seconds
|
|
||||||
try:
|
|
||||||
await asyncio.wait_for(loop.create_future(), timeout=5.0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
print("\n[TIMEOUT] Firmware did not respond with confirmation keyword.")
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if transport and not transport.is_closing():
|
|
||||||
transport.close()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = argparse.ArgumentParser(description='Control ESP32 iperf via USB')
|
|
||||||
parser.add_argument('port', help='Serial port (e.g., /dev/ttyUSB0)')
|
|
||||||
parser.add_argument('action', choices=['start', 'stop'], help='Action to perform')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
asyncio.set_event_loop(asyncio.ProactorEventLoop())
|
|
||||||
|
|
||||||
try:
|
|
||||||
asyncio.run(run_control(args.port, args.action))
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue