build works
This commit is contained in:
parent
a623e4e1e6
commit
c03679358e
|
|
@ -1,6 +1,8 @@
|
|||
# The following lines of boilerplate have to be in your project's
|
||||
# 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)
|
||||
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
142
README.md
|
|
@ -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
|
||||
- Bandwidth measurement
|
||||
- Packet loss detection (UDP)
|
||||
- Console-based control interface
|
||||
- WiFi station mode support
|
||||
| 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 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
|
||||
|
||||
## Hardware Requirements
|
||||
# Hello World Example
|
||||
|
||||
- ESP32, ESP32-S2, or ESP32-S3 development board
|
||||
- WiFi network
|
||||
Starts a FreeRTOS task to print "Hello World".
|
||||
|
||||
## Software Requirements
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
- ESP-IDF v5.0 or later
|
||||
- iperf2 or iperf3 for testing with PC
|
||||
## How to use example
|
||||
|
||||
## Building
|
||||
Follow detailed instructions provided specifically for this example.
|
||||
|
||||
1. Set up ESP-IDF environment:
|
||||
```bash
|
||||
. $HOME/Code/esp32/esp-idf/export.sh
|
||||
```
|
||||
Select the instructions depending on Espressif chip installed on your development board:
|
||||
|
||||
2. Set the target chip (choose one):
|
||||
```bash
|
||||
# For ESP32
|
||||
idf.py set-target esp32
|
||||
- [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html)
|
||||
- [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html)
|
||||
|
||||
# For ESP32-S2
|
||||
idf.py set-target esp32s2
|
||||
|
||||
# For ESP32-S3
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
## Example folder contents
|
||||
|
||||
3. Configure WiFi credentials:
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
Navigate to "ESP32 iperf Configuration" and set your SSID and password.
|
||||
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).
|
||||
|
||||
4. Build the project:
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
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).
|
||||
|
||||
5. Flash to your device:
|
||||
```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
|
||||
Below is short explanation of remaining files in the project folder.
|
||||
|
||||
```
|
||||
esp32-iperf/
|
||||
├── CMakeLists.txt
|
||||
├── main/
|
||||
├── pytest_hello_world.py Python script used for automated testing
|
||||
├── main
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── Kconfig.projbuild
|
||||
│ ├── main.c # WiFi initialization and console
|
||||
│ ├── iperf.c # iperf implementation
|
||||
│ └── iperf.h # iperf header
|
||||
└── README.md
|
||||
│ └── hello_world_main.c
|
||||
└── README.md This is the file you are currently reading
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -1,203 +1,72 @@
|
|||
|
||||
#!/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 typing import List, Dict
|
||||
|
||||
import serial, serial.tools.list_ports as list_ports
|
||||
import subprocess
|
||||
def send_cfg(port, ssid, password, dhcp, start_ip, mask, gw):
|
||||
print(f"Connecting to {port}...")
|
||||
with serial.Serial(port, baudrate=115200, timeout=2) as ser:
|
||||
time.sleep(0.5)
|
||||
ser.reset_input_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)
|
||||
|
||||
KNOWN_VIDS = {0x0403, 0x10C4, 0x1A86, 0x067B, 0x303A}
|
||||
|
||||
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_output_buffer()
|
||||
|
||||
lines = [
|
||||
"CFG\n",
|
||||
f"SSID:{ssid}\n" if ssid else "",
|
||||
f"PASS:{password}\n" if password else "",
|
||||
f"IP:{ip}\n" if ip else "",
|
||||
f"MASK:{mask}\n" if mask else "",
|
||||
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
|
||||
print("\nDevice response:")
|
||||
while ser.in_waiting:
|
||||
sys.stdout.write(ser.read(ser.in_waiting).decode(errors='ignore'))
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.1)
|
||||
print("\n✅ Configuration sent successfully.")
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Mass flash ESP32 devices and push Wi‑Fi/IP config over serial.")
|
||||
ap.add_argument("--project", required=True, help="Path to ESP‑IDF project")
|
||||
ap.add_argument("--ssid", required=True, help="Wi‑Fi SSID")
|
||||
ap.add_argument("--password", required=True, help="Wi‑Fi password")
|
||||
ap.add_argument("--start-ip", default="192.168.1.50", help="Base IP (only used if --dhcp=0)")
|
||||
ap.add_argument("--mask", default="255.255.255.0", help="Netmask for static IP")
|
||||
ap.add_argument("--gw", default="192.168.1.1", help="Gateway for static IP")
|
||||
ap.add_argument("--dhcp", type=int, choices=[0,1], default=1, help="1=use DHCP, 0=set static IPs")
|
||||
ap.add_argument("--baud", type=int, default=460800, help="Flashing baud rate")
|
||||
ap.add_argument("--cfg-baud", type=int, default=115200, help="Serial baud for config exchange")
|
||||
ap.add_argument("--ports", help="Comma-separated globs to override port selection, e.g. '/dev/ttyUSB*,/dev/ttyACM*'")
|
||||
ap.add_argument("--dry-run", action="store_true", help="Plan only; do not flash or configure")
|
||||
args = ap.parse_args()
|
||||
parser = argparse.ArgumentParser(description="Configure ESP32 Wi-Fi settings over serial")
|
||||
parser.add_argument("--project", help="ESP-IDF project path (defaults to current working directory)")
|
||||
parser.add_argument("--ssid", required=True)
|
||||
parser.add_argument("--password", required=True)
|
||||
parser.add_argument("--start-ip", help="Static IP address")
|
||||
parser.add_argument("--mask", default="255.255.255.0")
|
||||
parser.add_argument("--gw", help="Gateway address")
|
||||
parser.add_argument("--dhcp", type=int, choices=[0,1], default=1)
|
||||
parser.add_argument("--baud", type=int, default=460800)
|
||||
parser.add_argument("--cfg-baud", type=int, default=115200)
|
||||
parser.add_argument("--ports", nargs='+', help="Serial port(s), e.g., /dev/ttyUSB0 /dev/ttyUSB1")
|
||||
parser.add_argument("--port", help="Single serial port (shorthand for --ports PORT)")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
|
||||
project_dir = os.path.abspath(args.project)
|
||||
if not os.path.isdir(project_dir):
|
||||
print(f"Project directory not found: {project_dir}")
|
||||
args = parser.parse_args()
|
||||
|
||||
# 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)
|
||||
|
||||
patterns = [p.strip() for p in args.ports.split(",")] if args.ports else None
|
||||
devices = list_ports_filtered(patterns)
|
||||
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:
|
||||
print("\nDry run only.")
|
||||
return
|
||||
|
||||
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 Wi‑Fi 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.")
|
||||
# Apply configuration
|
||||
for port in ports:
|
||||
if args.dry_run:
|
||||
print(f"[DRY RUN] Would send Wi-Fi config to {port}")
|
||||
else:
|
||||
send_cfg(port, args.ssid, args.password, args.dhcp, args.start_ip, args.mask, args.gw)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
idf_component_register(
|
||||
SRCS
|
||||
"main.c"
|
||||
"iperf.c"
|
||||
"wifi_cfg.c"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
)
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES
|
||||
esp_wifi
|
||||
nvs_flash
|
||||
esp_netif
|
||||
console
|
||||
lwip
|
||||
driver
|
||||
esp_driver_uart
|
||||
vfs
|
||||
iperf
|
||||
wifi_cfg
|
||||
)
|
||||
|
|
|
|||
340
main/iperf.c
340
main/iperf.c
|
|
@ -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...");
|
||||
}
|
||||
}
|
||||
59
main/iperf.h
59
main/iperf.h
|
|
@ -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
|
||||
|
|
@ -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 <string.h>
|
||||
|
|
|
|||
172
main/wifi_cfg.c
172
main/wifi_cfg.c
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 Wi‑Fi
|
||||
void wifi_cfg_force_dhcp(bool enable); // for testing
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Reference in New Issue