1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-27 07:32:22 +01:00
Files
esphome/esphome/components/esp32/post_build.py.script
2025-08-04 14:14:17 +12:00

129 lines
4.6 KiB
Plaintext

Import("env") # noqa: F821
import itertools # noqa: E402
import json # noqa: E402
import os # noqa: E402
import pathlib # noqa: E402
import shutil # noqa: E402
def merge_factory_bin(source, target, env):
"""
Merges all flash sections into a single .factory.bin using esptool.
Attempts multiple methods to detect image layout: flasher_args.json, FLASH_EXTRA_IMAGES, fallback guesses.
"""
firmware_name = os.path.basename(env.subst("$PROGNAME")) + ".bin"
build_dir = pathlib.Path(env.subst("$BUILD_DIR"))
firmware_path = build_dir / firmware_name
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
chip = env.BoardConfig().get("build.mcu", "esp32")
sections = []
flasher_args_path = build_dir / "flasher_args.json"
# 1. Try flasher_args.json
if flasher_args_path.exists():
try:
with flasher_args_path.open() as f:
flash_data = json.load(f)
for addr, fname in sorted(
flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16)
):
file_path = pathlib.Path(fname)
if file_path.exists():
sections.append((addr, str(file_path)))
else:
print(f"Info: {file_path.name} not found - skipping")
except Exception as e:
print(f"Warning: Failed to parse flasher_args.json - {e}")
# 2. Try FLASH_EXTRA_IMAGES if flasher_args.json failed or was empty
if not sections:
flash_images = env.get("FLASH_EXTRA_IMAGES")
if flash_images:
print("Using FLASH_EXTRA_IMAGES from PlatformIO environment")
# flatten any nested lists
flat = list(
itertools.chain.from_iterable(
x if isinstance(x, (list, tuple)) else [x] for x in flash_images
)
)
entries = [env.subst(x) for x in flat]
for i in range(0, len(entries) - 1, 2):
addr, fname = entries[i], entries[i + 1]
if isinstance(fname, (list, tuple)):
print(
f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}"
)
continue
file_path = pathlib.Path(str(fname))
if file_path.exists():
sections.append((addr, file_path))
else:
print(f"Info: {file_path.name} not found — skipping")
if sections:
# Append main firmware to sections
sections.append(("0x10000", firmware_path))
# 3. Final fallback: guess standard image locations
if not sections:
print("Fallback: guessing legacy image paths")
guesses = [
("0x0", build_dir / "bootloader" / "bootloader.bin"),
("0x8000", build_dir / "partition_table" / "partition-table.bin"),
("0xe000", build_dir / "ota_data_initial.bin"),
("0x10000", firmware_path),
]
for addr, file_path in guesses:
if file_path.exists():
sections.append((addr, file_path))
else:
print(f"Info: {file_path.name} not found — skipping")
# If no valid sections found, skip merge
if not sections:
print("No valid flash sections found — skipping .factory.bin creation.")
return
output_path = firmware_path.with_suffix(".factory.bin")
python_exe = f'"{env.subst("$PYTHONEXE")}"'
cmd = [
python_exe,
"-m",
"esptool",
"--chip",
chip,
"merge-bin",
"--flash-size",
flash_size,
"--output",
str(output_path),
]
for addr, file_path in sections:
cmd += [addr, str(file_path)]
print(f"Merging binaries into {output_path}")
result = env.Execute(
env.VerboseAction(" ".join(cmd), "Merging binaries with esptool")
)
if result == 0:
print(f"Successfully created {output_path}")
else:
print(f"Error: esptool merge-bin failed with code {result}")
def esp32_copy_ota_bin(source, target, env):
"""
Copy the main firmware to a .ota.bin file for compatibility with ESPHome OTA tools.
"""
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
shutil.copyfile(firmware_name, new_file_name)
print(f"Copied firmware to {new_file_name}")
# Run merge first, then ota copy second
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821