#!/usr/bin/env python3 """ CLI to send JSON over SPI to the ESP32-C3 SPI slave. Examples: ./send_json.py --data '{"settings":{"pattern":"on","brightness":128}}' ./send_json.py --file payload.json ./send_json.py --brightness 180 --delay 30 --pattern wave """ import argparse import json import os import sys # Ensure we can import the local test helper SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) if SCRIPT_DIR not in sys.path: sys.path.insert(0, SCRIPT_DIR) from spi_master_test import SPIMasterTest # noqa: E402 def build_payload_from_args(args: argparse.Namespace) -> dict: """Build a control payload matching led-bar's receiver format from args. Format: {"settings": { ... }, "save": false} Or raw passthrough if --data/--file is provided. """ if args.data: try: return json.loads(args.data) except Exception as exc: print(f"Invalid JSON in --data: {exc}") sys.exit(2) if args.file: try: with open(args.file, "r", encoding="utf-8") as f: return json.load(f) except Exception as exc: print(f"Failed to read JSON file {args.file}: {exc}") sys.exit(2) # Build led-bar settings object settings: dict = {} if args.pattern: settings["pattern"] = args.pattern if args.brightness is not None: settings["brightness"] = int(args.brightness) if args.delay is not None: settings["delay"] = int(args.delay) # Optional numeric params if supported on led-bar side if args.n1 is not None: settings["n1"] = int(args.n1) if args.n2 is not None: settings["n2"] = int(args.n2) if args.n3 is not None: settings["n3"] = int(args.n3) payload: dict = {"settings": settings} # Optional: step tick control if args.step is not None: payload["step"] = int(args.step) # Allow override raw NAME JSON into settings directly if provided if args.name_value: try: name, value = args.name_value override = json.loads(value) # Merge override fields into settings if not isinstance(override, dict): raise ValueError("--name-value JSON must be an object") settings.update(override) except Exception as exc: print(f"Invalid --name-value: {exc}") sys.exit(2) # Default save=false; caller can include save in --data if desired payload.setdefault("save", False) if not settings and "step" not in payload: print("No settings specified to send.", file=sys.stderr) return {"settings": {}, "save": False} return payload def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser(description="Send JSON over SPI to ESP32-C3 (led-bar format)") src = p.add_mutually_exclusive_group() src.add_argument("--data", help="Raw JSON string to send (passthrough)") src.add_argument("--file", help="Path to JSON file to send (passthrough)") p.add_argument("--brightness", type=int, help="Brightness") p.add_argument("--delay", type=int, help="Delay (ms)") p.add_argument("--pattern", help="Pattern name") p.add_argument("--n1", type=int, help="n1 parameter") p.add_argument("--n2", type=int, help="n2 parameter") p.add_argument("--n3", type=int, help="n3 parameter") p.add_argument("--step", type=int, help="Pattern step override") p.add_argument("--name-value", nargs=2, metavar=("NAME", "JSON"), help="Merge JSON into settings (shortcut)") # SPI params p.add_argument("--bus", type=int, default=0, help="SPI bus (default 0)") p.add_argument("--device", type=int, default=0, help="SPI device/CE (default 0)") p.add_argument("--speed", type=int, default=1_000_000, help="SPI speed Hz (default 1MHz)") return p.parse_args() def main() -> int: args = parse_args() payload = build_payload_from_args(args) spi = SPIMasterTest(bus=args.bus, device=args.device, max_speed_hz=args.speed) try: spi.send_json(payload) finally: spi.cleanup() return 0 if __name__ == "__main__": raise SystemExit(main())