mass deploy
This commit is contained in:
parent
60c1d49d3e
commit
f2abcf450b
|
|
@ -89,7 +89,7 @@ static void led_task(void *arg)
|
|||
|
||||
case LED_STATE_CONNECTED:
|
||||
// Blue solid - connected successfully
|
||||
set_led_color(0, 0, 255); // Blue
|
||||
set_led_color(0, 255, 0); // Green
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Mass Deployment Tool
|
||||
Parallel or sequential flashing and WiFi configuration for multiple ESP32 devices
|
||||
Parallel flashing and WiFi configuration for multiple ESP32 devices
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
@ -46,6 +46,7 @@ class DeviceDeployer:
|
|||
|
||||
self.devices = []
|
||||
self.results = {}
|
||||
self.log_dir = Path('/tmp')
|
||||
|
||||
def print_banner(self):
|
||||
"""Print deployment configuration"""
|
||||
|
|
@ -119,6 +120,7 @@ class DeviceDeployer:
|
|||
def flash_and_configure(self, index, device):
|
||||
"""Flash and configure a single device"""
|
||||
ip_addr = self.get_ip_for_index(index)
|
||||
log_file = self.log_dir / f"esp32_deploy_{index}.log"
|
||||
log_lines = []
|
||||
|
||||
def log(msg):
|
||||
|
|
@ -133,17 +135,20 @@ class DeviceDeployer:
|
|||
# Flash firmware
|
||||
log("Flashing...")
|
||||
try:
|
||||
subprocess.run(
|
||||
result = subprocess.run(
|
||||
['idf.py', '-p', device, '-b', str(self.baud_rate), 'flash'],
|
||||
cwd=self.project_dir,
|
||||
check=True,
|
||||
capture_output=not (self.verbose or not self.parallel),
|
||||
capture_output=True,
|
||||
timeout=300 # 5 minute timeout
|
||||
)
|
||||
log("✓ Flash successful")
|
||||
except subprocess.CalledProcessError as e:
|
||||
log(f"✗ Flash failed on attempt {attempt}")
|
||||
if attempt == self.max_retries:
|
||||
# Write log file
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -156,6 +161,8 @@ class DeviceDeployer:
|
|||
except subprocess.TimeoutExpired:
|
||||
log(f"✗ Flash timeout on attempt {attempt}")
|
||||
if attempt == self.max_retries:
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -206,6 +213,9 @@ class DeviceDeployer:
|
|||
|
||||
if result.returncode == 0:
|
||||
log("✓ Ping successful")
|
||||
# Write log file
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -216,6 +226,8 @@ class DeviceDeployer:
|
|||
else:
|
||||
log(f"✗ Ping failed on attempt {attempt}")
|
||||
if attempt == self.max_retries:
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -226,6 +238,9 @@ class DeviceDeployer:
|
|||
except subprocess.TimeoutExpired:
|
||||
log(f"✗ Ping timeout on attempt {attempt}")
|
||||
else:
|
||||
# Write log file
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -237,6 +252,8 @@ class DeviceDeployer:
|
|||
time.sleep(2)
|
||||
|
||||
# If we get here, all retries failed
|
||||
with open(log_file, 'w') as f:
|
||||
f.write('\n'.join(log_lines))
|
||||
return {
|
||||
'index': index,
|
||||
'device': device,
|
||||
|
|
@ -251,6 +268,10 @@ class DeviceDeployer:
|
|||
print(f"{Colors.YELLOW}[3/4] Flashing and configuring (parallel)...{Colors.NC}")
|
||||
print()
|
||||
|
||||
# Clean old log files
|
||||
for f in self.log_dir.glob('esp32_deploy_*.log'):
|
||||
f.unlink()
|
||||
|
||||
# Use ThreadPoolExecutor for parallel execution
|
||||
max_workers = min(32, len(self.devices))
|
||||
|
||||
|
|
@ -275,6 +296,10 @@ class DeviceDeployer:
|
|||
print(f"{Colors.YELLOW}[3/4] Flashing and configuring (sequential)...{Colors.NC}")
|
||||
print()
|
||||
|
||||
# Clean old log files
|
||||
for f in self.log_dir.glob('esp32_deploy_*.log'):
|
||||
f.unlink()
|
||||
|
||||
for i, device in enumerate(self.devices):
|
||||
print(f"\n{Colors.BLUE}--- Device {i+1}/{len(self.devices)} ---{Colors.NC}")
|
||||
result = self.flash_and_configure(i, device)
|
||||
|
|
@ -352,19 +377,20 @@ class DeviceDeployer:
|
|||
|
||||
print(f"{Colors.BLUE}{'='*70}{Colors.NC}")
|
||||
|
||||
# Print log location
|
||||
print()
|
||||
print(f"Logs: /tmp/esp32_deploy_*.log")
|
||||
|
||||
# Print test commands
|
||||
print()
|
||||
print("Test commands:")
|
||||
print(f" # Test first device")
|
||||
print(f" iperf -c {self.get_ip_for_index(0)}")
|
||||
print()
|
||||
print(f" # Ping all devices")
|
||||
ip_range = f"{self.ip_start}..{self.ip_start + len(self.devices) - 1}"
|
||||
print(f" for i in {{{ip_range}}}; do ping -c 1 {self.ip_base}.$i & done; wait")
|
||||
print()
|
||||
print(f" # Test first device with iperf")
|
||||
print(f" iperf -c {self.get_ip_for_index(0)}")
|
||||
print()
|
||||
print(f" # Check all device status")
|
||||
print(f" ./check_device_status.py --reset")
|
||||
print()
|
||||
|
||||
return failed_count
|
||||
|
||||
|
|
@ -374,55 +400,58 @@ def main():
|
|||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Deploy to first 15 devices (parallel, default)
|
||||
# Deploy to first 30 devices (default)
|
||||
%(prog)s -s ClubHouse2G -p mypassword
|
||||
|
||||
# Deploy sequentially (easier debugging)
|
||||
%(prog)s -s ClubHouse2G -p mypassword --sequential
|
||||
|
||||
# Deploy to 31 devices in parallel
|
||||
%(prog)s -s ClubHouse2G -p mypassword -n 31
|
||||
# Deploy to all connected devices
|
||||
%(prog)s -s ClubHouse2G -p mypassword -n 0
|
||||
|
||||
# Custom IP range
|
||||
%(prog)s -s ClubHouse2G -p mypassword --start-ip 10.0.0.100
|
||||
|
||||
# From environment variables
|
||||
export WIFI_SSID="MyNetwork"
|
||||
export WIFI_PASSWORD="secret123"
|
||||
export SSID="MyNetwork"
|
||||
export PASSWORD="secret123"
|
||||
export START_IP="192.168.1.51"
|
||||
%(prog)s
|
||||
|
||||
# Verbose sequential mode (see everything)
|
||||
%(prog)s -s ClubHouse2G -p mypassword --sequential -v
|
||||
|
||||
# Skip ping verification (faster)
|
||||
%(prog)s -s ClubHouse2G -p mypassword --no-verify
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('-d', '--dir', default=os.getcwd(),
|
||||
help='ESP-IDF project directory (default: current dir)')
|
||||
parser.add_argument('-s', '--ssid',
|
||||
default=os.environ.get('WIFI_SSID', 'ClubHouse2G'),
|
||||
help='WiFi SSID (default: ClubHouse2G or $WIFI_SSID)')
|
||||
default=os.environ.get('SSID', 'ClubHouse2G'),
|
||||
help='WiFi SSID (default: ClubHouse2G or $SSID)')
|
||||
parser.add_argument('-p', '--password',
|
||||
default=os.environ.get('WIFI_PASSWORD', ''),
|
||||
help='WiFi password (default: $WIFI_PASSWORD)')
|
||||
parser.add_argument('--start-ip', default='192.168.1.51',
|
||||
help='Starting IP address (default: 192.168.1.51)')
|
||||
parser.add_argument('-n', '--num-devices', type=int, default=15,
|
||||
help='Number of devices to deploy (default: 15, use 0 for all)')
|
||||
parser.add_argument('-g', '--gateway', default='192.168.1.1',
|
||||
help='Gateway IP (default: 192.168.1.1)')
|
||||
parser.add_argument('-m', '--netmask', default='255.255.255.0',
|
||||
help='Network mask (default: 255.255.255.0)')
|
||||
parser.add_argument('-b', '--baud', type=int, default=460800,
|
||||
help='Baud rate for flashing (default: 460800)')
|
||||
parser.add_argument('-r', '--retries', type=int, default=2,
|
||||
help='Max retries per device (default: 2)')
|
||||
default=os.environ.get('PASSWORD', ''),
|
||||
help='WiFi password (default: $PASSWORD)')
|
||||
parser.add_argument('--start-ip',
|
||||
default=os.environ.get('START_IP', '192.168.1.51'),
|
||||
help='Starting IP address (default: 192.168.1.51 or $START_IP)')
|
||||
parser.add_argument('-n', '--num-devices', type=int, default=30,
|
||||
help='Number of devices to deploy (default: 30, use 0 for all)')
|
||||
parser.add_argument('-g', '--gateway',
|
||||
default=os.environ.get('GATEWAY', '192.168.1.1'),
|
||||
help='Gateway IP (default: 192.168.1.1 or $GATEWAY)')
|
||||
parser.add_argument('-m', '--netmask',
|
||||
default=os.environ.get('NETMASK', '255.255.255.0'),
|
||||
help='Network mask (default: 255.255.255.0 or $NETMASK)')
|
||||
parser.add_argument('-b', '--baud', type=int,
|
||||
default=int(os.environ.get('BAUD_RATE', 460800)),
|
||||
help='Baud rate for flashing (default: 460800 or $BAUD_RATE)')
|
||||
parser.add_argument('-r', '--retries', type=int,
|
||||
default=int(os.environ.get('MAX_RETRIES', 2)),
|
||||
help='Max retries per device (default: 2 or $MAX_RETRIES)')
|
||||
parser.add_argument('--no-verify', action='store_true',
|
||||
help='Skip ping verification')
|
||||
parser.add_argument('--sequential', action='store_true',
|
||||
help='Flash devices sequentially instead of parallel (easier debugging)')
|
||||
help='Flash devices sequentially instead of parallel')
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='Verbose output')
|
||||
|
||||
|
|
@ -434,7 +463,7 @@ Examples:
|
|||
print()
|
||||
print("Provide password via:")
|
||||
print(" 1. Command line: -p 'your_password'")
|
||||
print(" 2. Environment: export WIFI_PASSWORD='your_password'")
|
||||
print(" 2. Environment: export PASSWORD='your_password'")
|
||||
print()
|
||||
print("Example:")
|
||||
print(f" {sys.argv[0]} -s MyWiFi -p mypassword")
|
||||
|
|
|
|||
Loading…
Reference in New Issue