143 lines
4.5 KiB
Python
Executable File
143 lines
4.5 KiB
Python
Executable File
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = [
|
|
# "python-dotenv",
|
|
# "questionary",
|
|
# ]
|
|
# ///
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import shutil
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
|
|
# Ensure we run from the script's directory
|
|
script_dir = Path(__file__).resolve().parent
|
|
os.chdir(script_dir)
|
|
|
|
try:
|
|
import questionary
|
|
except ImportError:
|
|
print("[Error] Failed to import questionary. Run this script using 'uv run flash.py'.")
|
|
sys.exit(1)
|
|
|
|
def run_esphome(args, env):
|
|
esphome_bin = shutil.which("esphome")
|
|
if not esphome_bin:
|
|
print("\n[Error] 'esphome' CLI not found on PATH.")
|
|
print("Please install it (e.g. 'brew install esphome' or 'pip install esphome').\n")
|
|
sys.exit(1)
|
|
|
|
cmd = [esphome_bin] + args
|
|
print(f"\nExecuting: {' '.join(cmd)}\n")
|
|
try:
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
env=env,
|
|
stdout=sys.stdout,
|
|
stderr=sys.stderr,
|
|
text=True
|
|
)
|
|
process.wait()
|
|
return process.returncode
|
|
except KeyboardInterrupt:
|
|
print("\n\nExecution interrupted.")
|
|
return 1
|
|
|
|
def get_device_name(yaml_path):
|
|
try:
|
|
with open(yaml_path, "r") as f:
|
|
in_esphome = False
|
|
for line in f:
|
|
stripped = line.strip()
|
|
if stripped.startswith("esphome:"):
|
|
in_esphome = True
|
|
continue
|
|
if in_esphome:
|
|
if line.strip() and not line.startswith(" ") and not line.startswith("\t"):
|
|
break
|
|
if stripped.startswith("name:"):
|
|
name = stripped.split(":", 1)[1].split("#", 1)[0].strip()
|
|
return name.replace('"', '').replace("'", "")
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
def main():
|
|
env_file = script_dir / ".env"
|
|
template_file = script_dir / ".env.template"
|
|
|
|
# Auto-copy template if .env is missing
|
|
if not env_file.exists():
|
|
if template_file.exists():
|
|
print(f"'.env' not found. Copying '.env.template' to '.env'...")
|
|
shutil.copy(template_file, env_file)
|
|
print("\n[Action Required] Please open '.env' and fill in your Wi-Fi credentials.")
|
|
sys.exit(0)
|
|
else:
|
|
print("[Error] Neither '.env' nor '.env.template' could be found.")
|
|
sys.exit(1)
|
|
|
|
load_dotenv(env_file)
|
|
|
|
# 1. Select YAML Configuration
|
|
yaml_files = sorted([f.name for f in script_dir.glob("*.yaml")])
|
|
if not yaml_files:
|
|
print("[Error] No ESPHome configuration files (*.yaml) found in this directory.")
|
|
sys.exit(1)
|
|
|
|
selected_yaml = questionary.select(
|
|
"Select the ESPHome configuration to compile/flash:",
|
|
choices=yaml_files
|
|
).ask()
|
|
|
|
if not selected_yaml:
|
|
sys.exit(0)
|
|
|
|
# 2. Select Command Action
|
|
commands = [
|
|
questionary.Choice("Run (Validate, compile, upload, and start logs)", "run"),
|
|
questionary.Choice("Compile (Build only)", "compile"),
|
|
questionary.Choice("Upload (Flash compiled binary only)", "upload"),
|
|
questionary.Choice("Logs (Stream device logs)", "logs"),
|
|
questionary.Choice("Validate (Check syntax only)", "config"),
|
|
questionary.Choice("Clean (Clear build artifacts)", "clean"),
|
|
]
|
|
|
|
selected_cmd = questionary.select(
|
|
"Select ESPHome action to perform:",
|
|
choices=commands
|
|
).ask()
|
|
|
|
if not selected_cmd:
|
|
sys.exit(0)
|
|
|
|
# If upload is selected, verify build files exist
|
|
if selected_cmd == "upload":
|
|
device_name = get_device_name(script_dir / selected_yaml)
|
|
if device_name:
|
|
build_dir = script_dir / ".esphome" / "build" / device_name
|
|
if not build_dir.exists():
|
|
print(f"\n[Warning] Build directory for '{device_name}' does not exist.")
|
|
print("You must compile the project before uploading it.")
|
|
confirm = questionary.confirm(
|
|
"Would you like to compile + upload the configuration now (Run)?",
|
|
default=True
|
|
).ask()
|
|
if confirm:
|
|
selected_cmd = "run"
|
|
else:
|
|
sys.exit(0)
|
|
|
|
# Prepare system environment
|
|
env = os.environ.copy()
|
|
|
|
# Execute ESPHome command
|
|
exit_code = run_esphome([selected_cmd, selected_yaml], env)
|
|
sys.exit(exit_code)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|