Compare commits
3 Commits
8acd9ba9c0
...
0b8c3ae03a
| Author | SHA1 | Date |
|---|---|---|
|
|
0b8c3ae03a | |
|
|
74eb6cb553 | |
|
|
2b7ef9cb19 |
40
README.md
40
README.md
|
|
@ -96,6 +96,11 @@ iperf start
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
iperf status
|
iperf status
|
||||||
|
|
||||||
|
# Wi‑Fi telemetry (monitor mode writes to fiwi-telemetry on SD; disconnects WiFi)
|
||||||
|
monitor start 6
|
||||||
|
sdcard status
|
||||||
|
sdcard read fiwi-telemetry
|
||||||
```
|
```
|
||||||
|
|
||||||
## Console Commands
|
## Console Commands
|
||||||
|
|
@ -131,11 +136,29 @@ iperf status
|
||||||
- `gps status` - Show GPS synchronization status
|
- `gps status` - Show GPS synchronization status
|
||||||
|
|
||||||
### Monitor Mode
|
### Monitor Mode
|
||||||
- `monitor start [channel]` - Start WiFi monitor mode
|
- `monitor start [channel]` - Start WiFi monitor mode (writes MCS telemetry to `fiwi-telemetry` on SD every 10s)
|
||||||
- `monitor stop` - Stop monitor mode
|
- `monitor stop` - Stop monitor mode
|
||||||
- `monitor channel <channel>` - Set monitor channel
|
- `monitor channel <channel>` - Set monitor channel
|
||||||
- `monitor status` - Show monitor status
|
- `monitor status` - Show monitor status
|
||||||
|
|
||||||
|
### SD Card Commands
|
||||||
|
Telemetry from monitor mode is stored in `fiwi-telemetry` (NDJSON, appended each flush). Use SD commands to inspect or transfer it.
|
||||||
|
|
||||||
|
- `sdcard status` - Show CD, mounted, capacity, and fiwi-telemetry file info
|
||||||
|
- `sdcard list [path]` - List files (path optional, default root)
|
||||||
|
- `sdcard read <file>` - Read and print file (e.g. `sdcard read fiwi-telemetry`)
|
||||||
|
- `sdcard write <file> <text>` - Write text to file
|
||||||
|
- `sdcard send <file>` - Stream file over serial (use `tools/sdcard_recv.py`)
|
||||||
|
- `sdcard delete <file>` - Delete a file
|
||||||
|
|
||||||
|
**Example (telemetry capture):**
|
||||||
|
```text
|
||||||
|
monitor start 6
|
||||||
|
sdcard status
|
||||||
|
sdcard list
|
||||||
|
sdcard read fiwi-telemetry
|
||||||
|
```
|
||||||
|
|
||||||
For detailed command documentation, see [Quick Reference](doc/QUICK_REFERENCE.md).
|
For detailed command documentation, see [Quick Reference](doc/QUICK_REFERENCE.md).
|
||||||
|
|
||||||
## Mass Deployment
|
## Mass Deployment
|
||||||
|
|
@ -192,15 +215,24 @@ The deployment script (`esp32_deploy.py`) can use these stable symlinks when `--
|
||||||
```
|
```
|
||||||
├── main/ # Main application code
|
├── main/ # Main application code
|
||||||
│ ├── main.c # Entry point and console initialization
|
│ ├── main.c # Entry point and console initialization
|
||||||
|
│ ├── broadcast_beacon.c/h # UDP broadcast for laptop discovery
|
||||||
│ └── board_config.h # Hardware pin definitions
|
│ └── board_config.h # Hardware pin definitions
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── app_console/ # Console command implementations
|
│ ├── app_console/ # Console command implementations (cmd_*)
|
||||||
│ ├── iperf/ # iPerf traffic generator core
|
│ ├── iperf/ # iPerf traffic generator core
|
||||||
│ ├── wifi_controller/ # WiFi management and monitor mode
|
│ ├── wifi_controller/ # WiFi management and monitor mode
|
||||||
|
│ ├── wifi_monitor/ # 802.11 frame capture and collapse detection
|
||||||
│ ├── wifi_cfg/ # WiFi and IP configuration storage
|
│ ├── wifi_cfg/ # WiFi and IP configuration storage
|
||||||
│ ├── gps_sync/ # GPS PPS and NMEA parsing
|
│ ├── gps_sync/ # GPS PPS and NMEA parsing
|
||||||
│ ├── status_led/ # LED status indication
|
│ ├── status_led/ # LED status indication
|
||||||
│ └── ...
|
│ ├── sd_card/ # SD card SPI mount and file I/O
|
||||||
|
│ ├── sdcard_http/ # HTTP server for SD file download (port 8080)
|
||||||
|
│ ├── mcs_telemetry/ # MCS/RSSI telemetry -> fiwi-telemetry
|
||||||
|
│ ├── csi_log/ # CSI logging (when CSI enabled)
|
||||||
|
│ └── csi_manager/ # CSI configuration (when CSI enabled)
|
||||||
|
├── tools/
|
||||||
|
│ ├── beacon_listen.py # Listen for beacons, download fiwi-telemetry
|
||||||
|
│ └── sdcard_recv.py # Receive file over serial from device
|
||||||
├── esp32_deploy.py # Mass deployment script
|
├── esp32_deploy.py # Mass deployment script
|
||||||
├── gen_udev_rules.py # USB port mapping utility
|
├── gen_udev_rules.py # USB port mapping utility
|
||||||
└── doc/ # Additional documentation
|
└── doc/ # Additional documentation
|
||||||
|
|
@ -210,6 +242,8 @@ The deployment script (`esp32_deploy.py`) can use these stable symlinks when `--
|
||||||
|
|
||||||
- [Quick Start Guide](doc/QUICK_START.md)
|
- [Quick Start Guide](doc/QUICK_START.md)
|
||||||
- [Quick Reference](doc/QUICK_REFERENCE.md)
|
- [Quick Reference](doc/QUICK_REFERENCE.md)
|
||||||
|
- [SD Card Wiring](doc/SD_CARD_WIRING.md) - Hardware, file transfer, beacon discovery
|
||||||
|
- [Telemetry Capture](doc/TELEMETRY_CAPTURE.md) - Enable and capture fiwi-telemetry
|
||||||
- [Deployment Guide](doc/DEPLOYMENT_GUIDE.md)
|
- [Deployment Guide](doc/DEPLOYMENT_GUIDE.md)
|
||||||
- [Mass Deployment](doc/MASS_DEPLOY.md)
|
- [Mass Deployment](doc/MASS_DEPLOY.md)
|
||||||
- [GDB Debugging Guide (ESP32-C5)](doc/ESP32-C5_GDB_Debugging_Guide.md)
|
- [GDB Debugging Guide (ESP32-C5)](doc/ESP32-C5_GDB_Debugging_Guide.md)
|
||||||
|
|
|
||||||
|
|
@ -302,30 +302,40 @@ esp_err_t mcs_telemetry_to_json(char *json_buffer, size_t buffer_len, const char
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MCS_JSON_TAIL_RESERVE 4 /* room for "]\0" and safety */
|
||||||
|
if (buffer_len <= MCS_JSON_TAIL_RESERVE) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t now_ms = esp_timer_get_time() / 1000;
|
uint32_t now_ms = esp_timer_get_time() / 1000;
|
||||||
int written = snprintf(json_buffer, buffer_len,
|
int written = snprintf(json_buffer, buffer_len,
|
||||||
"{\"device_id\":\"%s\",\"timestamp\":%lu,\"total_frames\":%lu,\"devices\":[",
|
"{\"device_id\":\"%s\",\"timestamp\":%lu,\"total_frames\":%lu,\"devices\":[",
|
||||||
device_id ? device_id : "unknown", now_ms, s_stats.total_frames_captured);
|
device_id ? device_id : "unknown", now_ms, s_stats.total_frames_captured);
|
||||||
|
|
||||||
if (written < 0 || written >= buffer_len) {
|
if (written < 0 || (size_t)written >= buffer_len) {
|
||||||
return ESP_ERR_NO_MEM;
|
return ESP_ERR_NO_MEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = written;
|
size_t offset = (size_t)written;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
for (int i = 0; i < MCS_TELEMETRY_MAX_DEVICES && offset < buffer_len - 100; i++) {
|
for (int i = 0; i < MCS_TELEMETRY_MAX_DEVICES; i++) {
|
||||||
mcs_device_telemetry_t *dev = &s_stats.devices[i];
|
mcs_device_telemetry_t *dev = &s_stats.devices[i];
|
||||||
if (dev->sample_count == 0) continue;
|
if (dev->sample_count == 0) continue;
|
||||||
|
|
||||||
|
size_t space = buffer_len - offset - MCS_JSON_TAIL_RESERVE;
|
||||||
|
if (space < 2) break;
|
||||||
|
|
||||||
if (!first) {
|
if (!first) {
|
||||||
written = snprintf(json_buffer + offset, buffer_len - offset, ",");
|
written = snprintf(json_buffer + offset, space, ",");
|
||||||
if (written < 0) break;
|
if (written < 0) break;
|
||||||
offset += written;
|
offset += (size_t)((written < (int)space) ? written : (space - 1));
|
||||||
|
space = buffer_len - offset - MCS_JSON_TAIL_RESERVE;
|
||||||
|
if (space < 2) break;
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
written = snprintf(json_buffer + offset, buffer_len - offset,
|
written = snprintf(json_buffer + offset, space,
|
||||||
"{\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\","
|
"{\"mac\":\"%02x:%02x:%02x:%02x:%02x:%02x\","
|
||||||
"\"mcs\":%u,\"ss\":%u,\"rssi\":%d,"
|
"\"mcs\":%u,\"ss\":%u,\"rssi\":%d,"
|
||||||
"\"channel\":%u,\"bandwidth\":%u,"
|
"\"channel\":%u,\"bandwidth\":%u,"
|
||||||
|
|
@ -338,15 +348,22 @@ esp_err_t mcs_telemetry_to_json(char *json_buffer, size_t buffer_len, const char
|
||||||
dev->avg_phy_rate_kbps);
|
dev->avg_phy_rate_kbps);
|
||||||
|
|
||||||
if (written < 0) break;
|
if (written < 0) break;
|
||||||
offset += written;
|
offset += (size_t)((written < (int)space) ? written : (space - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
written = snprintf(json_buffer + offset, buffer_len - offset, "]}");
|
{
|
||||||
if (written < 0) {
|
size_t tail_space = buffer_len - offset;
|
||||||
|
if (tail_space < 3) {
|
||||||
return ESP_ERR_NO_MEM;
|
return ESP_ERR_NO_MEM;
|
||||||
}
|
}
|
||||||
|
written = snprintf(json_buffer + offset, tail_space, "]}");
|
||||||
|
if (written < 0 || (size_t)written >= tail_space) {
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
#undef MCS_JSON_TAIL_RESERVE
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t mcs_calculate_phy_rate_ax(uint8_t mcs, uint8_t ss, mcs_bandwidth_t bandwidth, bool sgi) {
|
uint32_t mcs_calculate_phy_rate_ax(uint8_t mcs, uint8_t ss, mcs_bandwidth_t bandwidth, bool sgi) {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/unistd.h>
|
#include <sys/unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
// Pin definitions for SparkFun microSD Transflash Breakout
|
// Pin definitions for SparkFun microSD Transflash Breakout
|
||||||
|
|
@ -255,22 +256,27 @@ esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len,
|
||||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||||
(filename[0] == '/') ? "" : "/", filename);
|
(filename[0] == '/') ? "" : "/", filename);
|
||||||
|
|
||||||
const char *mode = append ? "a" : "w";
|
int flags = O_WRONLY | O_CREAT;
|
||||||
FILE *f = fopen(full_path, mode);
|
if (append) {
|
||||||
if (f == NULL) {
|
flags |= O_APPEND;
|
||||||
|
} else {
|
||||||
|
flags |= O_TRUNC;
|
||||||
|
}
|
||||||
|
int fd = open(full_path, flags, 0644);
|
||||||
|
if (fd < 0) {
|
||||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", full_path);
|
ESP_LOGE(TAG, "Failed to open file for writing: %s", full_path);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t written = fwrite(data, 1, len, f);
|
ssize_t written = write(fd, data, len);
|
||||||
fclose(f);
|
close(fd);
|
||||||
|
|
||||||
if (written != len) {
|
if (written < 0 || (size_t)written != len) {
|
||||||
ESP_LOGE(TAG, "Failed to write all data: wrote %zu of %zu bytes", written, len);
|
ESP_LOGE(TAG, "Failed to write all data: wrote %zd of %zu bytes", (ssize_t)written, len);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Wrote %zu bytes to %s", written, full_path);
|
ESP_LOGD(TAG, "Wrote %zu bytes to %s", (size_t)written, full_path);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ 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;
|
||||||
|
static volatile bool s_capture_active = false;
|
||||||
|
|
||||||
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) {
|
||||||
|
|
@ -69,8 +70,13 @@ static void led_task(void *arg) {
|
||||||
case LED_STATE_CONNECTED:
|
case LED_STATE_CONNECTED:
|
||||||
set_color(0, 25, 0); vTaskDelay(pdMS_TO_TICKS(1000));
|
set_color(0, 25, 0); vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
break;
|
break;
|
||||||
case LED_STATE_MONITORING:
|
case LED_STATE_MONITORING: /* Blink blue only when frames being captured */
|
||||||
set_color(0, 0, 50); vTaskDelay(pdMS_TO_TICKS(1000));
|
if (s_capture_active) {
|
||||||
|
set_color(0, 0, toggle ? 50 : 0); toggle = !toggle;
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(300));
|
||||||
|
} else {
|
||||||
|
set_color(0, 0, 10); vTaskDelay(pdMS_TO_TICKS(1000)); /* Dim solid: monitor on, no capture */
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LED_STATE_TRANSMITTING:
|
case LED_STATE_TRANSMITTING:
|
||||||
set_color(toggle ? 50 : 0, 0, toggle ? 50 : 0); toggle = !toggle;
|
set_color(toggle ? 50 : 0, 0, toggle ? 50 : 0); toggle = !toggle;
|
||||||
|
|
@ -116,6 +122,6 @@ void status_led_init(int gpio_pin, bool is_rgb_strip) {
|
||||||
xTaskCreate(led_task, "led_task", 2048, NULL, 5, NULL);
|
xTaskCreate(led_task, "led_task", 2048, NULL, 5, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... Setters/Getters ...
|
|
||||||
void status_led_set_state(led_state_t state) { s_current_state = state; }
|
void status_led_set_state(led_state_t state) { s_current_state = state; }
|
||||||
led_state_t status_led_get_state(void) { return s_current_state; }
|
led_state_t status_led_get_state(void) { return s_current_state; }
|
||||||
|
void status_led_set_capture_active(bool active) { s_capture_active = active; }
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,12 @@ void status_led_set_state(led_state_t state);
|
||||||
*/
|
*/
|
||||||
led_state_t status_led_get_state(void);
|
led_state_t status_led_get_state(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set capture-active flag (frames being captured in monitor mode)
|
||||||
|
* Used with LED_STATE_MONITORING: blink blue only when capture_active is true.
|
||||||
|
*/
|
||||||
|
void status_led_set_capture_active(bool active);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t
|
||||||
(void)payload;
|
(void)payload;
|
||||||
(void)len;
|
(void)len;
|
||||||
s_monitor_frame_count++;
|
s_monitor_frame_count++;
|
||||||
|
status_led_set_capture_active(true);
|
||||||
if (frame->retry && frame->duration_id > 5000) {
|
if (frame->retry && frame->duration_id > 5000) {
|
||||||
log_collapse_event((float)frame->duration_id, frame->rssi, frame->retry);
|
log_collapse_event((float)frame->duration_id, frame->rssi, frame->retry);
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +103,13 @@ static void monitor_stats_task(void *arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
static char json_buf[FIWI_TELEMETRY_JSON_BUF_SIZE];
|
static char json_buf[FIWI_TELEMETRY_JSON_BUF_SIZE];
|
||||||
uint32_t flush_count = 0;
|
uint32_t flush_count = 0;
|
||||||
|
uint32_t last_frame_count = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||||
|
if (s_monitor_frame_count == last_frame_count) {
|
||||||
|
status_led_set_capture_active(false);
|
||||||
|
}
|
||||||
|
last_frame_count = s_monitor_frame_count;
|
||||||
wifi_collapse_stats_t stats;
|
wifi_collapse_stats_t stats;
|
||||||
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
||||||
ESP_LOGI("MONITOR", "--- Stats: %lu frames, Retry: %.2f%%, Avg NAV: %u us ---",
|
ESP_LOGI("MONITOR", "--- Stats: %lu frames, Retry: %.2f%%, Avg NAV: %u us ---",
|
||||||
|
|
@ -113,13 +119,16 @@ static void monitor_stats_task(void *arg) {
|
||||||
/* Write MCS telemetry to fiwi-telemetry on SD card (default on monitor start) */
|
/* Write MCS telemetry to fiwi-telemetry on SD card (default on monitor start) */
|
||||||
if (sd_card_is_ready() && mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == ESP_OK) {
|
if (sd_card_is_ready() && mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == ESP_OK) {
|
||||||
size_t len = strlen(json_buf);
|
size_t len = strlen(json_buf);
|
||||||
if (len > 0 && sd_card_write_file(FIWI_TELEMETRY_FILE, json_buf, len, false) == ESP_OK) {
|
if (len > 0) {
|
||||||
|
json_buf[len] = '\n'; /* NDJSON: one object per line */
|
||||||
|
if (sd_card_write_file(FIWI_TELEMETRY_FILE, json_buf, len + 1, true) == ESP_OK) {
|
||||||
flush_count++;
|
flush_count++;
|
||||||
ESP_LOGD(TAG, "fiwi-telemetry flushed (#%lu)", (unsigned long)flush_count);
|
ESP_LOGD(TAG, "fiwi-telemetry flushed (#%lu)", (unsigned long)flush_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Helper to apply IP settings ---
|
// --- Helper to apply IP settings ---
|
||||||
static void apply_ip_settings(void) {
|
static void apply_ip_settings(void) {
|
||||||
|
|
@ -212,6 +221,7 @@ esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
|
||||||
esp_wifi_set_mode(WIFI_MODE_NULL);
|
esp_wifi_set_mode(WIFI_MODE_NULL);
|
||||||
|
status_led_set_capture_active(false);
|
||||||
if (wifi_monitor_init(channel, monitor_frame_callback) != ESP_OK) {
|
if (wifi_monitor_init(channel, monitor_frame_callback) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to init monitor mode");
|
ESP_LOGE(TAG, "Failed to init monitor mode");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
|
|
@ -257,6 +267,7 @@ esp_err_t wifi_ctl_switch_to_sta(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_monitor_enabled) {
|
if (s_monitor_enabled) {
|
||||||
|
status_led_set_capture_active(false);
|
||||||
mcs_telemetry_stop();
|
mcs_telemetry_stop();
|
||||||
wifi_monitor_stop();
|
wifi_monitor_stop();
|
||||||
s_monitor_enabled = false;
|
s_monitor_enabled = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue