Add mass deployment system with static IP and WiFi configuration
- Add static IP configuration via Kconfig - Add WiFi console command for runtime reconfiguration - Add mass flash script for automated deployment - Add device detection script - Add comprehensive deployment documentation
This commit is contained in:
parent
b9a4a7c45e
commit
49dc6962ba
|
|
@ -0,0 +1,363 @@
|
||||||
|
# Mass ESP32 Deployment Guide
|
||||||
|
|
||||||
|
Complete guide for flashing 32+ ESP32 devices with unique static IPs.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This system allows you to:
|
||||||
|
- Flash multiple ESP32/ESP32-S2/ESP32-S3 devices automatically
|
||||||
|
- Assign unique static IP addresses to each device (192.168.1.50-192.168.1.81)
|
||||||
|
- Configure WiFi credentials during build
|
||||||
|
- Reconfigure WiFi after flashing via console
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Python dependencies
|
||||||
|
pip install pyserial
|
||||||
|
|
||||||
|
# Ensure ESP-IDF is installed with all targets
|
||||||
|
cd ~/Code/esp32/esp-idf
|
||||||
|
./install.sh esp32,esp32s2,esp32s3
|
||||||
|
|
||||||
|
# Activate ESP-IDF environment
|
||||||
|
. ~/Code/esp32/esp-idf/export.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method 1: Automated Mass Flash (Recommended)
|
||||||
|
|
||||||
|
### Prepare Your Devices
|
||||||
|
|
||||||
|
1. Connect all ESP32 devices to USB hubs
|
||||||
|
2. Verify detection:
|
||||||
|
```bash
|
||||||
|
cd ~/Code/esp32/esp32-iperf
|
||||||
|
python3 detect_esp32.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flash All Devices
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Code/esp32/esp32-iperf
|
||||||
|
|
||||||
|
# Dry run to see the plan
|
||||||
|
python3 flash_all.py \
|
||||||
|
--ssid "YourWiFiSSID" \
|
||||||
|
--password "YourPassword" \
|
||||||
|
--start-ip 192.168.1.50 \
|
||||||
|
--gateway 192.168.1.1 \
|
||||||
|
--dry-run
|
||||||
|
|
||||||
|
# Actually flash (this will take a while!)
|
||||||
|
python3 flash_all.py \
|
||||||
|
--ssid "YourWiFiSSID" \
|
||||||
|
--password "YourPassword" \
|
||||||
|
--start-ip 192.168.1.50 \
|
||||||
|
--gateway 192.168.1.1
|
||||||
|
|
||||||
|
# With chip probing (slower but more accurate)
|
||||||
|
python3 flash_all.py \
|
||||||
|
--ssid "YourWiFiSSID" \
|
||||||
|
--password "YourPassword" \
|
||||||
|
--start-ip 192.168.1.50 \
|
||||||
|
--probe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Script Options
|
||||||
|
|
||||||
|
- `--ssid`: WiFi network name (required)
|
||||||
|
- `--password`: WiFi password (required)
|
||||||
|
- `--start-ip`: Starting IP address (default: 192.168.1.50)
|
||||||
|
- `--gateway`: Gateway IP (default: 192.168.1.1)
|
||||||
|
- `--probe`: Probe each device to detect exact chip type (slower)
|
||||||
|
- `--dry-run`: Show plan without flashing
|
||||||
|
- `--project-dir`: Custom project directory
|
||||||
|
|
||||||
|
### What the Script Does
|
||||||
|
|
||||||
|
For each device:
|
||||||
|
1. Detects chip type (ESP32/ESP32-S2/ESP32-S3)
|
||||||
|
2. Calculates unique IP address (increments from start IP)
|
||||||
|
3. Creates custom sdkconfig.defaults with WiFi and IP settings
|
||||||
|
4. Sets the correct target (esp32/esp32s2/esp32s3)
|
||||||
|
5. Builds firmware with custom configuration
|
||||||
|
6. Flashes the device
|
||||||
|
|
||||||
|
## Method 2: Manual Configuration Per Device
|
||||||
|
|
||||||
|
If you want to flash devices one at a time or need custom settings:
|
||||||
|
|
||||||
|
### Create sdkconfig.defaults
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Code/esp32/esp32-iperf
|
||||||
|
cat > sdkconfig.defaults << EOF
|
||||||
|
# WiFi Configuration
|
||||||
|
CONFIG_WIFI_SSID="YourSSID"
|
||||||
|
CONFIG_WIFI_PASSWORD="YourPassword"
|
||||||
|
CONFIG_WIFI_MAXIMUM_RETRY=5
|
||||||
|
|
||||||
|
# Static IP Configuration
|
||||||
|
CONFIG_USE_STATIC_IP=y
|
||||||
|
CONFIG_STATIC_IP_ADDR="192.168.1.50"
|
||||||
|
CONFIG_STATIC_GATEWAY_ADDR="192.168.1.1"
|
||||||
|
CONFIG_STATIC_NETMASK_ADDR="255.255.255.0"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build and Flash
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set target (choose one)
|
||||||
|
idf.py set-target esp32 # for ESP32
|
||||||
|
idf.py set-target esp32s2 # for ESP32-S2
|
||||||
|
idf.py set-target esp32s3 # for ESP32-S3
|
||||||
|
|
||||||
|
# Build
|
||||||
|
idf.py build
|
||||||
|
|
||||||
|
# Flash to specific device
|
||||||
|
idf.py -p /dev/ttyUSB0 flash monitor
|
||||||
|
|
||||||
|
# For next device, edit sdkconfig.defaults with new IP
|
||||||
|
# Then clean and rebuild
|
||||||
|
idf.py fullclean
|
||||||
|
# ... edit sdkconfig.defaults ...
|
||||||
|
idf.py build
|
||||||
|
idf.py -p /dev/ttyUSB1 flash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method 3: Reconfigure After Flashing
|
||||||
|
|
||||||
|
If devices are already flashed but need different WiFi credentials:
|
||||||
|
|
||||||
|
### Via Console
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to device
|
||||||
|
idf.py -p /dev/ttyUSB0 monitor
|
||||||
|
|
||||||
|
# At the prompt
|
||||||
|
iperf> wifi -s "NewSSID" -p "NewPassword"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Via Script (Multiple Devices)
|
||||||
|
|
||||||
|
Create a script to update all devices:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
for port in /dev/ttyUSB{0..31}; do
|
||||||
|
echo "Updating $port..."
|
||||||
|
# Send commands via screen or minicom
|
||||||
|
screen -S esp_config $port 115200 -X stuff "wifi -s \"NewSSID\" -p \"NewPassword\"\n"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## IP Address Assignment
|
||||||
|
|
||||||
|
Based on your 32 detected devices:
|
||||||
|
|
||||||
|
```
|
||||||
|
Device 1 -> /dev/ttyUSB0 -> 192.168.1.50
|
||||||
|
Device 2 -> /dev/ttyUSB1 -> 192.168.1.51
|
||||||
|
Device 3 -> /dev/ttyUSB2 -> 192.168.1.52
|
||||||
|
...
|
||||||
|
Device 32 -> /dev/ttyUSB31 -> 192.168.1.81
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Your Deployment
|
||||||
|
|
||||||
|
### Check Device Connectivity
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ping all devices
|
||||||
|
for i in {50..81}; do
|
||||||
|
ping -c 1 -W 1 192.168.1.$i && echo "192.168.1.$i is UP" || echo "192.168.1.$i is DOWN"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run iperf Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all devices as iperf servers
|
||||||
|
# (via console on each device)
|
||||||
|
iperf> iperf -s
|
||||||
|
|
||||||
|
# From your PC, test each device
|
||||||
|
for i in {50..81}; do
|
||||||
|
echo "Testing 192.168.1.$i"
|
||||||
|
iperf -c 192.168.1.$i -t 5
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch iperf Test Script
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
start_ip = "192.168.1.50"
|
||||||
|
end_ip = "192.168.1.81"
|
||||||
|
|
||||||
|
base = start_ip.rsplit('.', 1)[0]
|
||||||
|
start = int(start_ip.rsplit('.', 1)[1])
|
||||||
|
end = int(end_ip.rsplit('.', 1)[1])
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for i in range(start, end + 1):
|
||||||
|
ip = f"{base}.{i}"
|
||||||
|
print(f"Testing {ip}...", end=' ', flush=True)
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
['iperf', '-c', ip, '-t', '5', '-f', 'm'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Parse bandwidth from output
|
||||||
|
for line in result.stdout.split('\n'):
|
||||||
|
if 'Mbits/sec' in line:
|
||||||
|
bandwidth = line.split()[-2]
|
||||||
|
print(f"✓ {bandwidth} Mbits/sec")
|
||||||
|
results.append((ip, bandwidth))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("✗ FAILED")
|
||||||
|
|
||||||
|
print(f"\nTested {len(results)}/{end-start+1} devices successfully")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Device Not Detected
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check USB connection
|
||||||
|
lsusb
|
||||||
|
|
||||||
|
# Check permissions
|
||||||
|
sudo usermod -a -G dialout $USER
|
||||||
|
# Log out and back in
|
||||||
|
|
||||||
|
# Check if port exists
|
||||||
|
ls -la /dev/ttyUSB*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flash Failed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Try holding BOOT button during flash
|
||||||
|
idf.py -p /dev/ttyUSB0 flash
|
||||||
|
|
||||||
|
# Lower baud rate
|
||||||
|
idf.py -p /dev/ttyUSB0 -b 115200 flash
|
||||||
|
|
||||||
|
# Erase flash first
|
||||||
|
idf.py -p /dev/ttyUSB0 erase-flash
|
||||||
|
idf.py -p /dev/ttyUSB0 flash
|
||||||
|
```
|
||||||
|
|
||||||
|
### WiFi Not Connecting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor the device
|
||||||
|
idf.py -p /dev/ttyUSB0 monitor
|
||||||
|
|
||||||
|
# Check logs for WiFi errors
|
||||||
|
# Try reconfiguring via console:
|
||||||
|
iperf> wifi -s "YourSSID" -p "YourPassword"
|
||||||
|
```
|
||||||
|
|
||||||
|
### IP Address Conflict
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what's using the IP
|
||||||
|
ping 192.168.1.50
|
||||||
|
arp -a | grep 192.168.1.50
|
||||||
|
|
||||||
|
# Reflash with different IP range
|
||||||
|
python3 flash_all.py \
|
||||||
|
--ssid "YourSSID" \
|
||||||
|
--password "YourPassword" \
|
||||||
|
--start-ip 192.168.1.100
|
||||||
|
```
|
||||||
|
|
||||||
|
## Console Commands Reference
|
||||||
|
|
||||||
|
### WiFi Configuration
|
||||||
|
```
|
||||||
|
wifi -s <ssid> -p <password>
|
||||||
|
```
|
||||||
|
|
||||||
|
### iperf Commands
|
||||||
|
```bash
|
||||||
|
# TCP server
|
||||||
|
iperf -s
|
||||||
|
|
||||||
|
# TCP client
|
||||||
|
iperf -c <ip>
|
||||||
|
|
||||||
|
# UDP server
|
||||||
|
iperf -s -u
|
||||||
|
|
||||||
|
# UDP client
|
||||||
|
iperf -c <ip> -u
|
||||||
|
|
||||||
|
# Custom port
|
||||||
|
iperf -s -p 5002
|
||||||
|
|
||||||
|
# Custom duration
|
||||||
|
iperf -c <ip> -t 30
|
||||||
|
|
||||||
|
# Stop running test
|
||||||
|
iperf -a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced: Parallel Flashing
|
||||||
|
|
||||||
|
To flash multiple devices simultaneously:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# flash_parallel.sh
|
||||||
|
|
||||||
|
# Flash 4 devices at once
|
||||||
|
idf.py -p /dev/ttyUSB0 flash &
|
||||||
|
idf.py -p /dev/ttyUSB1 flash &
|
||||||
|
idf.py -p /dev/ttyUSB2 flash &
|
||||||
|
idf.py -p /dev/ttyUSB3 flash &
|
||||||
|
|
||||||
|
wait
|
||||||
|
echo "Batch complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Each device still needs its own build with unique IP.
|
||||||
|
|
||||||
|
## Production Deployment Workflow
|
||||||
|
|
||||||
|
1. **Prepare**: Connect all devices, verify detection
|
||||||
|
2. **Flash**: Run mass flash script with dry-run first
|
||||||
|
3. **Verify**: Ping all IPs to confirm connectivity
|
||||||
|
4. **Test**: Run iperf from PC to each device
|
||||||
|
5. **Deploy**: Mount devices in test locations
|
||||||
|
6. **Monitor**: Use iperf console to run tests
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
esp32-iperf/
|
||||||
|
├── main/
|
||||||
|
│ ├── main.c # Main app with WiFi
|
||||||
|
│ ├── iperf.c # iperf implementation
|
||||||
|
│ ├── iperf.h # iperf header
|
||||||
|
│ └── Kconfig.projbuild # Configuration options
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── README.md
|
||||||
|
├── flash_all.py # Mass flash script
|
||||||
|
├── detect_esp32.py # Device detection
|
||||||
|
└── sdkconfig.defaults # Auto-generated config
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ESP32 USB Device Detection Script
|
||||||
|
Detects and lists ESP32 devices connected via USB
|
||||||
|
"""
|
||||||
|
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
def detect_esp32_devices():
|
||||||
|
"""
|
||||||
|
Detect ESP32 devices connected via USB.
|
||||||
|
Returns a list of tuples containing (port, description, hwid)
|
||||||
|
"""
|
||||||
|
esp32_devices = []
|
||||||
|
|
||||||
|
# Common USB vendor IDs used by ESP32 boards
|
||||||
|
ESP32_VID_PIDS = [
|
||||||
|
('10C4', 'EA60'), # Silicon Labs CP210x
|
||||||
|
('1A86', '7523'), # CH340
|
||||||
|
('1A86', '55D4'), # CH9102
|
||||||
|
('0403', '6001'), # FTDI FT232
|
||||||
|
('0403', '6010'), # FTDI FT2232
|
||||||
|
('303A', '1001'), # Espressif USB JTAG/serial debug unit (ESP32-C3, ESP32-S3)
|
||||||
|
('303A', '1002'), # Espressif USB JTAG/serial debug unit
|
||||||
|
]
|
||||||
|
|
||||||
|
# Keywords that might appear in ESP32 device descriptions
|
||||||
|
ESP32_KEYWORDS = [
|
||||||
|
'CP210',
|
||||||
|
'CH340',
|
||||||
|
'CH9102',
|
||||||
|
'UART',
|
||||||
|
'USB-SERIAL',
|
||||||
|
'USB SERIAL',
|
||||||
|
'JTAG',
|
||||||
|
'ESP32',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get all available ports
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
# Check by VID:PID
|
||||||
|
if port.vid is not None and port.pid is not None:
|
||||||
|
vid = f"{port.vid:04X}"
|
||||||
|
pid = f"{port.pid:04X}"
|
||||||
|
|
||||||
|
if any((vid == v and pid == p) for v, p in ESP32_VID_PIDS):
|
||||||
|
esp32_devices.append(port)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check by description keywords
|
||||||
|
description_upper = port.description.upper()
|
||||||
|
if any(keyword in description_upper for keyword in ESP32_KEYWORDS):
|
||||||
|
esp32_devices.append(port)
|
||||||
|
|
||||||
|
return esp32_devices
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("ESP32 USB Device Detection")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Detect ESP32 devices
|
||||||
|
esp32_devices = detect_esp32_devices()
|
||||||
|
|
||||||
|
if esp32_devices:
|
||||||
|
print(f"Found {len(esp32_devices)} ESP32 device(s):\n")
|
||||||
|
|
||||||
|
for idx, device in enumerate(esp32_devices, 1):
|
||||||
|
print(f"Device {idx}:")
|
||||||
|
print(f" Port: {device.device}")
|
||||||
|
print(f" Description: {device.description}")
|
||||||
|
print(f" Hardware ID: {device.hwid}")
|
||||||
|
|
||||||
|
if device.vid is not None and device.pid is not None:
|
||||||
|
print(f" VID:PID: {device.vid:04X}:{device.pid:04X}")
|
||||||
|
|
||||||
|
if device.manufacturer:
|
||||||
|
print(f" Manufacturer: {device.manufacturer}")
|
||||||
|
|
||||||
|
if device.serial_number:
|
||||||
|
print(f" Serial: {device.serial_number}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Total ESP32 devices detected: {len(esp32_devices)}")
|
||||||
|
print("=" * 60)
|
||||||
|
else:
|
||||||
|
print("No ESP32 devices detected.")
|
||||||
|
print()
|
||||||
|
print("Available USB serial devices:")
|
||||||
|
all_ports = serial.tools.list_ports.comports()
|
||||||
|
|
||||||
|
if all_ports:
|
||||||
|
for port in all_ports:
|
||||||
|
print(f" - {port.device}: {port.description}")
|
||||||
|
if port.vid is not None and port.pid is not None:
|
||||||
|
print(f" VID:PID = {port.vid:04X}:{port.pid:04X}")
|
||||||
|
else:
|
||||||
|
print(" No USB serial devices found.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nDetection interrupted by user.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError: {e}")
|
||||||
|
print("\nMake sure pyserial is installed: pip install pyserial")
|
||||||
|
|
@ -0,0 +1,308 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ESP32 Mass Flash Script
|
||||||
|
Automatically detects, configures, and flashes multiple ESP32 devices with unique IPs
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Import the detection script
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
try:
|
||||||
|
import detect_esp32
|
||||||
|
except ImportError:
|
||||||
|
print("Error: detect_esp32.py must be in the same directory")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Device type detection based on USB chip
|
||||||
|
def detect_device_type(port_info):
|
||||||
|
"""
|
||||||
|
Try to detect ESP32 variant based on USB chip and other heuristics.
|
||||||
|
Returns: 'esp32', 'esp32s2', 'esp32s3', or 'unknown'
|
||||||
|
"""
|
||||||
|
# Espressif's own USB JTAG is used in ESP32-C3 and ESP32-S3
|
||||||
|
if port_info.vid == 0x303A:
|
||||||
|
if port_info.pid == 0x1001:
|
||||||
|
return 'esp32s3' # Most likely S3
|
||||||
|
return 'esp32s3' # Default to S3 for Espressif USB
|
||||||
|
|
||||||
|
# For FTDI and CP210x, we need to probe the chip
|
||||||
|
# Default assumption based on quantity in your setup
|
||||||
|
# You may need to adjust this logic
|
||||||
|
return 'esp32' # Default to ESP32 for FTDI/CP210x
|
||||||
|
|
||||||
|
|
||||||
|
def probe_chip_type(port):
|
||||||
|
"""
|
||||||
|
Probe the actual chip type using esptool.py
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['esptool.py', '--port', port, 'chip_id'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
output = result.stdout + result.stderr
|
||||||
|
|
||||||
|
if 'ESP32-S3' in output:
|
||||||
|
return 'esp32s3'
|
||||||
|
elif 'ESP32-S2' in output:
|
||||||
|
return 'esp32s2'
|
||||||
|
elif 'ESP32-C3' in output:
|
||||||
|
return 'esp32c3'
|
||||||
|
elif 'ESP32' in output:
|
||||||
|
return 'esp32'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Warning: Could not probe {port}: {e}")
|
||||||
|
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
|
def create_sdkconfig(project_dir, ssid, password, ip_addr, gateway='192.168.1.1', netmask='255.255.255.0'):
|
||||||
|
"""
|
||||||
|
Create sdkconfig.defaults file with WiFi and IP configuration
|
||||||
|
"""
|
||||||
|
sdkconfig_path = os.path.join(project_dir, 'sdkconfig.defaults')
|
||||||
|
|
||||||
|
config_content = f"""# WiFi Configuration
|
||||||
|
CONFIG_WIFI_SSID="{ssid}"
|
||||||
|
CONFIG_WIFI_PASSWORD="{password}"
|
||||||
|
CONFIG_WIFI_MAXIMUM_RETRY=5
|
||||||
|
|
||||||
|
# Static IP Configuration
|
||||||
|
CONFIG_USE_STATIC_IP=y
|
||||||
|
CONFIG_STATIC_IP_ADDR="{ip_addr}"
|
||||||
|
CONFIG_STATIC_GATEWAY_ADDR="{gateway}"
|
||||||
|
CONFIG_STATIC_NETMASK_ADDR="{netmask}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open(sdkconfig_path, 'w') as f:
|
||||||
|
f.write(config_content)
|
||||||
|
|
||||||
|
print(f" Created sdkconfig.defaults with IP {ip_addr}")
|
||||||
|
|
||||||
|
|
||||||
|
def flash_device(port, chip_type, device_num, ssid, password, base_ip, project_dir):
|
||||||
|
"""
|
||||||
|
Configure and flash a single device
|
||||||
|
"""
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"Device {device_num}: {port} ({chip_type})")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
# Calculate IP address
|
||||||
|
base_parts = base_ip.split('.')
|
||||||
|
ip_last_octet = int(base_parts[3]) + device_num - 1
|
||||||
|
|
||||||
|
if ip_last_octet > 254:
|
||||||
|
print(f" ERROR: IP address overflow! Device {device_num} would exceed .254")
|
||||||
|
return False
|
||||||
|
|
||||||
|
ip_addr = f"{base_parts[0]}.{base_parts[1]}.{base_parts[2]}.{ip_last_octet}"
|
||||||
|
|
||||||
|
print(f" Assigned IP: {ip_addr}")
|
||||||
|
|
||||||
|
# Create sdkconfig.defaults
|
||||||
|
create_sdkconfig(project_dir, ssid, password, ip_addr)
|
||||||
|
|
||||||
|
# Clean previous build if target changed
|
||||||
|
print(" Cleaning previous build...")
|
||||||
|
subprocess.run(['idf.py', 'fullclean'], cwd=project_dir,
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
# Set target
|
||||||
|
print(f" Setting target to {chip_type}...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['idf.py', 'set-target', chip_type],
|
||||||
|
cwd=project_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f" ERROR: Failed to set target: {result.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Build
|
||||||
|
print(f" Building for {chip_type}...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['idf.py', 'build'],
|
||||||
|
cwd=project_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f" ERROR: Build failed!")
|
||||||
|
print(result.stderr[-1000:]) # Print last 1000 chars of error
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f" Build successful!")
|
||||||
|
|
||||||
|
# Flash
|
||||||
|
print(f" Flashing to {port}...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['idf.py', '-p', port, 'flash'],
|
||||||
|
cwd=project_dir,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f" ERROR: Flash failed!")
|
||||||
|
print(result.stderr[-1000:])
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f" ✓ Successfully flashed device {device_num} at {ip_addr}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Mass flash ESP32 devices with unique IPs')
|
||||||
|
parser.add_argument('--ssid', required=True, help='WiFi SSID')
|
||||||
|
parser.add_argument('--password', required=True, help='WiFi password')
|
||||||
|
parser.add_argument('--start-ip', default='192.168.1.50',
|
||||||
|
help='Starting IP address (default: 192.168.1.50)')
|
||||||
|
parser.add_argument('--gateway', default='192.168.1.1',
|
||||||
|
help='Gateway IP (default: 192.168.1.1)')
|
||||||
|
parser.add_argument('--project-dir', default=None,
|
||||||
|
help='ESP32 iperf project directory')
|
||||||
|
parser.add_argument('--probe', action='store_true',
|
||||||
|
help='Probe each device to detect exact chip type (slower)')
|
||||||
|
parser.add_argument('--dry-run', action='store_true',
|
||||||
|
help='Show what would be done without flashing')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Find project directory
|
||||||
|
if args.project_dir:
|
||||||
|
project_dir = args.project_dir
|
||||||
|
else:
|
||||||
|
# Try to find it relative to script location
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
project_dir = os.path.join(script_dir, 'esp32-iperf')
|
||||||
|
|
||||||
|
if not os.path.exists(project_dir):
|
||||||
|
project_dir = os.path.join(os.path.expanduser('~/Code/esp32'), 'esp32-iperf')
|
||||||
|
|
||||||
|
if not os.path.exists(project_dir):
|
||||||
|
print(f"ERROR: Project directory not found: {project_dir}")
|
||||||
|
print("Please specify --project-dir")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Using project directory: {project_dir}")
|
||||||
|
|
||||||
|
# Detect devices
|
||||||
|
print("\nDetecting ESP32 devices...")
|
||||||
|
devices = detect_esp32.detect_esp32_devices()
|
||||||
|
|
||||||
|
if not devices:
|
||||||
|
print("No ESP32 devices detected!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Found {len(devices)} device(s)")
|
||||||
|
|
||||||
|
# Detect chip types
|
||||||
|
device_list = []
|
||||||
|
for idx, device in enumerate(devices, 1):
|
||||||
|
if args.probe:
|
||||||
|
print(f"Probing {device.device}...")
|
||||||
|
chip_type = probe_chip_type(device.device)
|
||||||
|
else:
|
||||||
|
chip_type = detect_device_type(device)
|
||||||
|
|
||||||
|
device_list.append({
|
||||||
|
'number': idx,
|
||||||
|
'port': device.device,
|
||||||
|
'chip': chip_type,
|
||||||
|
'info': device
|
||||||
|
})
|
||||||
|
|
||||||
|
# Display plan
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("FLASH PLAN")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
print(f"SSID: {args.ssid}")
|
||||||
|
print(f"Starting IP: {args.start_ip}")
|
||||||
|
print(f"Gateway: {args.gateway}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
base_parts = args.start_ip.split('.')
|
||||||
|
for dev in device_list:
|
||||||
|
ip_last = int(base_parts[3]) + dev['number'] - 1
|
||||||
|
ip = f"{base_parts[0]}.{base_parts[1]}.{base_parts[2]}.{ip_last}"
|
||||||
|
print(f"Device {dev['number']:2d}: {dev['port']} -> {dev['chip']:8s} -> {ip}")
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print("\nDry run - no devices will be flashed")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
response = input("Proceed with flashing? (yes/no): ").strip().lower()
|
||||||
|
if response != 'yes':
|
||||||
|
print("Aborted.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Flash devices
|
||||||
|
success_count = 0
|
||||||
|
failed_devices = []
|
||||||
|
|
||||||
|
for dev in device_list:
|
||||||
|
try:
|
||||||
|
success = flash_device(
|
||||||
|
dev['port'],
|
||||||
|
dev['chip'],
|
||||||
|
dev['number'],
|
||||||
|
args.ssid,
|
||||||
|
args.password,
|
||||||
|
args.start_ip,
|
||||||
|
project_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
failed_devices.append(dev['number'])
|
||||||
|
|
||||||
|
time.sleep(1) # Brief pause between devices
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nFlashing interrupted by user!")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n ERROR: Exception during flash: {e}")
|
||||||
|
failed_devices.append(dev['number'])
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print("FLASH SUMMARY")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
print(f"Successfully flashed: {success_count}/{len(device_list)} devices")
|
||||||
|
|
||||||
|
if failed_devices:
|
||||||
|
print(f"Failed devices: {', '.join(map(str, failed_devices))}")
|
||||||
|
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nInterrupted by user")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nFATAL ERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
@ -18,4 +18,31 @@ menu "ESP32 iperf Configuration"
|
||||||
help
|
help
|
||||||
Set the Maximum retry to prevent station reconnecting to the AP unlimited when the AP doesn't exist.
|
Set the Maximum retry to prevent station reconnecting to the AP unlimited when the AP doesn't exist.
|
||||||
|
|
||||||
|
config USE_STATIC_IP
|
||||||
|
bool "Use static IP address"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable static IP address configuration instead of DHCP.
|
||||||
|
|
||||||
|
config STATIC_IP_ADDR
|
||||||
|
string "Static IP address"
|
||||||
|
default "192.168.1.50"
|
||||||
|
depends on USE_STATIC_IP
|
||||||
|
help
|
||||||
|
Static IP address for this device.
|
||||||
|
|
||||||
|
config STATIC_GATEWAY_ADDR
|
||||||
|
string "Gateway address"
|
||||||
|
default "192.168.1.1"
|
||||||
|
depends on USE_STATIC_IP
|
||||||
|
help
|
||||||
|
Gateway IP address.
|
||||||
|
|
||||||
|
config STATIC_NETMASK_ADDR
|
||||||
|
string "Netmask"
|
||||||
|
default "255.255.255.0"
|
||||||
|
depends on USE_STATIC_IP
|
||||||
|
help
|
||||||
|
Netmask for the network.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
||||||
88
main/main.c
88
main/main.c
|
|
@ -54,7 +54,24 @@ void wifi_init_sta(void)
|
||||||
|
|
||||||
ESP_ERROR_CHECK(esp_netif_init());
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
esp_netif_create_default_wifi_sta();
|
|
||||||
|
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||||
|
|
||||||
|
#ifdef CONFIG_USE_STATIC_IP
|
||||||
|
// Configure static IP
|
||||||
|
ESP_LOGI(TAG, "Configuring static IP: %s", CONFIG_STATIC_IP_ADDR);
|
||||||
|
|
||||||
|
esp_netif_dhcpc_stop(sta_netif);
|
||||||
|
|
||||||
|
esp_netif_ip_info_t ip_info;
|
||||||
|
memset(&ip_info, 0, sizeof(esp_netif_ip_info_t));
|
||||||
|
|
||||||
|
ip_info.ip.addr = ipaddr_addr(CONFIG_STATIC_IP_ADDR);
|
||||||
|
ip_info.gw.addr = ipaddr_addr(CONFIG_STATIC_GATEWAY_ADDR);
|
||||||
|
ip_info.netmask.addr = ipaddr_addr(CONFIG_STATIC_NETMASK_ADDR);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &ip_info));
|
||||||
|
#endif
|
||||||
|
|
||||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|
@ -114,6 +131,74 @@ static struct {
|
||||||
struct arg_end *end;
|
struct arg_end *end;
|
||||||
} iperf_args;
|
} iperf_args;
|
||||||
|
|
||||||
|
/* Console command structure for wifi config */
|
||||||
|
static struct {
|
||||||
|
struct arg_str *ssid;
|
||||||
|
struct arg_str *password;
|
||||||
|
struct arg_end *end;
|
||||||
|
} wifi_args;
|
||||||
|
|
||||||
|
static int cmd_wifi(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int nerrors = arg_parse(argc, argv, (void **)&wifi_args);
|
||||||
|
if (nerrors != 0) {
|
||||||
|
arg_print_errors(stderr, wifi_args.end, argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wifi_args.ssid->count == 0) {
|
||||||
|
ESP_LOGE(TAG, "Please provide SSID with -s option");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
wifi_config_t wifi_config;
|
||||||
|
memset(&wifi_config, 0, sizeof(wifi_config_t));
|
||||||
|
|
||||||
|
// Set SSID
|
||||||
|
strncpy((char *)wifi_config.sta.ssid, wifi_args.ssid->sval[0], sizeof(wifi_config.sta.ssid) - 1);
|
||||||
|
|
||||||
|
// Set password if provided
|
||||||
|
if (wifi_args.password->count > 0) {
|
||||||
|
strncpy((char *)wifi_config.sta.password, wifi_args.password->sval[0], sizeof(wifi_config.sta.password) - 1);
|
||||||
|
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Updating WiFi configuration...");
|
||||||
|
ESP_LOGI(TAG, "SSID: %s", wifi_config.sta.ssid);
|
||||||
|
|
||||||
|
// Stop WiFi
|
||||||
|
esp_wifi_disconnect();
|
||||||
|
esp_wifi_stop();
|
||||||
|
|
||||||
|
// Update config
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||||
|
|
||||||
|
// Restart WiFi
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start());
|
||||||
|
esp_wifi_connect();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "WiFi configuration updated. Attempting to connect...");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_wifi(void)
|
||||||
|
{
|
||||||
|
wifi_args.ssid = arg_str1("s", "ssid", "<ssid>", "WiFi SSID");
|
||||||
|
wifi_args.password = arg_str0("p", "password", "<password>", "WiFi password");
|
||||||
|
wifi_args.end = arg_end(1);
|
||||||
|
|
||||||
|
const esp_console_cmd_t wifi_cmd = {
|
||||||
|
.command = "wifi",
|
||||||
|
.help = "Configure WiFi credentials (wifi -s <ssid> -p <password>)",
|
||||||
|
.hint = NULL,
|
||||||
|
.func = &cmd_wifi,
|
||||||
|
.argtable = &wifi_args
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_console_cmd_register(&wifi_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
static int cmd_iperf(int argc, char **argv)
|
static int cmd_iperf(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
|
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
|
||||||
|
|
@ -215,6 +300,7 @@ static void initialize_console(void)
|
||||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||||
|
|
||||||
register_iperf();
|
register_iperf();
|
||||||
|
register_wifi();
|
||||||
|
|
||||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue