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
|
# 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
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
|
| 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.
|
||||||
|
|
|
||||||
|
|
@ -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 Wi‑Fi/IP config over serial.")
|
parser = argparse.ArgumentParser(description="Configure ESP32 Wi-Fi settings over serial")
|
||||||
ap.add_argument("--project", required=True, help="Path to ESP‑IDF project")
|
parser.add_argument("--project", help="ESP-IDF project path (defaults to current working directory)")
|
||||||
ap.add_argument("--ssid", required=True, help="Wi‑Fi SSID")
|
parser.add_argument("--ssid", required=True)
|
||||||
ap.add_argument("--password", required=True, help="Wi‑Fi 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 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.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
||||||
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 <stdio.h>
|
||||||
#include <string.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