Files
home_assistant/wled_push.py
T
tomatocream 3a40741096 Add README, sunrise alarm, CCT (white/warm) support and configs
- README: WLED pull/push usage, sunrise alarm, white vs warm (CCT) section
- wled_push: --cct 0-255 for segment 0 color temperature
- wled_config: white.json, warm.json, sunrise presets; updated state/full

Made-with: Cursor
2026-03-09 00:50:35 +08:00

102 lines
3.4 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="Brightness")
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.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()