add async manual
This commit is contained in:
parent
0a4bce5bf6
commit
05fbff0092
|
|
@ -1,10 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Async Batch Configuration Tool
|
||||
The definitive parallel configuration tool.
|
||||
Configures 30+ ESP32 devices concurrently using non-blocking I/O.
|
||||
|
||||
Features:
|
||||
- Concurrent execution (configure 30 devices in <20 seconds)
|
||||
- Regex-based state detection (Robust against noise)
|
||||
- Robust Regex-based state detection
|
||||
- Supports verifying both Station Mode (IP check) and Monitor Mode
|
||||
- Context-aware logging
|
||||
"""
|
||||
|
||||
|
|
@ -32,11 +35,10 @@ class DeviceLoggerAdapter(logging.LoggerAdapter):
|
|||
def process(self, msg, kwargs):
|
||||
return '[%s] %s' % (self.extra['connid'], msg), kwargs
|
||||
|
||||
# Configure clean logging format
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')
|
||||
logger = logging.getLogger("BatchConfig")
|
||||
|
||||
class AsyncConfigurator:
|
||||
class Esp32Configurator:
|
||||
"""
|
||||
Manages the lifecycle of configuring a single ESP32 device via Async Serial.
|
||||
"""
|
||||
|
|
@ -46,11 +48,15 @@ class AsyncConfigurator:
|
|||
self.args = args
|
||||
self.log = DeviceLoggerAdapter(logger, {'connid': port})
|
||||
|
||||
# Pre-compile Regex patterns for efficiency (Parsing logic)
|
||||
# --- Regex Patterns ---
|
||||
# Success indicators
|
||||
self.regex_got_ip = re.compile(r'got ip:(\d+\.\d+\.\d+\.\d+)', re.IGNORECASE)
|
||||
self.regex_config_saved = re.compile(r'Config saved|saved to NVS', re.IGNORECASE)
|
||||
# Prompts that indicate the device is alive and listening
|
||||
self.regex_monitor_success = re.compile(r'Monitor mode active', re.IGNORECASE)
|
||||
|
||||
# Prompts indicating device is booting/ready
|
||||
self.regex_ready = re.compile(r'Initialization complete|GPS synced|No WiFi config found', re.IGNORECASE)
|
||||
|
||||
# Error indicators
|
||||
self.regex_error = re.compile(r'Error:|Failed|Disconnect', re.IGNORECASE)
|
||||
|
||||
async def run(self):
|
||||
|
|
@ -62,7 +68,7 @@ class AsyncConfigurator:
|
|||
return False
|
||||
|
||||
try:
|
||||
# 1. Hardware Reset
|
||||
# 1. Hardware Reset (DTR/RTS)
|
||||
self.log.info("Resetting...")
|
||||
writer.transport.serial.dtr = False
|
||||
writer.transport.serial.rts = True
|
||||
|
|
@ -71,9 +77,10 @@ class AsyncConfigurator:
|
|||
await asyncio.sleep(0.1)
|
||||
writer.transport.serial.dtr = True
|
||||
|
||||
# 2. Wait for Boot (Regex detection)
|
||||
# 2. Wait for Boot
|
||||
# We assume the device is ready when we see logs or a prompt
|
||||
if not await self._wait_for_boot(reader):
|
||||
self.log.warning("Boot prompt missed, sending config anyway...")
|
||||
self.log.warning("Boot prompt missed, attempting config anyway...")
|
||||
|
||||
# 3. Send Configuration
|
||||
await self._send_config(writer)
|
||||
|
|
@ -95,7 +102,6 @@ class AsyncConfigurator:
|
|||
|
||||
while time.time() < timeout:
|
||||
try:
|
||||
# Read line with a short timeout to keep checking total time
|
||||
line_bytes = await asyncio.wait_for(reader.readline(), timeout=0.5)
|
||||
line = line_bytes.decode('utf-8', errors='ignore').strip()
|
||||
if not line: continue
|
||||
|
|
@ -110,6 +116,7 @@ class AsyncConfigurator:
|
|||
"""Builds and transmits the configuration command"""
|
||||
self.log.info(f"Sending config for IP {self.target_ip}...")
|
||||
|
||||
# Construct command block
|
||||
config_str = (
|
||||
f"CFG\n"
|
||||
f"SSID:{self.args.ssid}\n"
|
||||
|
|
@ -130,9 +137,9 @@ class AsyncConfigurator:
|
|||
await writer.drain()
|
||||
|
||||
async def _verify_configuration(self, reader):
|
||||
"""Monitors output for confirmation of IP assignment"""
|
||||
"""Monitors output for confirmation of Success"""
|
||||
self.log.info("Verifying configuration...")
|
||||
timeout = time.time() + 15 # 15s connection timeout
|
||||
timeout = time.time() + 15 # 15s verification timeout
|
||||
|
||||
while time.time() < timeout:
|
||||
try:
|
||||
|
|
@ -140,7 +147,7 @@ class AsyncConfigurator:
|
|||
line = line_bytes.decode('utf-8', errors='ignore').strip()
|
||||
if not line: continue
|
||||
|
||||
# Check for IP assignment
|
||||
# Check for Station Mode Success (IP Address)
|
||||
m_ip = self.regex_got_ip.search(line)
|
||||
if m_ip:
|
||||
got_ip = m_ip.group(1)
|
||||
|
|
@ -150,20 +157,25 @@ class AsyncConfigurator:
|
|||
else:
|
||||
self.log.warning(f"MISMATCH: Wanted {self.target_ip}, got {got_ip}")
|
||||
|
||||
# Check for Monitor Mode Success
|
||||
if self.regex_monitor_success.search(line):
|
||||
self.log.info("SUCCESS: Monitor Mode Active")
|
||||
return True
|
||||
|
||||
# Check for errors
|
||||
if self.regex_error.search(line):
|
||||
self.log.warning(f"Device Error: {line}")
|
||||
self.log.warning(f"Device Reported Error: {line}")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
|
||||
self.log.error("Timeout: Device did not report IP address.")
|
||||
self.log.error("Timeout: Device did not confirm configuration.")
|
||||
return False
|
||||
|
||||
async def main_async():
|
||||
parser = argparse.ArgumentParser(description='Async ESP32 Batch Config')
|
||||
|
||||
# Arguments matching your existing tools
|
||||
# Arguments
|
||||
parser.add_argument('--start-ip', required=True, help='Starting Static IP')
|
||||
parser.add_argument('-s', '--ssid', default='ClubHouse2G', help='WiFi SSID')
|
||||
parser.add_argument('-P', '--password', default='ez2remember', help='WiFi password')
|
||||
|
|
@ -201,7 +213,7 @@ async def main_async():
|
|||
|
||||
for i, dev in enumerate(devices):
|
||||
current_ip = str(start_ip_obj + i)
|
||||
configurator = AsyncConfigurator(dev.device, current_ip, args)
|
||||
configurator = Esp32Configurator(dev.device, current_ip, args)
|
||||
tasks.append(configurator.run())
|
||||
|
||||
# Run everything at once
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Async Batch Configuration Tool (Pro Version)
|
||||
Incorporates architectural patterns from flows.py:
|
||||
- Regex-based state detection
|
||||
- Event-driven synchronization
|
||||
- Context-aware logging
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import serial_asyncio
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import ipaddress
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
|
||||
# Ensure detection script is available
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
try:
|
||||
import detect_esp32
|
||||
except ImportError:
|
||||
print("Error: 'detect_esp32.py' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Logging Setup (Borrowed concept from flows.py) ---
|
||||
class DeviceLoggerAdapter(logging.LoggerAdapter):
|
||||
def process(self, msg, kwargs):
|
||||
return '[%s] %s' % (self.extra['connid'], msg), kwargs
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s', datefmt='%H:%M:%S')
|
||||
logger = logging.getLogger("BatchConfig")
|
||||
|
||||
class Esp32Configurator:
|
||||
"""
|
||||
Manages the lifecycle of configuring a single ESP32 device.
|
||||
Uses regex patterns similar to iperf_client in flows.py.
|
||||
"""
|
||||
def __init__(self, port, target_ip, config_args):
|
||||
self.port = port
|
||||
self.target_ip = target_ip
|
||||
self.args = config_args
|
||||
self.log = DeviceLoggerAdapter(logger, {'connid': port})
|
||||
|
||||
# Regex Patterns (Inspired by flows.py lines 350+)
|
||||
# We pre-compile these for efficiency
|
||||
self.regex_got_ip = re.compile(r'got ip:(\d+\.\d+\.\d+\.\d+)', re.IGNORECASE)
|
||||
self.regex_wifi_connected = re.compile(r'WiFi connected: Yes', re.IGNORECASE)
|
||||
self.regex_config_saved = re.compile(r'Config saved', re.IGNORECASE)
|
||||
self.regex_ready_prompt = re.compile(r'Initialization complete|GPS synced|No WiFi config found', re.IGNORECASE)
|
||||
self.regex_error = re.compile(r'Error:|Failed|Disconnect', re.IGNORECASE)
|
||||
|
||||
async def run(self):
|
||||
"""Main coroutine for this device"""
|
||||
try:
|
||||
reader, writer = await serial_asyncio.open_serial_connection(url=self.port, baudrate=115200)
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to open port: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 1. Hardware Reset via DTR/RTS
|
||||
self.log.info("Resetting...")
|
||||
writer.transport.serial.dtr = False
|
||||
writer.transport.serial.rts = True
|
||||
await asyncio.sleep(0.1)
|
||||
writer.transport.serial.rts = False
|
||||
await asyncio.sleep(0.1)
|
||||
writer.transport.serial.dtr = True
|
||||
|
||||
# 2. Monitor Boot Stream
|
||||
# We wait until the device settles or we see a prompt
|
||||
await self._wait_for_boot(reader)
|
||||
|
||||
# 3. Send Configuration
|
||||
await self._send_config(writer)
|
||||
|
||||
# 4. Verification Loop
|
||||
success = await self._verify_connection(reader)
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
self.log.error(f"Process Exception: {e}")
|
||||
return False
|
||||
finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
async def _wait_for_boot(self, reader):
|
||||
"""Consumes boot logs until device looks ready"""
|
||||
self.log.info("Waiting for boot...")
|
||||
# Give it a max of 5 seconds to settle or show a prompt
|
||||
end_time = time.time() + 5
|
||||
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
line_bytes = await asyncio.wait_for(reader.readline(), timeout=0.5)
|
||||
line = line_bytes.decode('utf-8', errors='ignore').strip()
|
||||
if not line: continue
|
||||
|
||||
# Check if device is ready to accept commands
|
||||
if self.regex_ready_prompt.search(line):
|
||||
self.log.info("Device ready detected.")
|
||||
return
|
||||
except asyncio.TimeoutError:
|
||||
# If silence for 0.5s, it's probably waiting
|
||||
continue
|
||||
|
||||
async def _send_config(self, writer):
|
||||
"""Constructs and writes the config block"""
|
||||
self.log.info(f"Sending config for IP {self.target_ip}...")
|
||||
|
||||
config_str = (
|
||||
f"CFG\n"
|
||||
f"SSID:{self.args.ssid}\n"
|
||||
f"PASS:{self.args.password}\n"
|
||||
f"IP:{self.target_ip}\n"
|
||||
f"MASK:{self.args.netmask}\n"
|
||||
f"GW:{self.args.gateway}\n"
|
||||
f"DHCP:0\n"
|
||||
f"BAND:{self.args.band}\n"
|
||||
f"BW:{self.args.bandwidth}\n"
|
||||
f"POWERSAVE:{self.args.powersave}\n"
|
||||
f"MODE:{self.args.mode}\n"
|
||||
f"MON_CH:{self.args.monitor_channel}\n"
|
||||
f"END\n"
|
||||
)
|
||||
|
||||
# Flush input buffer before writing to ensure clean state
|
||||
# (Note: asyncio streams don't have a direct flush_input, relies on OS)
|
||||
writer.write(config_str.encode('utf-8'))
|
||||
await writer.drain()
|
||||
|
||||
async def _verify_connection(self, reader):
|
||||
"""Reads stream verifying IP assignment"""
|
||||
self.log.info("Verifying configuration...")
|
||||
end_time = time.time() + 15 # 15s Timeout
|
||||
|
||||
while time.time() < end_time:
|
||||
try:
|
||||
line_bytes = await asyncio.wait_for(reader.readline(), timeout=1.0)
|
||||
line = line_bytes.decode('utf-8', errors='ignore').strip()
|
||||
if not line: continue
|
||||
|
||||
# Regex Checks
|
||||
m_ip = self.regex_got_ip.search(line)
|
||||
if m_ip:
|
||||
got_ip = m_ip.group(1)
|
||||
if got_ip == self.target_ip:
|
||||
self.log.info(f"SUCCESS: Assigned {got_ip}")
|
||||
return True
|
||||
else:
|
||||
self.log.warning(f"MISMATCH: Wanted {self.target_ip}, got {got_ip}")
|
||||
|
||||
if self.regex_error.search(line):
|
||||
self.log.warning(f"Device reported error: {line}")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
continue
|
||||
|
||||
self.log.error("Timeout waiting for IP confirmation.")
|
||||
return False
|
||||
|
||||
async def main_async():
|
||||
parser = argparse.ArgumentParser(description='Async ESP32 Batch Config (Pro)')
|
||||
parser.add_argument('--start-ip', required=True, help='Start IP')
|
||||
parser.add_argument('-s', '--ssid', default='ClubHouse2G')
|
||||
parser.add_argument('-P', '--password', default='ez2remember')
|
||||
parser.add_argument('-g', '--gateway', default='192.168.1.1')
|
||||
parser.add_argument('-m', '--netmask', default='255.255.255.0')
|
||||
parser.add_argument('-b', '--band', default='2.4G')
|
||||
parser.add_argument('-B', '--bandwidth', default='HT20')
|
||||
parser.add_argument('-ps', '--powersave', default='NONE')
|
||||
parser.add_argument('-M', '--mode', default='STA')
|
||||
parser.add_argument('-mc', '--monitor-channel', type=int, default=36)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 1. Detect
|
||||
print("Scanning devices...")
|
||||
devices = detect_esp32.detect_esp32_devices()
|
||||
if not devices:
|
||||
print("No devices found.")
|
||||
return
|
||||
|
||||
# Sort naturally
|
||||
def natural_keys(d):
|
||||
return [int(c) if c.isdigit() else c for c in re.split(r'(\d+)', d.device)]
|
||||
devices.sort(key=natural_keys)
|
||||
|
||||
# 2. Parse IP
|
||||
try:
|
||||
start_ip_obj = ipaddress.IPv4Address(args.start_ip)
|
||||
except:
|
||||
print("Invalid IP")
|
||||
return
|
||||
|
||||
# 3. Create Tasks
|
||||
tasks = []
|
||||
print(f"Configuring {len(devices)} devices concurrently...")
|
||||
|
||||
for i, dev in enumerate(devices):
|
||||
current_ip = str(start_ip_obj + i)
|
||||
configurator = Esp32Configurator(dev.device, current_ip, args)
|
||||
tasks.append(configurator.run())
|
||||
|
||||
# 4. Run All
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
# 5. Summary
|
||||
success_count = results.count(True)
|
||||
print("\n" + "="*40)
|
||||
print(f"Total: {len(devices)}")
|
||||
print(f"Success: {success_count}")
|
||||
print(f"Failed: {len(devices) - success_count}")
|
||||
print("="*40)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
# Windows/Linux loop compatibility handling (borrowed from ssh_node.py lines 60-65)
|
||||
if os.name == 'nt':
|
||||
loop = asyncio.ProactorEventLoop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
asyncio.run(main_async())
|
||||
except KeyboardInterrupt:
|
||||
print("\nCancelled.")
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# ESP32 Fleet Management Tools: User Manual
|
||||
|
||||
This document provides instructions for using the asynchronous Python tools designed to manage, flash, and configure large fleets (30+) of ESP32 devices simultaneously.
|
||||
|
||||
## ⚠️ Build Target Selection (Carve Out)
|
||||
**Critical Step:** Before using the mass deployment tools, you must define which chip architecture you are building for. The `async_mass_deploy.py` script relies on the project's current configuration.
|
||||
|
||||
Run **one** of the following commands in the project root to set the target:
|
||||
|
||||
| Target Hardware | Command |
|
||||
| :--- | :--- |
|
||||
| **Original ESP32** | `idf.py set-target esp32` |
|
||||
| **ESP32-S3** | `idf.py set-target esp32s3` |
|
||||
| **ESP32-C5** | `idf.py set-target esp32c5` |
|
||||
|
||||
*Note: Changing the target forces a full clean rebuild. Ensure you see a successful configuration message before proceeding to deployment.*
|
||||
|
||||
---
|
||||
|
||||
## NVS Behavior Summary
|
||||
Understanding how Non-Volatile Storage (NVS) is handled is critical to preventing data loss or configuration mismatch.
|
||||
|
||||
| Script | Operation Mode | NVS Behavior | Use Case |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **async_mass_deploy.py** | Default (No credentials) | **Preserved** | Updating firmware code without changing IP/WiFi settings. |
|
||||
| **async_mass_deploy.py** | With SSID/Pass | **Partially Rewritten** | Updating firmware AND forcing new WiFi credentials/IPs. |
|
||||
| **async_mass_deploy.py** | With `--erase` | **WIPED Completely** | Factory reset. Deletes all settings, calibration, and code. |
|
||||
| **async_batch_config.py** | Normal Run | **Partially Rewritten** | Changing WiFi/IP settings *without* touching firmware. |
|
||||
| **async_find_failed.py** | Audit / Diagnostics | **Read-Only** | Checking status. No changes to NVS. |
|
||||
|
||||
---
|
||||
|
||||
## 1. async_mass_deploy.py
|
||||
**The "Factory Floor" Tool.** Use this to flash the compiled binary (`.bin`) to the chips. It combines building, flashing (via `esptool`), and optional initial configuration.
|
||||
|
||||
### When to use:
|
||||
* You have new, blank ESP32 chips.
|
||||
* You have modified the C code (`main.c`) and need to update the firmware.
|
||||
* Devices are stuck in a boot loop and need a full erase.
|
||||
|
||||
|
||||
|
||||
### Usage Examples:
|
||||
|
||||
**A. Firmware Update Only (Preserve Settings)**
|
||||
Updates the code but keeps the existing WiFi SSID, Password, and Static IP stored on the device.
|
||||
```bash
|
||||
python3 async_mass_deploy.py
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ESP32 Fleet Management Manual</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.container {
|
||||
background-color: #fff;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 { border-bottom: 2px solid #eaeaea; padding-bottom: 10px; color: #2c3e50; }
|
||||
h2 { margin-top: 30px; color: #34495e; border-bottom: 1px solid #eee; padding-bottom: 5px; }
|
||||
h3 { margin-top: 20px; color: #455a64; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
th { background-color: #f2f2f2; color: #333; }
|
||||
tr:nth-child(even) { background-color: #f9f9f9; }
|
||||
|
||||
code {
|
||||
background-color: #f4f4f4;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 0.9em;
|
||||
color: #d63384;
|
||||
}
|
||||
pre {
|
||||
background-color: #2d3436;
|
||||
color: #dfe6e9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
}
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
color: #856404;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.info-box {
|
||||
background-color: #e1f5fe;
|
||||
border-left: 5px solid #039be5;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.tag-green { color: #27ae60; font-weight: bold; }
|
||||
.tag-yellow { color: #f39c12; font-weight: bold; }
|
||||
.tag-red { color: #c0392b; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>ESP32 Fleet Management Tools: User Manual</h1>
|
||||
<p>This document provides instructions for using the asynchronous Python tools designed to manage, flash, and configure large fleets (30+) of ESP32 devices simultaneously.</p>
|
||||
|
||||
<div class="info-box">
|
||||
<h2>🛠️ Critical Step: Set Build Target</h2>
|
||||
<p>Before running <code>async_mass_deploy.py</code>, you <strong>must</strong> configure the project for your specific chip architecture. The deployment script uses the currently configured target.</p>
|
||||
<p>Run <strong>one</strong> of the following commands in your project root before deployment:</p>
|
||||
|
||||
<p><strong>For Original ESP32:</strong></p>
|
||||
<pre><code>idf.py set-target esp32</code></pre>
|
||||
|
||||
<p><strong>For ESP32-S3:</strong></p>
|
||||
<pre><code>idf.py set-target esp32s3</code></pre>
|
||||
|
||||
<p><strong>For ESP32-C5:</strong></p>
|
||||
<pre><code>idf.py set-target esp32c5</code></pre>
|
||||
|
||||
<p><em>Note: Changing the target triggers a full re-build. Ensure the build completes successfully before attempting mass deployment.</em></p>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<strong>⚠️ NVS Behavior Summary (Read First)</strong><br>
|
||||
Understanding how Non-Volatile Storage (NVS) is handled is critical to preventing data loss or configuration mismatch.
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Script</th>
|
||||
<th>Operation Mode</th>
|
||||
<th>NVS Behavior</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>async_mass_deploy.py</code></td>
|
||||
<td>Default</td>
|
||||
<td><span class="tag-green">Preserved</span></td>
|
||||
<td>Updating firmware code without changing IP/WiFi settings.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>async_mass_deploy.py</code></td>
|
||||
<td>With SSID/Pass</td>
|
||||
<td><span class="tag-yellow">Partially Rewritten</span></td>
|
||||
<td>Updating firmware AND forcing new WiFi credentials/IPs.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>async_mass_deploy.py</code></td>
|
||||
<td>With <code>--erase</code></td>
|
||||
<td><span class="tag-red">WIPED Completely</span></td>
|
||||
<td>Factory reset. Deletes all settings, calibration, and code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>async_batch_config.py</code></td>
|
||||
<td>Normal Run</td>
|
||||
<td><span class="tag-yellow">Partially Rewritten</span></td>
|
||||
<td>Changing WiFi/IP settings <em>without</em> touching firmware.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>async_find_failed.py</code></td>
|
||||
<td>Audit / Diagnostics</td>
|
||||
<td><span class="tag-green">Read-Only</span></td>
|
||||
<td>Checking status. No changes to NVS.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>1. async_mass_deploy.py</h2>
|
||||
<p><strong>The "Factory Floor" Tool.</strong> Use this to flash the compiled binary (<code>.bin</code>) to the chips. It combines building, flashing (via <code>esptool</code>), and optional initial configuration.</p>
|
||||
|
||||
<h3>When to use:</h3>
|
||||
<ul>
|
||||
<li>You have new, blank ESP32 chips.</li>
|
||||
<li>You have modified the C code (<code>main.c</code>) and need to update the firmware.</li>
|
||||
<li>Devices are stuck in a boot loop and need a full erase.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Usage Examples:</h3>
|
||||
|
||||
<p><strong>A. Firmware Update Only (Preserve Settings)</strong><br>
|
||||
Updates the code but keeps the existing WiFi SSID, Password, and Static IP stored on the device.</p>
|
||||
<pre><code>python3 async_mass_deploy.py</code></pre>
|
||||
|
||||
<p><strong>B. Full Factory Deployment (Erase & Provision)</strong><br>
|
||||
Wipes the chip clean, flashes new code, and sets up WiFi/IPs from scratch.</p>
|
||||
<pre><code>python3 async_mass_deploy.py \
|
||||
--erase \
|
||||
--start-ip 192.168.1.101 \
|
||||
-s ClubHouse2G \
|
||||
-p ez2remember</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>2. async_batch_config.py</h2>
|
||||
<p><strong>The "Field Update" Tool.</strong> Use this to change settings (IP, WiFi, Mode) on devices that are <em>already</em> running valid firmware. It is much faster than <code>mass_deploy</code> because it does not flash binaries.</p>
|
||||
|
||||
<h3>NVS Behavior:</h3>
|
||||
<ul>
|
||||
<li><strong>Rewrites:</strong> SSID, Password, IP, Gateway, Netmask, Mode, Bandwidth.</li>
|
||||
<li><strong>Preserves:</strong> Any other NVS keys not explicitly touched by the config command.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Usage Examples:</h3>
|
||||
|
||||
<p><strong>A. Configure for Station Mode (Standard)</strong><br>
|
||||
Sets sequential IPs starting at .101.</p>
|
||||
<pre><code>python3 async_batch_config.py \
|
||||
--start-ip 192.168.1.101 \
|
||||
-s ClubHouse2G \
|
||||
-P ez2remember</code></pre>
|
||||
|
||||
<p><strong>B. Configure for Monitor Mode (Sniffer)</strong><br>
|
||||
Sets devices to listen promiscuously on Channel 36.</p>
|
||||
<pre><code>python3 async_batch_config.py \
|
||||
--start-ip 192.168.1.101 \
|
||||
-s ClubHouse2G \
|
||||
-P ez2remember \
|
||||
-M MONITOR \
|
||||
-mc 36 \
|
||||
-ps NONE</code></pre>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>3. async_find_failed.py</h2>
|
||||
<p><strong>The "Doctor" Tool.</strong> Use this to audit the status of the fleet. It does not flash or configure; it only asks the devices "Are you okay?" and displays the result.</p>
|
||||
|
||||
<h3>NVS Behavior:</h3>
|
||||
<ul>
|
||||
<li><strong>Read-Only:</strong> Does not write to NVS during scanning.</li>
|
||||
<li><strong>Recovery:</strong> The "Soft Reconnect" option sends a command to retry WiFi, but does not permanently alter the saved config.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Usage:</h3>
|
||||
<pre><code>python3 async_find_failed.py</code></pre>
|
||||
<p><em>Output:</em> Displays a table of Port, IP, Mode, and LED status.<br>
|
||||
<em>Interactive Menu:</em> Allows you to reboot or send reconnect commands to failed devices found during the scan.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue