3ESP32 Ethernet OTA Upload Script
4=================================
5Recompiles the OTA_Test_Firmware with a custom serial message,
6then pushes the resulting binary to the ESP32 over TCP (W5500 Ethernet).
9 python ota_upload.py # Uses timestamped default message
10 python ota_upload.py --message "Hello world" # Custom message
11 python ota_upload.py --ip 192.168.2.10 # Override target IP
14 1. Send 4-byte firmware size (big-endian)
15 2. Send raw firmware binary in 4 KB chunks
29DEFAULT_IP =
"192.168.2.5"
36SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
37PROJECT_DIR = os.path.join(SCRIPT_DIR,
"OTA_Test_Firmware")
38PIO_BUILD_DIR = os.path.join(PROJECT_DIR,
".pio",
"build",
"adafruit_feather_esp32s3")
39FIRMWARE_BIN = os.path.join(PIO_BUILD_DIR,
"firmware.bin")
44 print(
" ESP32-S3 Ethernet OTA Upload Tool")
50 """Try to find the 'pio' command in PATH or common installation locations."""
52 pio_path = shutil.which(
"pio")
57 if sys.platform ==
"win32":
58 user_profile = os.environ.get(
"USERPROFILE",
"")
60 os.path.join(user_profile,
".platformio",
"penv",
"Scripts",
"pio.exe"),
71 "C:\\Python313\\Scripts\\pio.exe",
73 for path
in paths_to_check:
74 if os.path.isfile(path):
82 """Compile the firmware with the given OTA_MESSAGE baked in.
84 Returns the path to the compiled .bin file.
86 print(f
"[COMPILE] Message to bake into firmware:")
87 print(f
' "{message}"')
92 env = os.environ.copy()
93 escaped_msg = message.replace(
'"',
'\\"')
94 extra_flags = f
"-DOTA_MESSAGE='\"{escaped_msg}\"'"
95 env[
"PLATFORMIO_BUILD_FLAGS"] = extra_flags
98 print(f
"[COMPILE] Running: {pio_cmd} run (in {PROJECT_DIR})")
99 print(f
"[COMPILE] Extra build flags: {extra_flags}")
103 result = subprocess.run(
104 [pio_cmd,
"run"], cwd=PROJECT_DIR, env=env, capture_output=
True, text=
True
106 except FileNotFoundError:
107 print(
"[COMPILE] ERROR: 'pio' command not found.")
108 print(
" Make sure PlatformIO CLI is installed and in your PATH.")
109 print(
" Install: pip install platformio")
114 for line
in result.stdout.strip().
split(
"\n"):
115 print(f
" [PIO] {line}")
117 for line
in result.stderr.strip().
split(
"\n"):
119 if "error" in line.lower():
120 print(f
" [PIO ERROR] {line}")
122 print(f
" [PIO] {line}")
124 if result.returncode != 0:
126 print(f
"[COMPILE] FAILED (exit code {result.returncode})")
129 if not os.path.isfile(FIRMWARE_BIN):
130 print(f
"[COMPILE] ERROR: Expected binary not found at:")
131 print(f
" {FIRMWARE_BIN}")
134 size = os.path.getsize(FIRMWARE_BIN)
136 print(f
"[COMPILE] SUCCESS — firmware.bin = {size:,} bytes")
137 print(f
" Path: {FIRMWARE_BIN}")
143 """Send the firmware binary to the ESP32 over TCP."""
144 file_size = os.path.getsize(bin_path)
146 print(f
"[UPLOAD] Target: {ip}:{port}")
147 print(f
"[UPLOAD] Firmware size: {file_size:,} bytes")
151 print(f
"[UPLOAD] Connecting to ESP32...")
152 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
153 sock.settimeout(CONNECT_TIMEOUT)
156 sock.connect((ip, port))
157 except socket.timeout:
158 print(f
"[UPLOAD] ERROR: Connection timed out after {CONNECT_TIMEOUT}s.")
159 print(f
" Is the ESP32 running and reachable at {ip}?")
161 except ConnectionRefusedError:
162 print(f
"[UPLOAD] ERROR: Connection refused by {ip}:{port}.")
163 print(f
" Is the OTA firmware running on the ESP32?")
166 print(f
"[UPLOAD] ERROR: Could not connect: {e}")
169 print(f
"[UPLOAD] Connected!")
170 sock.settimeout(TRANSFER_TIMEOUT)
174 header = struct.pack(
">I", file_size)
176 print(f
"[UPLOAD] Sent size header: {file_size} bytes")
183 with open(bin_path,
"rb")
as f:
184 while sent < file_size:
185 chunk = f.read(CHUNK_SIZE)
191 percent = int(sent * 100 / file_size)
193 if percent // 5 != last_percent // 5:
194 last_percent = percent
196 rate = sent / elapsed / 1024
if elapsed > 0
else 0
198 f
"[UPLOAD] Progress: {percent:3d}% "
199 f
"({sent:,} / {file_size:,} bytes) "
205 print(f
"[UPLOAD] Transfer complete! {sent:,} bytes in {elapsed:.1f}s")
206 print(f
"[UPLOAD] Waiting for ESP32 confirmation...")
210 response = sock.recv(1024).
decode(errors=
"ignore").strip()
212 print(f
"[UPLOAD] SUCCESS: ESP32 received firmware and is rebooting.")
215 f
"[UPLOAD] WARNING: Connection closed without 'OK' response. Response was: '{response}'"
217 except socket.timeout:
218 print(f
"[UPLOAD] WARNING: Timed out waiting for 'OK' confirmation.")
220 except socket.timeout:
221 print(f
"[UPLOAD] ERROR: Transfer timed out.")
223 except BrokenPipeError:
224 print(f
"[UPLOAD] ERROR: Connection lost during transfer.")
233 parser = argparse.ArgumentParser(
234 description=
"Compile and OTA-upload firmware to ESP32-S3 over Ethernet"
240 help=
"Message to bake into firmware (default: timestamped string)",
243 "--ip", default=DEFAULT_IP, help=f
"ESP32 IP address (default: {DEFAULT_IP})"
249 default=DEFAULT_PORT,
250 help=f
"ESP32 OTA TCP port (default: {DEFAULT_PORT})",
255 help=
"Skip compilation, just upload the existing binary",
257 args = parser.parse_args()
260 if args.message
is None:
261 ts = datetime.datetime.now().strftime(
"%Y-%m-%d %H:%M:%S")
262 args.message = f
"OTA update at {ts}"
265 if args.skip_compile:
266 if not os.path.isfile(FIRMWARE_BIN):
267 print(f
"[ERROR] --skip-compile set but no binary found at:")
268 print(f
" {FIRMWARE_BIN}")
270 print(f
"[COMPILE] Skipped (--skip-compile). Using existing binary.")
271 print(f
" {FIRMWARE_BIN} ({os.path.getsize(FIRMWARE_BIN):,} bytes)")
273 bin_path = FIRMWARE_BIN
282 print(
" Done! Watch the ESP32 serial monitor for the")
283 print(f
' new message: "{args.message}"')
287if __name__ ==
"__main__":
static std::vector< string_t > split(const string_t &reference_string)
split the string input to reference tokens
str compile_firmware(str message)
upload_firmware(str bin_path, str ip, int port)
TOML_NODISCARD_CTOR time() noexcept=default
static void decode(const disk_t *d)