Skip to content

Commit 731f140

Browse files
authored
Merge pull request wled#4625 from LordMike/patch-1
Create wled-tools
2 parents d7d1e92 + 88aa8e8 commit 731f140

File tree

1 file changed

+286
-0
lines changed

1 file changed

+286
-0
lines changed

tools/wled-tools

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#!/bin/bash
2+
3+
# WLED Tools
4+
# A utility for managing WLED devices in a local network
5+
# https://github.com/wled/WLED
6+
7+
# Color Definitions
8+
GREEN="\e[32m"
9+
RED="\e[31m"
10+
BLUE="\e[34m"
11+
YELLOW="\e[33m"
12+
RESET="\e[0m"
13+
14+
# Logging function
15+
log() {
16+
local category="$1"
17+
local color="$2"
18+
local text="$3"
19+
20+
if [ "$quiet" = true ]; then
21+
return
22+
fi
23+
24+
if [ -t 1 ]; then # Check if output is a terminal
25+
echo -e "${color}[${category}]${RESET} ${text}"
26+
else
27+
echo "[${category}] ${text}"
28+
fi
29+
}
30+
31+
# Generic curl handler function
32+
curl_handler() {
33+
local command="$1"
34+
local hostname="$2"
35+
36+
response=$($command -w "%{http_code}" -o /dev/null)
37+
curl_exit_code=$?
38+
39+
if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then
40+
return 0
41+
elif [ $curl_exit_code -ne 0 ]; then
42+
log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)."
43+
return 1
44+
elif [ "$response" -ge 400 ]; then
45+
log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)."
46+
return 2
47+
else
48+
log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)."
49+
return 3
50+
fi
51+
}
52+
53+
# Print help message
54+
show_help() {
55+
cat << EOF
56+
Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...]
57+
58+
Options:
59+
-h, --help Show this help message and exit.
60+
-t, --target <IP/Host> Specify a single WLED device by IP address or hostname.
61+
-D, --discover Discover multiple WLED devices using mDNS.
62+
-d, --directory <Path> Specify a directory for saving backups (default: working directory).
63+
-f, --firmware <File> Specify the firmware file for updating devices.
64+
-q, --quiet Suppress logging output (also makes discover output hostnames only).
65+
66+
Commands:
67+
backup Backup the current state of a WLED device or multiple discovered devices.
68+
update Update the firmware of a WLED device or multiple discovered devices.
69+
discover Discover WLED devices using mDNS and list their IP addresses and names.
70+
71+
Examples:
72+
# Discover all WLED devices on the network
73+
./wled-tools discover
74+
75+
# Backup a specific WLED device
76+
./wled-tools -t 192.168.1.100 backup
77+
78+
# Backup all discovered WLED devices to a specific directory
79+
./wled-tools -D -d /path/to/backups backup
80+
81+
# Update firmware on all discovered WLED devices
82+
./wled-tools -D -f /path/to/firmware.bin update
83+
84+
EOF
85+
}
86+
87+
# Discover devices using mDNS
88+
discover_devices() {
89+
if ! command -v avahi-browse &> /dev/null; then
90+
log "ERROR" "$RED" "'avahi-browse' is required but not installed, please install avahi-utils using your preferred package manager."
91+
exit 1
92+
fi
93+
94+
# Map avahi responses to strings seperated by 0x1F (unit separator)
95+
mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}')
96+
97+
local devices_array=()
98+
for device in "${raw_devices[@]}"; do
99+
IFS=$'\x1F' read -r hostname address port <<< "$device"
100+
devices_array+=("$hostname" "$address" "$port")
101+
done
102+
103+
echo "${devices_array[@]}"
104+
}
105+
106+
# Backup one device
107+
backup_one() {
108+
local hostname="$1"
109+
local address="$2"
110+
local port="$3"
111+
112+
log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)"
113+
114+
mkdir -p "$backup_dir"
115+
116+
local cfg_url="http://$address:$port/cfg.json"
117+
local presets_url="http://$address:$port/presets.json"
118+
local cfg_dest="${backup_dir}/${hostname}.cfg.json"
119+
local presets_dest="${backup_dir}/${hostname}.presets.json"
120+
121+
# Write to ".tmp" files first, then move when success, to ensure we don't write partial files
122+
local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp""
123+
local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp""
124+
125+
if ! curl_handler "$curl_command_cfg" "$hostname"; then
126+
log "ERROR" "$RED" "Failed to backup configuration for $hostname"
127+
rm -f "$cfg_dest.tmp"
128+
return 1
129+
fi
130+
131+
if ! curl_handler "$curl_command_presets" "$hostname"; then
132+
log "ERROR" "$RED" "Failed to backup presets for $hostname"
133+
rm -f "$presets_dest.tmp"
134+
return 1
135+
fi
136+
137+
mv "$cfg_dest.tmp" "$cfg_dest"
138+
mv "$presets_dest.tmp" "$presets_dest"
139+
log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname"
140+
return 0
141+
}
142+
143+
# Update one device
144+
update_one() {
145+
local hostname="$1"
146+
local address="$2"
147+
local port="$3"
148+
local firmware="$4"
149+
150+
log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)"
151+
152+
local url="http://$address:$port/update"
153+
local curl_command="curl -s -X POST -F "file=@$firmware" "$url""
154+
155+
if ! curl_handler "$curl_command" "$hostname"; then
156+
log "ERROR" "$RED" "Failed to update firmware for $hostname"
157+
return 1
158+
fi
159+
160+
log "INFO" "$GREEN" "Successfully initiated firmware update for $hostname"
161+
return 0
162+
}
163+
164+
# Command-line arguments processing
165+
command=""
166+
target=""
167+
discover=false
168+
quiet=false
169+
backup_dir="./"
170+
firmware_file=""
171+
172+
if [ $# -eq 0 ]; then
173+
show_help
174+
exit 0
175+
fi
176+
177+
while [[ $# -gt 0 ]]; do
178+
case "$1" in
179+
-h|--help)
180+
show_help
181+
exit 0
182+
;;
183+
-t|--target)
184+
if [ -z "$2" ] || [[ "$2" == -* ]]; then
185+
log "ERROR" "$RED" "The --target option requires an argument."
186+
exit 1
187+
fi
188+
target="$2"
189+
shift 2
190+
;;
191+
-D|--discover)
192+
discover=true
193+
shift
194+
;;
195+
-d|--directory)
196+
if [ -z "$2" ] || [[ "$2" == -* ]]; then
197+
log "ERROR" "$RED" "The --directory option requires an argument."
198+
exit 1
199+
fi
200+
backup_dir="$2"
201+
shift 2
202+
;;
203+
-f|--firmware)
204+
if [ -z "$2" ] || [[ "$2" == -* ]]; then
205+
log "ERROR" "$RED" "The --firmware option requires an argument."
206+
exit 1
207+
fi
208+
firmware_file="$2"
209+
shift 2
210+
;;
211+
-q|--quiet)
212+
quiet=true
213+
shift
214+
;;
215+
backup|update|discover)
216+
command="$1"
217+
shift
218+
;;
219+
*)
220+
log "ERROR" "$RED" "Unknown argument: $1"
221+
exit 1
222+
;;
223+
esac
224+
done
225+
226+
# Execute the appropriate command
227+
case "$command" in
228+
discover)
229+
read -ra devices <<< "$(discover_devices)"
230+
for ((i=0; i<${#devices[@]}; i+=3)); do
231+
hostname="${devices[$i]}"
232+
address="${devices[$i+1]}"
233+
port="${devices[$i+2]}"
234+
235+
if [ "$quiet" = true ]; then
236+
echo "$hostname"
237+
else
238+
log "INFO" "$BLUE" "Discovered device: Hostname=$hostname, Address=$address, Port=$port"
239+
fi
240+
done
241+
;;
242+
backup)
243+
if [ -n "$target" ]; then
244+
# Assume target is both the hostname and address, with port 80
245+
backup_one "$target" "$target" "80"
246+
elif [ "$discover" = true ]; then
247+
read -ra devices <<< "$(discover_devices)"
248+
for ((i=0; i<${#devices[@]}; i+=3)); do
249+
hostname="${devices[$i]}"
250+
address="${devices[$i+1]}"
251+
port="${devices[$i+2]}"
252+
backup_one "$hostname" "$address" "$port"
253+
done
254+
else
255+
log "ERROR" "$RED" "No target specified. Use --target or --discover."
256+
exit 1
257+
fi
258+
;;
259+
update)
260+
# Validate firmware before proceeding
261+
if [ -z "$firmware_file" ] || [ ! -f "$firmware_file" ]; then
262+
log "ERROR" "$RED" "Please provide a file in --firmware that exists"
263+
exit 1
264+
fi
265+
266+
if [ -n "$target" ]; then
267+
# Assume target is both the hostname and address, with port 80
268+
update_one "$target" "$target" "80" "$firmware_file"
269+
elif [ "$discover" = true ]; then
270+
read -ra devices <<< "$(discover_devices)"
271+
for ((i=0; i<${#devices[@]}; i+=3)); do
272+
hostname="${devices[$i]}"
273+
address="${devices[$i+1]}"
274+
port="${devices[$i+2]}"
275+
update_one "$hostname" "$address" "$port" "$firmware_file"
276+
done
277+
else
278+
log "ERROR" "$RED" "No target specified. Use --target or --discover."
279+
exit 1
280+
fi
281+
;;
282+
*)
283+
show_help
284+
exit 1
285+
;;
286+
esac

0 commit comments

Comments
 (0)