From 3812dbc8ade1325ea2b6a74880a1e7c59beac739 Mon Sep 17 00:00:00 2001 From: Wong Ding Feng Date: Sun, 8 Mar 2026 00:52:20 +0800 Subject: [PATCH] Add WLED pull/push scripts and config backup - wled_pull.py: fetch state, info, cfg, effects, palettes from WLED - wled_push.py: send commands or JSON state to WLED - wled_config/: saved full.json, state.json, info.json from 192.168.240.30 Made-with: Cursor --- wled_config/full.json | 427 +++++++++++++++++++++++++++++++++++++++++ wled_config/info.json | 86 +++++++++ wled_config/state.json | 77 ++++++++ wled_pull.py | 70 +++++++ wled_push.py | 91 +++++++++ 5 files changed, 751 insertions(+) create mode 100644 wled_config/full.json create mode 100644 wled_config/info.json create mode 100644 wled_config/state.json create mode 100644 wled_pull.py create mode 100644 wled_push.py diff --git a/wled_config/full.json b/wled_config/full.json new file mode 100644 index 0000000..ea15fe3 --- /dev/null +++ b/wled_config/full.json @@ -0,0 +1,427 @@ +{ + "state": { + "on": true, + "bri": 254, + "transition": 7, + "ps": -1, + "pl": -1, + "ledmap": 0, + "AudioReactive": { + "on": true + }, + "nl": { + "on": false, + "dur": 60, + "mode": 1, + "tbri": 0, + "rem": -1 + }, + "udpn": { + "send": false, + "recv": true, + "sgrp": 1, + "rgrp": 1 + }, + "lor": 0, + "mainseg": 0, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 1, + "len": 1, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 0, + "set": 0, + "col": [ + [ + 56, + 56, + 56, + 56 + ], + [ + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + } + ] + }, + "info": { + "ver": "0.15.1", + "vid": 2507300, + "cn": "K\u014dsen", + "release": "ESP32", + "leds": { + "count": 1, + "pwr": 120, + "fps": 2, + "maxpwr": 20000, + "maxseg": 32, + "bootps": 0, + "seglc": [ + 6 + ], + "lc": 6, + "rgbw": false, + "wv": 2, + "cct": 4 + }, + "str": false, + "name": "WLED-Gledopto", + "udpport": 21324, + "simplifiedui": false, + "live": false, + "liveseg": -1, + "lm": "", + "lip": "", + "ws": 1, + "fxcount": 187, + "palcount": 71, + "cpalcount": 0, + "maps": [ + { + "id": 0 + } + ], + "wifi": { + "bssid": "A4:A9:30:F9:4D:96", + "rssi": -49, + "signal": 100, + "channel": 1, + "ap": false + }, + "fs": { + "u": 8, + "t": 983, + "pmt": 1772891797 + }, + "ndc": 0, + "arch": "esp32", + "core": "v3.3.6-16-gcc5440f6a2", + "clock": 240, + "flash": 4, + "lwip": 0, + "freeheap": 169496, + "uptime": 10830, + "time": "2026-3-7, 16:52:59", + "u": { + "AudioReactive": [ + "" + ], + "GEQ Input Level": [ + "
" + ], + "Audio Source": [ + "I2S digital", + " - peak 99%" + ], + "Sound Processing": [ + "running" + ], + "AGC Gain": [ + 8.46, + "x" + ], + "UDP Sound Sync": [ + "off" + ] + }, + "opt": 79, + "brand": "WLED", + "product": "FOSS", + "mac": "b0cbd8e1a8c8", + "ip": "192.168.240.30" + }, + "effects": [ + "Solid", + "Blink", + "Breathe", + "Wipe", + "Wipe Random", + "Random Colors", + "Sweep", + "Dynamic", + "Colorloop", + "Rainbow", + "Scan", + "Scan Dual", + "Fade", + "Theater", + "Theater Rainbow", + "Running", + "Saw", + "Twinkle", + "Dissolve", + "Dissolve Rnd", + "Sparkle", + "Sparkle Dark", + "Sparkle+", + "Strobe", + "Strobe Rainbow", + "Strobe Mega", + "Blink Rainbow", + "Android", + "Chase", + "Chase Random", + "Chase Rainbow", + "Chase Flash", + "Chase Flash Rnd", + "Rainbow Runner", + "Colorful", + "Traffic Light", + "Sweep Random", + "Chase 2", + "Aurora", + "Stream", + "Scanner", + "Lighthouse", + "Fireworks", + "Rain", + "Tetrix", + "Fire Flicker", + "Gradient", + "Loading", + "Rolling Balls", + "Fairy", + "Two Dots", + "Fairytwinkle", + "Running Dual", + "RSVD", + "Chase 3", + "Tri Wipe", + "Tri Fade", + "Lightning", + "ICU", + "Multi Comet", + "Scanner Dual", + "Stream 2", + "Oscillate", + "Pride 2015", + "Juggle", + "Palette", + "Fire 2012", + "Colorwaves", + "Bpm", + "Fill Noise", + "Noise 1", + "Noise 2", + "Noise 3", + "Noise 4", + "Colortwinkles", + "Lake", + "Meteor", + "Meteor Smooth", + "Railway", + "Ripple", + "Twinklefox", + "Twinklecat", + "Halloween Eyes", + "Solid Pattern", + "Solid Pattern Tri", + "Spots", + "Spots Fade", + "Glitter", + "Candle", + "Fireworks Starburst", + "Fireworks 1D", + "Bouncing Balls", + "Sinelon", + "Sinelon Dual", + "Sinelon Rainbow", + "Popcorn", + "Drip", + "Plasma", + "Percent", + "Ripple Rainbow", + "Heartbeat", + "Pacifica", + "Candle Multi", + "Solid Glitter", + "Sunrise", + "Phased", + "Twinkleup", + "Noise Pal", + "Sine", + "Phased Noise", + "Flow", + "Chunchun", + "Dancing Shadows", + "Washing Machine", + "Rotozoomer", + "Blends", + "TV Simulator", + "Dynamic Smooth", + "Spaceships", + "Crazy Bees", + "Ghost Rider", + "Blobs", + "Scrolling Text", + "Drift Rose", + "Distortion Waves", + "Soap", + "Octopus", + "Waving Cell", + "Pixels", + "Pixelwave", + "Juggles", + "Matripix", + "Gravimeter", + "Plasmoid", + "Puddles", + "Midnoise", + "Noisemeter", + "Freqwave", + "Freqmatrix", + "GEQ", + "Waterfall", + "Freqpixels", + "RSVD", + "Noisefire", + "Puddlepeak", + "Noisemove", + "Noise2D", + "Perlin Move", + "Ripple Peak", + "Firenoise", + "Squared Swirl", + "RSVD", + "DNA", + "Matrix", + "Metaballs", + "Freqmap", + "Gravcenter", + "Gravcentric", + "Gravfreq", + "DJ Light", + "Funky Plank", + "RSVD", + "Pulser", + "Blurz", + "Drift", + "Waverly", + "Sun Radiation", + "Colored Bursts", + "Julia", + "RSVD", + "RSVD", + "RSVD", + "Game Of Life", + "Tartan", + "Polar Lights", + "Swirl", + "Lissajous", + "Frizzles", + "Plasma Ball", + "Flow Stripe", + "Hiphotic", + "Sindots", + "DNA Spiral", + "Black Hole", + "Wavesins", + "Rocktaves", + "Akemi" + ], + "palettes": [ + "Default", + "* Random Cycle", + "* Color 1", + "* Colors 1&2", + "* Color Gradient", + "* Colors Only", + "Party", + "Cloud", + "Lava", + "Ocean", + "Forest", + "Rainbow", + "Rainbow Bands", + "Sunset", + "Rivendell", + "Breeze", + "Red & Blue", + "Yellowout", + "Analogous", + "Splash", + "Pastel", + "Sunset 2", + "Beach", + "Vintage", + "Departure", + "Landscape", + "Beech", + "Sherbet", + "Hult", + "Hult 64", + "Drywet", + "Jul", + "Grintage", + "Rewhi", + "Tertiary", + "Fire", + "Icefire", + "Cyane", + "Light Pink", + "Autumn", + "Magenta", + "Magred", + "Yelmag", + "Yelblu", + "Orange & Teal", + "Tiamat", + "April Night", + "Orangery", + "C9", + "Sakura", + "Aurora", + "Atlantica", + "C9 2", + "C9 New", + "Temperature", + "Aurora 2", + "Retro Clown", + "Candy", + "Toxy Reaf", + "Fairy Reaf", + "Semi Blue", + "Pink Candy", + "Red Reaf", + "Aqua Flash", + "Yelblu Hot", + "Lite Light", + "Red Flash", + "Blink Red", + "Red Shift", + "Red Tide", + "Candy2" + ] +} \ No newline at end of file diff --git a/wled_config/info.json b/wled_config/info.json new file mode 100644 index 0000000..9c4c688 --- /dev/null +++ b/wled_config/info.json @@ -0,0 +1,86 @@ +{ + "ver": "0.15.1", + "vid": 2507300, + "cn": "K\u014dsen", + "release": "ESP32", + "leds": { + "count": 1, + "pwr": 120, + "fps": 2, + "maxpwr": 20000, + "maxseg": 32, + "bootps": 0, + "seglc": [ + 6 + ], + "lc": 6, + "rgbw": false, + "wv": 2, + "cct": 4 + }, + "str": false, + "name": "WLED-Gledopto", + "udpport": 21324, + "simplifiedui": false, + "live": false, + "liveseg": -1, + "lm": "", + "lip": "", + "ws": 1, + "fxcount": 187, + "palcount": 71, + "cpalcount": 0, + "maps": [ + { + "id": 0 + } + ], + "wifi": { + "bssid": "A4:A9:30:F9:4D:96", + "rssi": -50, + "signal": 100, + "channel": 1, + "ap": false + }, + "fs": { + "u": 8, + "t": 983, + "pmt": 1772891797 + }, + "ndc": 0, + "arch": "esp32", + "core": "v3.3.6-16-gcc5440f6a2", + "clock": 240, + "flash": 4, + "lwip": 0, + "freeheap": 169488, + "uptime": 10831, + "time": "2026-3-7, 16:53:00", + "u": { + "AudioReactive": [ + "" + ], + "GEQ Input Level": [ + "
" + ], + "Audio Source": [ + "I2S digital", + " - peak 99%" + ], + "Sound Processing": [ + "running" + ], + "AGC Gain": [ + 8.46, + "x" + ], + "UDP Sound Sync": [ + "off" + ] + }, + "opt": 79, + "brand": "WLED", + "product": "FOSS", + "mac": "b0cbd8e1a8c8", + "ip": "192.168.240.30" +} \ No newline at end of file diff --git a/wled_config/state.json b/wled_config/state.json new file mode 100644 index 0000000..4383b12 --- /dev/null +++ b/wled_config/state.json @@ -0,0 +1,77 @@ +{ + "on": true, + "bri": 254, + "transition": 7, + "ps": -1, + "pl": -1, + "ledmap": 0, + "AudioReactive": { + "on": true + }, + "nl": { + "on": false, + "dur": 60, + "mode": 1, + "tbri": 0, + "rem": -1 + }, + "udpn": { + "send": false, + "recv": true, + "sgrp": 1, + "rgrp": 1 + }, + "lor": 0, + "mainseg": 0, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 1, + "len": 1, + "grp": 1, + "spc": 0, + "of": 0, + "on": true, + "frz": false, + "bri": 255, + "cct": 0, + "set": 0, + "col": [ + [ + 56, + 56, + 56, + 56 + ], + [ + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0 + ] + ], + "fx": 0, + "sx": 128, + "ix": 128, + "pal": 0, + "c1": 128, + "c2": 128, + "c3": 16, + "sel": true, + "rev": false, + "mi": false, + "o1": false, + "o2": false, + "o3": false, + "si": 0, + "m12": 0 + } + ] +} \ No newline at end of file diff --git a/wled_pull.py b/wled_pull.py new file mode 100644 index 0000000..d280b4c --- /dev/null +++ b/wled_pull.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Pull state, config, or info from a WLED device. +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" +ENDPOINTS = ("state", "info", "cfg", "eff", "pal", "full") + + +def pull(host: str, endpoint: str, save_path: Optional[str], indent: Optional[int]) -> dict: + base = host if host.startswith("http") else f"http://{host}" + url = f"{base}/json" if endpoint == "full" else f"{base}/json/{endpoint}" + + req = Request(url, headers={"Accept": "application/json"}) + try: + with urlopen(req, timeout=10) as r: + data = json.loads(r.read().decode()) + 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 JSON: {e}", file=sys.stderr) + sys.exit(1) + + if save_path: + with open(save_path, "w") as f: + json.dump(data, f, indent=indent or 2) + print(f"Saved to {save_path}") + else: + print(json.dumps(data, indent=indent or 2)) + + return data + + +def main(): + p = argparse.ArgumentParser(description="Pull state/config from WLED") + p.add_argument("--host", "-H", default=DEFAULT_HOST, help=f"WLED host (default: {DEFAULT_HOST})") + p.add_argument( + "endpoint", + nargs="?", + default="full", + choices=ENDPOINTS, + help="state, info, cfg, eff, pal, or full (default: full)", + ) + p.add_argument("--save", "-o", metavar="FILE", help="Write JSON to file") + p.add_argument("--no-indent", action="store_true", help="Compact JSON output") + args = p.parse_args() + + pull( + args.host, + args.endpoint, + args.save, + None if args.no_indent else 2, + ) + + +if __name__ == "__main__": + main() diff --git a/wled_push.py b/wled_push.py new file mode 100644 index 0000000..e58ad6e --- /dev/null +++ b/wled_push.py @@ -0,0 +1,91 @@ +#!/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("--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.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()