Files
2026-03-09 01:03:57 +08:00

129 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""
Push state/commands to a WLED device.
Accepts a JSON file or simple flags (on, off, bri, preset).
Uses only standard library (no pip install).
"""
import argparse
import json
import sys
from typing import Optional
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
DEFAULT_HOST = "192.168.240.30"
def push(host: str, payload: dict, return_state: bool = False) -> Optional[dict]:
base = host if host.startswith("http") else f"http://{host}"
url = f"{base}/json/state"
if return_state:
payload["v"] = True
body = json.dumps(payload).encode("utf-8")
req = Request(
url,
data=body,
method="POST",
headers={"Content-Type": "application/json", "Accept": "application/json"},
)
try:
with urlopen(req, timeout=10) as r:
raw = r.read().decode()
if not raw:
return None
return json.loads(raw)
except HTTPError as e:
print(f"HTTP error: {e.code} {e.reason}", file=sys.stderr)
if e.fp:
print(e.fp.read().decode(), file=sys.stderr)
sys.exit(1)
except URLError as e:
print(f"Request failed: {e.reason}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Invalid response JSON: {e}", file=sys.stderr)
sys.exit(1)
def main():
p = argparse.ArgumentParser(description="Push state/commands to WLED")
p.add_argument("--host", "-H", default=DEFAULT_HOST, help=f"WLED host (default: {DEFAULT_HOST})")
p.add_argument("--file", "-f", metavar="JSON", help="JSON file with state to send")
p.add_argument("--on", action="store_true", help="Turn on")
p.add_argument("--off", action="store_true", help="Turn off")
p.add_argument("--toggle", "-t", action="store_true", dest="toggle", help="Toggle on/off")
p.add_argument("--bri", type=int, metavar="0-255", help="Global brightness")
p.add_argument("--seg-bri", type=int, metavar="0-255", dest="seg_bri", help="Segment 0 brightness")
p.add_argument("--white", "-w", type=int, metavar="0-255", help="Segment 0 white channel (col[0][3], RGBW)")
p.add_argument("--cct", type=int, metavar="0-255", help="Color temp: 0=warm, 255=cool white (segment 0)")
p.add_argument("--preset", "-p", type=int, metavar="ID", help="Load preset by ID")
p.add_argument("--v", action="store_true", help="Return full state in response")
args = p.parse_args()
if args.file:
with open(args.file) as f:
payload = json.load(f)
else:
payload = {}
if args.off:
payload["on"] = False
if args.on:
payload["on"] = True
if args.toggle:
payload["on"] = "t"
if args.bri is not None:
payload["bri"] = max(0, min(255, args.bri))
if args.seg_bri is not None:
seg_bri = max(0, min(255, args.seg_bri))
payload.setdefault("seg", [])
seg0 = next((s for s in payload["seg"] if s.get("id") == 0), payload["seg"][0] if payload["seg"] else None)
if seg0 is None:
payload["seg"].insert(0, {"id": 0, "bri": seg_bri})
else:
seg0["bri"] = seg_bri
if args.white is not None:
w = max(0, min(255, args.white))
payload.setdefault("seg", [])
seg0 = next((s for s in payload["seg"] if s.get("id") == 0), payload["seg"][0] if payload["seg"] else None)
if seg0 is None:
payload["seg"].insert(0, {"id": 0, "fx": 0, "col": [[0, 0, 0, w], [0, 0, 0, 0], [0, 0, 0, 0]]})
else:
seg0.setdefault("col", [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
if len(seg0["col"]) < 1:
seg0["col"] = [[0, 0, 0, w], [0, 0, 0, 0], [0, 0, 0, 0]]
else:
prim = list(seg0["col"][0])
while len(prim) < 4:
prim.append(0)
prim[3] = w
seg0["col"][0] = prim
seg0["fx"] = 0 # Solid so white channel is visible
if args.cct is not None:
cct = max(0, min(255, args.cct))
payload.setdefault("seg", [])
seg0 = next((s for s in payload["seg"] if s.get("id") == 0), payload["seg"][0] if payload["seg"] else None)
if seg0 is None:
payload["seg"].insert(0, {"id": 0, "fx": 0, "cct": cct})
else:
seg0["cct"] = cct
seg0["fx"] = 0 # Solid so CCT is visible
if args.preset is not None:
payload["ps"] = args.preset
if args.v:
payload["v"] = True
if not payload:
print("Nothing to send. Use --file, --on, --off, --toggle, --bri, or --preset.", file=sys.stderr)
sys.exit(1)
out = push(args.host, payload, return_state=args.v)
if out:
print(json.dumps(out, indent=2))
if __name__ == "__main__":
main()