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
This commit is contained in:
2026-03-08 00:52:20 +08:00
commit 3812dbc8ad
5 changed files with 751 additions and 0 deletions
+427
View File
@@ -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": [
"<button class=\"btn btn-xs\" onclick=\"requestJson({AudioReactive:{enabled:false}});\"><i class=\"icons on\">&#xe08f;</i></button>"
],
"GEQ Input Level": [
"<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({AudioReactive:{inputLevel:parseInt(this.value)}});\" oninput=\"updateTrail(this);\" max=255 min=0 type=\"range\" value=128 /><div class=\"sliderdisplay\"></div></div></div>"
],
"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"
]
}
+86
View File
@@ -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": [
"<button class=\"btn btn-xs\" onclick=\"requestJson({AudioReactive:{enabled:false}});\"><i class=\"icons on\">&#xe08f;</i></button>"
],
"GEQ Input Level": [
"<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({AudioReactive:{inputLevel:parseInt(this.value)}});\" oninput=\"updateTrail(this);\" max=255 min=0 type=\"range\" value=128 /><div class=\"sliderdisplay\"></div></div></div>"
],
"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"
}
+77
View File
@@ -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
}
]
}
+70
View File
@@ -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()
+91
View File
@@ -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()