Files
lighting-controller/test/send_json.py

131 lines
4.1 KiB
Python
Executable File

#!/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())