#!/usr/bin/env python3 import argparse import re import shutil import subprocess import sys from datetime import datetime from pathlib import Path DEFAULT_ENV = "seeed_xiao_esp32c3" def find_pio(): pio = shutil.which("pio") if pio: return pio candidate = Path.home() / ".platformio" / "penv" / "Scripts" / "pio.exe" if candidate.exists(): return str(candidate) candidate = Path.home() / ".platformio" / "penv" / "bin" / "pio" if candidate.exists(): return str(candidate) return "pio" def run(command, cwd): print("+ " + " ".join(command), flush=True) subprocess.run(command, check=True, cwd=cwd) def project_name(root): app_h = root / "include" / "app.h" if app_h.exists(): match = re.search(r'^\s*#define\s+PROJECT_NAME\s+"([^"]+)"', app_h.read_text(encoding="utf-8"), re.MULTILINE) if match: return match.group(1) return root.name def safe_filename_part(value): value = value.strip().replace(" ", "-") return re.sub(r"[^A-Za-z0-9._+-]+", "-", value).strip("-") or "project" def package_timestamp(): return datetime.now().astimezone().isoformat(timespec="seconds").replace(":", "-") def main(): parser = argparse.ArgumentParser(description="Build firmware and LittleFS, then create a timestamped OTA update package.") parser.add_argument("-e", "--environment", default=DEFAULT_ENV, help="PlatformIO environment to build.") parser.add_argument("-o", "--output-dir", default="dist", help="Directory for the generated OTA package.") parser.add_argument("--pio", default=find_pio(), help="Path to the PlatformIO executable.") args = parser.parse_args() root = Path(__file__).resolve().parent.parent build_dir = root / ".pio" / "build" / args.environment firmware = build_dir / "firmware.bin" filesystem = build_dir / "littlefs.bin" output_dir = (root / args.output_dir).resolve() output = output_dir / f"{safe_filename_part(project_name(root))}_{package_timestamp()}.tslpkg" try: run([args.pio, "run", "-e", args.environment, "-t", "buildfs"], root) run([args.pio, "run", "-e", args.environment], root) run([ sys.executable, str(Path(__file__).resolve().parent / "create_update_package.py"), "--firmware", str(firmware), "--filesystem", str(filesystem), "--output", str(output), ], root) except subprocess.CalledProcessError as error: sys.exit(error.returncode) print(f"OTA update file: {output.resolve()}") if __name__ == "__main__": main()