build works

This commit is contained in:
Bob 2025-11-11 13:23:09 -08:00
parent a623e4e1e6
commit c03679358e
9 changed files with 115 additions and 893 deletions

View File

@ -1,6 +1,8 @@
# The following lines of boilerplate have to be in your project's # The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly # CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32-iperf) # "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(hello_world)

142
README.md
View File

@ -1,130 +1,60 @@
# ESP32 iperf # esp32-iperf (ESP-IDF 6.x)
Network performance testing tool for ESP32 based on iperf2. Minimal ESP32/ESP32-S3 iPerf firmware with **station mode** and optional **static IP**.
Tested on **ESP-IDF 6.0** (dev snapshot).
## Features ## Repo layout
- TCP and UDP client/server modes | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux |
- Bandwidth measurement | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
- Packet loss detection (UDP)
- Console-based control interface
- WiFi station mode support
## Hardware Requirements # Hello World Example
- ESP32, ESP32-S2, or ESP32-S3 development board Starts a FreeRTOS task to print "Hello World".
- WiFi network
## Software Requirements (See the README.md file in the upper level 'examples' directory for more information about examples.)
- ESP-IDF v5.0 or later ## How to use example
- iperf2 or iperf3 for testing with PC
## Building Follow detailed instructions provided specifically for this example.
1. Set up ESP-IDF environment: Select the instructions depending on Espressif chip installed on your development board:
```bash
. $HOME/Code/esp32/esp-idf/export.sh
```
2. Set the target chip (choose one): - [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html)
```bash - [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html)
# For ESP32
idf.py set-target esp32
# For ESP32-S2
idf.py set-target esp32s2
# For ESP32-S3 ## Example folder contents
idf.py set-target esp32s3
```
3. Configure WiFi credentials: The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main).
```bash
idf.py menuconfig
```
Navigate to "ESP32 iperf Configuration" and set your SSID and password.
4. Build the project: ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both).
```bash
idf.py build
```
5. Flash to your device: Below is short explanation of remaining files in the project folder.
```bash
idf.py -p /dev/ttyUSB0 flash monitor
```
## Usage
### TCP Server Mode
On ESP32:
```
iperf> iperf -s
```
On PC:
```bash
iperf -c <ESP32_IP>
```
### TCP Client Mode
On PC:
```bash
iperf -s
```
On ESP32:
```
iperf> iperf -c <PC_IP>
```
### UDP Mode
Add `-u` flag for UDP testing:
```
iperf> iperf -s -u
iperf> iperf -c <IP> -u
```
### Options
- `-s` : Run as server
- `-c <ip>` : Run as client, connecting to server IP
- `-u` : Use UDP instead of TCP
- `-p <port>` : Port number (default: 5001)
- `-t <time>` : Test duration in seconds (default: 30)
- `-b <bw>` : Bandwidth limit for UDP in Mbps
- `-a` : Abort running test
### Examples
```bash
# TCP client test for 10 seconds
iperf -c 192.168.1.100 -t 10
# UDP server on port 5002
iperf -s -u -p 5002
# Stop running test
iperf -a
```
## Project Structure
``` ```
esp32-iperf/
├── CMakeLists.txt ├── CMakeLists.txt
├── main/ ├── pytest_hello_world.py Python script used for automated testing
├── main
│ ├── CMakeLists.txt │ ├── CMakeLists.txt
│ ├── Kconfig.projbuild │ └── hello_world_main.c
│ ├── main.c # WiFi initialization and console └── README.md This is the file you are currently reading
│ ├── iperf.c # iperf implementation
│ └── iperf.h # iperf header
└── README.md
``` ```
## Author For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide.
Bob - Creator of iperf2 ## Troubleshooting
## License * Program upload failure
Open source * Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
## Technical support and feedback
Please use the following feedback channels:
* For technical queries, go to the [esp32.com](https://esp32.com/) forum
* For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues)
We will get back to you as soon as possible.

View File

@ -1,203 +1,72 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse, os, sys, time, fnmatch import os
import sys
import argparse
import serial
import time
from pathlib import Path from pathlib import Path
from typing import List, Dict
import serial, serial.tools.list_ports as list_ports def send_cfg(port, ssid, password, dhcp, start_ip, mask, gw):
import subprocess print(f"Connecting to {port}...")
with serial.Serial(port, baudrate=115200, timeout=2) as ser:
KNOWN_VIDS = {0x0403, 0x10C4, 0x1A86, 0x067B, 0x303A} time.sleep(0.5)
def run(cmd, cwd=None, check=True):
print(">>", " ".join(cmd))
p = subprocess.run(cmd, cwd=cwd)
if check and p.returncode != 0:
raise RuntimeError(f"Command failed ({p.returncode}): {' '.join(cmd)}")
return p.returncode
def detect_chip_type(port: str) -> str:
try:
out = subprocess.check_output(["esptool.py", "--port", port, "chip_id"], stderr=subprocess.STDOUT, text=True, timeout=6)
if "ESP32-S3" in out.upper(): return "ESP32-S3"
if "ESP32-S2" in out.upper(): return "ESP32-S2"
if "ESP32-C3" in out.upper(): return "ESP32-C3"
if "ESP32-C6" in out.upper(): return "ESP32-C6"
if "ESP32" in out.upper(): return "ESP32"
except Exception:
pass
return "Unknown"
def map_chip_to_idf_target(chip: str) -> str:
s = chip.upper()
if s.startswith("ESP32-S3"): return "esp32s3"
if s.startswith("ESP32-S2"): return "esp32s2"
if s.startswith("ESP32-C3"): return "esp32c3"
if s.startswith("ESP32-C6"): return "esp32c6"
if s.startswith("ESP32-H2"): return "esp32h2"
if s.startswith("ESP32"): return "esp32"
return "unknown"
def list_ports_filtered(patterns: List[str] = None):
ports = list_ports.comports()
out = []
for p in ports:
dev = p.device
if patterns:
if not any(fnmatch.fnmatch(dev, pat) for pat in patterns):
continue
else:
if not (dev.startswith("/dev/ttyUSB") or dev.startswith("/dev/ttyACM")):
continue
vid = getattr(p, "vid", None)
if vid is not None and vid not in KNOWN_VIDS:
continue
out.append(p)
return out
def ensure_target(project_dir: str, target: str):
if not target or target == "unknown":
raise ValueError("Unknown IDF target; cannot set-target")
run(["idf.py", "set-target", target], cwd=project_dir, check=True)
def flash_device(project_dir: str, port: str, idf_target: str, baud: int = 460800) -> bool:
try:
ensure_target(project_dir, idf_target)
run(["idf.py", "-p", port, "-b", str(baud), "flash"], cwd=project_dir, check=True)
return True
except Exception as e:
print(f" Flash failed on {port}: {e}")
return False
def toggle_reset(ser):
try:
ser.dtr = False
ser.rts = True
time.sleep(0.05)
ser.dtr = True
ser.rts = False
time.sleep(0.05)
except Exception:
pass
def send_wifi_config(port: str, ssid: str, password: str, ip: str, mask: str, gw: str, dhcp: bool, baud: int = 115200, retries: int = 3) -> bool:
for attempt in range(1, retries+1):
try:
print(f" [{port}] opening serial @ {baud} (attempt {attempt}/{retries})")
with serial.Serial(port, baudrate=baud, timeout=2) as ser:
time.sleep(0.3)
toggle_reset(ser)
time.sleep(0.8)
ser.reset_input_buffer() ser.reset_input_buffer()
ser.reset_output_buffer() ser.write(b"CFG\n")
ser.write(f"SSID:{ssid}\n".encode())
ser.write(f"PASS:{password}\n".encode())
if dhcp == 0:
ser.write(f"IP:{start_ip}\n".encode())
ser.write(f"MASK:{mask}\n".encode())
ser.write(f"GW:{gw}\n".encode())
ser.write(f"DHCP:{dhcp}\n".encode())
ser.write(b"END\n")
time.sleep(0.3)
lines = [ print("\nDevice response:")
"CFG\n", while ser.in_waiting:
f"SSID:{ssid}\n" if ssid else "", sys.stdout.write(ser.read(ser.in_waiting).decode(errors='ignore'))
f"PASS:{password}\n" if password else "", sys.stdout.flush()
f"IP:{ip}\n" if ip else "", time.sleep(0.1)
f"MASK:{mask}\n" if mask else "", print("\n✅ Configuration sent successfully.")
f"GW:{gw}\n" if gw else "",
f"DHCP:{1 if dhcp else 0}\n",
"END\n",
]
payload = "".join([l for l in lines if l])
ser.write(payload.encode("utf-8"))
ser.flush()
t0 = time.time()
buf = b""
while time.time() - t0 < 3.0:
chunk = ser.read(64)
if chunk:
buf += chunk
if b"OK" in buf:
print(f" [{port}] config applied: {buf.decode(errors='ignore').strip()}")
return True
print(f" [{port}] no OK from device, got: {buf.decode(errors='ignore').strip()}")
except Exception as e:
print(f" [{port}] serial error: {e}")
time.sleep(0.6)
return False
def main(): def main():
ap = argparse.ArgumentParser(description="Mass flash ESP32 devices and push WiFi/IP config over serial.") parser = argparse.ArgumentParser(description="Configure ESP32 Wi-Fi settings over serial")
ap.add_argument("--project", required=True, help="Path to ESPIDF project") parser.add_argument("--project", help="ESP-IDF project path (defaults to current working directory)")
ap.add_argument("--ssid", required=True, help="WiFi SSID") parser.add_argument("--ssid", required=True)
ap.add_argument("--password", required=True, help="WiFi password") parser.add_argument("--password", required=True)
ap.add_argument("--start-ip", default="192.168.1.50", help="Base IP (only used if --dhcp=0)") parser.add_argument("--start-ip", help="Static IP address")
ap.add_argument("--mask", default="255.255.255.0", help="Netmask for static IP") parser.add_argument("--mask", default="255.255.255.0")
ap.add_argument("--gw", default="192.168.1.1", help="Gateway for static IP") parser.add_argument("--gw", help="Gateway address")
ap.add_argument("--dhcp", type=int, choices=[0,1], default=1, help="1=use DHCP, 0=set static IPs") parser.add_argument("--dhcp", type=int, choices=[0,1], default=1)
ap.add_argument("--baud", type=int, default=460800, help="Flashing baud rate") parser.add_argument("--baud", type=int, default=460800)
ap.add_argument("--cfg-baud", type=int, default=115200, help="Serial baud for config exchange") parser.add_argument("--cfg-baud", type=int, default=115200)
ap.add_argument("--ports", help="Comma-separated globs to override port selection, e.g. '/dev/ttyUSB*,/dev/ttyACM*'") parser.add_argument("--ports", nargs='+', help="Serial port(s), e.g., /dev/ttyUSB0 /dev/ttyUSB1")
ap.add_argument("--dry-run", action="store_true", help="Plan only; do not flash or configure") parser.add_argument("--port", help="Single serial port (shorthand for --ports PORT)")
args = ap.parse_args() parser.add_argument("--dry-run", action="store_true")
project_dir = os.path.abspath(args.project) args = parser.parse_args()
if not os.path.isdir(project_dir):
print(f"Project directory not found: {project_dir}") # Default to current working directory
project_path = args.project or os.getcwd()
print(f"Using project directory: {project_path}")
# Resolve ports
ports = []
if args.port:
ports.append(args.port)
elif args.ports:
ports = args.ports
else:
print("❌ No serial port specified. Use --port or --ports.")
sys.exit(1) sys.exit(1)
patterns = [p.strip() for p in args.ports.split(",")] if args.ports else None # Apply configuration
devices = list_ports_filtered(patterns) for port in ports:
if not devices:
print("No candidate USB serial ports found.")
sys.exit(2)
print(f"Found {len(devices)} devices.")
base = args.start_ip.split(".")
try:
base = [int(x) for x in base]
except Exception:
base = [192,168,1,50]
plan = []
for idx, dev in enumerate(devices, 1):
port = dev.device
chip = detect_chip_type(port)
target = map_chip_to_idf_target(chip)
ip = f"{base[0]}.{base[1]}.{base[2]}.{base[3] + idx - 1}" if args.dhcp == 0 else None
plan.append(dict(idx=idx, port=port, chip=chip, target=target, ip=ip))
print("\nPlan:")
for d in plan:
print(f" {d['idx']:2d} {d['port']} {d['chip']} -> target {d['target']} IP:{d['ip'] or 'DHCP'}")
if args.dry_run: if args.dry_run:
print("\nDry run only.") print(f"[DRY RUN] Would send Wi-Fi config to {port}")
return else:
send_cfg(port, args.ssid, args.password, args.dhcp, args.start_ip, args.mask, args.gw)
failed = []
for d in plan:
if d['target'] == 'unknown':
print(f"\nERROR: Unknown IDF target for {d['port']} (chip '{d['chip']}'). Skipping.")
failed.append(d['idx'])
continue
print(f"\nFlashing {d['port']} as {d['target']}...")
if not flash_device(project_dir, d['port'], d['target'], baud=args.baud):
failed.append(d['idx'])
continue
print(f"Configuring WiFi on {d['port']}...")
ok = send_wifi_config(
d['port'],
args.ssid,
args.password,
d['ip'],
args.mask if d['ip'] else None,
args.gw if d['ip'] else None,
dhcp=(args.dhcp == 1),
baud=args.cfg_baud,
)
if not ok:
print(f" WARN: config not acknowledged on {d['port']}")
if failed:
print(f"\nCompleted with flashing failures on: {failed}")
sys.exit(3)
print("\nAll done.")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,8 +1,15 @@
idf_component_register( idf_component_register(
SRCS SRCS "main.c"
"main.c" INCLUDE_DIRS "."
"iperf.c" REQUIRES
"wifi_cfg.c" esp_wifi
INCLUDE_DIRS nvs_flash
"." esp_netif
console
lwip
driver
esp_driver_uart
vfs
iperf
wifi_cfg
) )

View File

@ -1,340 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_err.h"
#include "iperf.h"
static const char *TAG = "iperf";
typedef struct {
iperf_cfg_t cfg;
bool finish;
uint32_t total_len;
uint32_t buffer_len;
uint8_t *buffer;
uint32_t sockfd;
} iperf_ctrl_t;
static iperf_ctrl_t s_iperf_ctrl = {0};
static TaskHandle_t s_iperf_task_handle = NULL;
static void socket_send(int sockfd, const uint8_t *buffer, int len)
{
int actual_send = 0;
while (actual_send < len) {
int send_len = send(sockfd, buffer + actual_send, len - actual_send, 0);
if (send_len < 0) {
ESP_LOGE(TAG, "send failed: errno %d", errno);
break;
}
actual_send += send_len;
}
}
static int socket_recv(int sockfd, uint8_t *buffer, int len, TickType_t timeout_ticks)
{
struct timeval timeout;
timeout.tv_sec = timeout_ticks / configTICK_RATE_HZ;
timeout.tv_usec = (timeout_ticks % configTICK_RATE_HZ) * (1000000 / configTICK_RATE_HZ);
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
ESP_LOGE(TAG, "setsockopt failed: errno %d", errno);
return -1;
}
return recv(sockfd, buffer, len, 0);
}
static esp_err_t iperf_start_tcp_server(iperf_ctrl_t *ctrl)
{
struct sockaddr_in addr;
int listen_sock;
int opt = 1;
listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return ESP_FAIL;
}
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
addr.sin_family = AF_INET;
addr.sin_port = htons(ctrl->cfg.sport);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
ESP_LOGE(TAG, "Socket bind failed: errno %d", errno);
close(listen_sock);
return ESP_FAIL;
}
if (listen(listen_sock, 5) != 0) {
ESP_LOGE(TAG, "Socket listen failed: errno %d", errno);
close(listen_sock);
return ESP_FAIL;
}
ESP_LOGI(TAG, "TCP server listening on port %d", ctrl->cfg.sport);
while (!ctrl->finish) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len);
if (client_sock < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
ESP_LOGE(TAG, "Accept failed: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Client connected from %s", inet_ntoa(client_addr.sin_addr));
uint64_t total_len = 0;
int recv_len;
while (!ctrl->finish) {
recv_len = socket_recv(client_sock, ctrl->buffer, ctrl->buffer_len,
pdMS_TO_TICKS(IPERF_SOCKET_RX_TIMEOUT * 1000));
if (recv_len <= 0) {
break;
}
total_len += recv_len;
}
ESP_LOGI(TAG, "TCP server received: %" PRIu64 " bytes", total_len);
close(client_sock);
}
close(listen_sock);
return ESP_OK;
}
static esp_err_t iperf_start_tcp_client(iperf_ctrl_t *ctrl)
{
struct sockaddr_in addr;
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return ESP_FAIL;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(ctrl->cfg.dport);
addr.sin_addr.s_addr = htonl(ctrl->cfg.dip);
ESP_LOGI(TAG, "Connecting to TCP server...");
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
ESP_LOGE(TAG, "Socket connect failed: errno %d", errno);
close(sockfd);
return ESP_FAIL;
}
ESP_LOGI(TAG, "Connected to TCP server");
uint64_t total_len = 0;
uint32_t start_time = xTaskGetTickCount();
uint32_t end_time = start_time + pdMS_TO_TICKS(ctrl->cfg.time * 1000);
while (!ctrl->finish && xTaskGetTickCount() < end_time) {
socket_send(sockfd, ctrl->buffer, ctrl->buffer_len);
total_len += ctrl->buffer_len;
}
uint32_t actual_time = (xTaskGetTickCount() - start_time) / configTICK_RATE_HZ;
float bandwidth = (float)total_len * 8 / actual_time / 1000000; // Mbps
ESP_LOGI(TAG, "TCP client sent: %" PRIu64 " bytes in %" PRIu32 " seconds (%.2f Mbps)",
total_len, actual_time, bandwidth);
close(sockfd);
return ESP_OK;
}
static esp_err_t iperf_start_udp_server(iperf_ctrl_t *ctrl)
{
struct sockaddr_in addr;
int sockfd;
int opt = 1;
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return ESP_FAIL;
}
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
addr.sin_family = AF_INET;
addr.sin_port = htons(ctrl->cfg.sport);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
ESP_LOGE(TAG, "Socket bind failed: errno %d", errno);
close(sockfd);
return ESP_FAIL;
}
ESP_LOGI(TAG, "UDP server listening on port %d", ctrl->cfg.sport);
uint64_t total_len = 0;
uint32_t packet_count = 0;
int32_t last_id = -1;
uint32_t lost_packets = 0;
while (!ctrl->finish) {
int recv_len = socket_recv(sockfd, ctrl->buffer, ctrl->buffer_len,
pdMS_TO_TICKS(IPERF_SOCKET_RX_TIMEOUT * 1000));
if (recv_len <= 0) {
continue;
}
total_len += recv_len;
packet_count++;
// Check for lost packets using UDP header
if (recv_len >= sizeof(udp_datagram)) {
udp_datagram *header = (udp_datagram *)ctrl->buffer;
int32_t current_id = ntohl(header->id);
if (last_id >= 0 && current_id > last_id + 1) {
lost_packets += (current_id - last_id - 1);
}
last_id = current_id;
}
}
float loss_rate = packet_count > 0 ? (float)lost_packets * 100 / packet_count : 0;
ESP_LOGI(TAG, "UDP server received: %" PRIu64 " bytes, %" PRIu32 " packets, %" PRIu32 " lost (%.2f%%)",
total_len, packet_count, lost_packets, loss_rate);
close(sockfd);
return ESP_OK;
}
static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl)
{
struct sockaddr_in addr;
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
return ESP_FAIL;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(ctrl->cfg.dport);
addr.sin_addr.s_addr = htonl(ctrl->cfg.dip);
ESP_LOGI(TAG, "Starting UDP client");
uint64_t total_len = 0;
uint32_t packet_count = 0;
uint32_t start_time = xTaskGetTickCount();
uint32_t end_time = start_time + pdMS_TO_TICKS(ctrl->cfg.time * 1000);
while (!ctrl->finish && xTaskGetTickCount() < end_time) {
udp_datagram *header = (udp_datagram *)ctrl->buffer;
header->id = htonl(packet_count);
int send_len = sendto(sockfd, ctrl->buffer, ctrl->buffer_len, 0,
(struct sockaddr *)&addr, sizeof(addr));
if (send_len > 0) {
total_len += send_len;
packet_count++;
}
// Bandwidth limiting
if (ctrl->cfg.bw_lim > 0) {
vTaskDelay(1);
}
}
uint32_t actual_time = (xTaskGetTickCount() - start_time) / configTICK_RATE_HZ;
float bandwidth = actual_time > 0 ? (float)total_len * 8 / actual_time / 1000000 : 0;
ESP_LOGI(TAG, "UDP client sent: %" PRIu64 " bytes, %" PRIu32 " packets in %" PRIu32 " seconds (%.2f Mbps)",
total_len, packet_count, actual_time, bandwidth);
close(sockfd);
return ESP_OK;
}
static void iperf_task(void *arg)
{
iperf_ctrl_t *ctrl = (iperf_ctrl_t *)arg;
if (ctrl->cfg.flag & IPERF_FLAG_TCP) {
if (ctrl->cfg.flag & IPERF_FLAG_SERVER) {
iperf_start_tcp_server(ctrl);
} else {
iperf_start_tcp_client(ctrl);
}
} else {
if (ctrl->cfg.flag & IPERF_FLAG_SERVER) {
iperf_start_udp_server(ctrl);
} else {
iperf_start_udp_client(ctrl);
}
}
if (ctrl->buffer) {
free(ctrl->buffer);
ctrl->buffer = NULL;
}
ESP_LOGI(TAG, "iperf task finished");
s_iperf_task_handle = NULL;
vTaskDelete(NULL);
}
void iperf_start(iperf_cfg_t *cfg)
{
if (s_iperf_task_handle != NULL) {
ESP_LOGW(TAG, "iperf is already running");
return;
}
memcpy(&s_iperf_ctrl.cfg, cfg, sizeof(iperf_cfg_t));
s_iperf_ctrl.finish = false;
// Allocate buffer
if (cfg->flag & IPERF_FLAG_TCP) {
s_iperf_ctrl.buffer_len = cfg->flag & IPERF_FLAG_SERVER ?
IPERF_TCP_RX_LEN : IPERF_TCP_TX_LEN;
} else {
s_iperf_ctrl.buffer_len = cfg->flag & IPERF_FLAG_SERVER ?
IPERF_UDP_RX_LEN : IPERF_UDP_TX_LEN;
}
s_iperf_ctrl.buffer = (uint8_t *)malloc(s_iperf_ctrl.buffer_len);
if (!s_iperf_ctrl.buffer) {
ESP_LOGE(TAG, "Failed to allocate buffer");
return;
}
memset(s_iperf_ctrl.buffer, 0, s_iperf_ctrl.buffer_len);
xTaskCreate(iperf_task, "iperf", 4096, &s_iperf_ctrl, IPERF_TRAFFIC_TASK_PRIORITY,
&s_iperf_task_handle);
}
void iperf_stop(void)
{
if (s_iperf_task_handle != NULL) {
s_iperf_ctrl.finish = true;
ESP_LOGI(TAG, "Stopping iperf...");
}
}

View File

@ -1,59 +0,0 @@
#ifndef IPERF_H
#define IPERF_H
#include <stdint.h>
#include <stdbool.h>
#define IPERF_FLAG_CLIENT (1 << 0)
#define IPERF_FLAG_SERVER (1 << 1)
#define IPERF_FLAG_TCP (1 << 2)
#define IPERF_FLAG_UDP (1 << 3)
#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_SOCKET_RX_TIMEOUT 10
#define IPERF_SOCKET_ACCEPT_TIMEOUT 5
#define IPERF_UDP_TX_LEN (1470)
#define IPERF_UDP_RX_LEN (16 << 10)
#define IPERF_TCP_TX_LEN (16 << 10)
#define IPERF_TCP_RX_LEN (16 << 10)
typedef struct {
uint32_t flag;
uint8_t type;
uint16_t dip;
uint16_t dport;
uint16_t sport;
uint32_t interval;
uint32_t time;
uint16_t bw_lim;
uint32_t buffer_len;
} iperf_cfg_t;
typedef struct {
uint64_t total_len;
uint32_t buffer_len;
uint32_t sockfd;
uint32_t actual_len;
uint32_t packet_count;
uint8_t *buffer;
uint32_t udp_lost_counter;
uint32_t udp_packet_counter;
} iperf_traffic_t;
// UDP header for iperf
typedef struct {
int32_t id;
uint32_t tv_sec;
uint32_t tv_usec;
} udp_datagram;
void iperf_start(iperf_cfg_t *cfg);
void iperf_stop(void);
#endif // IPERF_H

View File

@ -1,4 +1,4 @@
// main.c Add support for wifi_cfg and ip address assignment // main.c Add support for wifi_cfg and ip address assignment
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>

View File

@ -1,172 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "driver/usb_serial_jtag.h"
#include "esp_vfs_dev.h"
#include "wifi_cfg.h"
static const char *TAG = "wifi_cfg";
static void trim(char *s){
int n = strlen(s);
while(n>0 && (s[n-1]=='\r' || s[n-1]=='\n' || isspace((unsigned char)s[n-1]))) s[--n]=0;
while(*s && isspace((unsigned char)*s)){
memmove(s, s+1, strlen(s));
}
}
static esp_err_t nvs_set_str2(nvs_handle_t h, const char *key, const char *val){
return val ? nvs_set_str(h, key, val) : nvs_erase_key(h, key);
}
static bool cfg_dhcp = true;
static esp_netif_t *sta_netif = NULL;
static void save_cfg(const char* ssid, const char* pass, const char* ip, const char* mask, const char* gw, bool dhcp){
nvs_handle_t h;
if (nvs_open("netcfg", NVS_READWRITE, &h) != ESP_OK) return;
if (ssid) nvs_set_str2(h, "ssid", ssid);
if (pass) nvs_set_str2(h, "pass", pass);
if (ip) nvs_set_str2(h, "ip", ip);
if (mask) nvs_set_str2(h, "mask", mask);
if (gw) nvs_set_str2(h, "gw", gw);
nvs_set_u8(h, "dhcp", dhcp ? 1 : 0);
nvs_commit(h);
nvs_close(h);
cfg_dhcp = dhcp;
}
static bool load_cfg(char* ssid, size_t ssz, char* pass, size_t psz,
char* ip, size_t isz, char* mask, size_t msz, char* gw, size_t gsz, bool* dhcp){
nvs_handle_t h;
if (nvs_open("netcfg", NVS_READONLY, &h) != ESP_OK) return false;
size_t len;
esp_err_t e;
if ((e = nvs_get_str(h, "ssid", NULL, &len)) != ESP_OK){ nvs_close(h); return false; }
if (len >= ssz){ nvs_close(h); return false; }
nvs_get_str(h, "ssid", ssid, &len);
len = psz; e = nvs_get_str(h, "pass", pass, &len); if (e!=ESP_OK) pass[0]=0;
len = isz; e = nvs_get_str(h, "ip", ip, &len); if (e!=ESP_OK) ip[0]=0;
len = msz; e = nvs_get_str(h, "mask", mask, &len); if (e!=ESP_OK) mask[0]=0;
len = gsz; e = nvs_get_str(h, "gw", gw, &len); if (e!=ESP_OK) gw[0]=0;
uint8_t d=1; nvs_get_u8(h, "dhcp", &d); *dhcp = (d!=0);
nvs_close(h);
return true;
}
void wifi_cfg_force_dhcp(bool enable){ cfg_dhcp = enable; }
static void apply_ip_static(const char* ip, const char* mask, const char* gw){
if (!sta_netif) return;
esp_netif_ip_info_t info = {0};
esp_netif_dhcpc_stop(sta_netif);
info.ip.addr = esp_ip4addr_aton(ip);
info.netmask.addr = esp_ip4addr_aton(mask);
info.gw.addr = esp_ip4addr_aton(gw);
ESP_ERROR_CHECK( esp_netif_set_ip_info(sta_netif, &info) );
}
bool wifi_cfg_apply_from_nvs(void){
char ssid[64]={0}, pass[64]={0}, ip[32]={0}, mask[32]={0}, gw[32]={0};
bool dhcp = true;
if (!load_cfg(ssid,sizeof(ssid), pass,sizeof(pass), ip,sizeof(ip), mask,sizeof(mask), gw,sizeof(gw), &dhcp)){
ESP_LOGW(TAG, "No Wi-Fi config in NVS");
return false;
}
ESP_LOGI(TAG, "Applying Wi-Fi config: SSID=%s DHCP=%d IP=%s", ssid, dhcp, ip);
static bool inited = false;
if (!inited){
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
sta_netif = esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
inited = true;
}
wifi_config_t wcfg = {0};
strncpy((char*)wcfg.sta.ssid, ssid, sizeof(wcfg.sta.ssid)-1);
strncpy((char*)wcfg.sta.password, pass, sizeof(wcfg.sta.password)-1);
wcfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wcfg.sta.sae_pwe_h2e = WPA3_SAE_PWE_BOTH;
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wcfg) );
if (!dhcp && ip[0] && mask[0] && gw[0]){
apply_ip_static(ip, mask, gw);
}else{
if (sta_netif) esp_netif_dhcpc_start(sta_netif);
}
ESP_ERROR_CHECK( esp_wifi_start() );
ESP_ERROR_CHECK( esp_wifi_connect() );
return true;
}
static void cfg_listener_task(void *arg){
usb_serial_jtag_driver_config_t dcfg = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&dcfg));
// Deprecated but functional - causes only a warning
esp_vfs_usb_serial_jtag_use_driver();
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
char line[160];
char ssid[64]={0}, pass[64]={0}, ip[32]={0}, mask[32]={0}, gw[32]={0};
bool dhcp = true;
bool in_cfg = false;
while (1){
if (!fgets(line, sizeof(line), stdin)){
vTaskDelay(pdMS_TO_TICKS(50));
continue;
}
trim(line);
if (!in_cfg){
if (strcmp(line, "CFG")==0){
in_cfg = true;
ssid[0]=pass[0]=ip[0]=mask[0]=gw[0]=0;
dhcp = true;
}
continue;
}
if (strcmp(line, "END")==0){
save_cfg(ssid, pass, ip, mask, gw, dhcp);
printf("OK\n");
wifi_cfg_apply_from_nvs();
in_cfg = false;
continue;
}
if (strncmp(line, "SSID:",5)==0){ strncpy(ssid, line+5, sizeof(ssid)-1); continue; }
if (strncmp(line, "PASS:",5)==0){ strncpy(pass, line+5, sizeof(pass)-1); continue; }
if (strncmp(line, "IP:",3)==0){ strncpy(ip, line+3, sizeof(ip)-1); continue; }
if (strncmp(line, "MASK:",5)==0){ strncpy(mask, line+5, sizeof(mask)-1); continue; }
if (strncmp(line, "GW:",3)==0){ strncpy(gw, line+3, sizeof(gw)-1); continue; }
if (strncmp(line, "DHCP:",5)==0){ dhcp = atoi(line+5) ? true:false; continue; }
}
}
void wifi_cfg_init(void){
nvs_flash_init();
xTaskCreatePinnedToCore(cfg_listener_task, "cfg_listener",
6144, NULL, 5, NULL, tskNO_AFFINITY);
}

View File

@ -1,15 +0,0 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
void wifi_cfg_init(void); // starts serial listener task
bool wifi_cfg_apply_from_nvs(void); // reads saved config and connects WiFi
void wifi_cfg_force_dhcp(bool enable); // for testing
#ifdef __cplusplus
}
#endif