tools: leds: add m4-led-perf-test.sh
Bash/sh script (BusyBox compatible) to stress-test the WAGO M4 RGB LED
strip driver by activating kernel LED triggers on all ten strip LEDs
(sys, run, io, em, u1-u6).
Three trigger modes selectable via --trigger=<mode>:
pattern (default)
Phase 1 - FADE: staggered smooth brightness ramp (200 ms
offset per LED) using the pattern trigger
Phase 2 - FAST FLASH: 50 ms on/off in distinct colours per LED
Phase 3 - SLOW FLASH: 400 ms on/off with colour cycling
timer
Simple on/off blink with staggered delay_on/delay_off values
(50 ms .. 500 ms) and per-LED colours.
heartbeat
Kernel heartbeat trigger, all LEDs white.
Additional options:
--duration=<sec> Total test duration in seconds (default: 30)
--version Print version string
--help Print usage
Cleanup handler (trap INT/TERM) restores trigger=none + brightness=0
on all LEDs when the test ends or is interrupted.
Signed-off-by: Heinrich Toews <ht@twx-software.de>
This commit is contained in:
Executable
+384
@@ -0,0 +1,384 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# m4-led-perf-test.sh - Performance / stress test for WAGO M4 RGB LED strip
|
||||
#
|
||||
# Exercises all ten M4 strip LEDs (sys, run, io, em, u1-u6) by activating
|
||||
# kernel LED triggers at various frequencies and patterns. Designed to run
|
||||
# on a BusyBox-based target (ash/sh compatible, no bashisms).
|
||||
#
|
||||
# Triggers supported:
|
||||
# pattern - (default) three phases: fade, flash-fast, flash-slow
|
||||
# timer - simple on/off blink at staggered frequencies per LED
|
||||
# heartbeat - kernel heartbeat trigger (frequency fixed by kernel)
|
||||
#
|
||||
# Usage:
|
||||
# m4-led-perf-test.sh [--trigger=<pattern|timer|heartbeat>]
|
||||
# [--duration=<seconds>]
|
||||
# [--help] [--version]
|
||||
#
|
||||
# Author: WAGO GmbH & Co. KG
|
||||
|
||||
VERSION="1.0.0"
|
||||
LED_SYSFS="/sys/class/leds"
|
||||
|
||||
# All M4 strip LED names (must match DT 'label' properties)
|
||||
LEDS="sys run io em u1 u2 u3 u4 u5 u6"
|
||||
|
||||
# Default options
|
||||
OPT_TRIGGER="pattern"
|
||||
OPT_DURATION=30
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
log() { echo "[ m4-led-test ] $*"; }
|
||||
info() { log "INFO $*"; }
|
||||
warn() { log "WARN $*"; }
|
||||
err() { log "ERROR $*" >&2; }
|
||||
die() { err "$*"; cleanup; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Usage / version
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Performance / stress test for the WAGO M4 RGB LED strip.
|
||||
Activates kernel LED triggers on all ten strip LEDs at various
|
||||
frequencies to stress the RPMsg / M4 communication path.
|
||||
|
||||
Options:
|
||||
--trigger=<mode> Trigger mode to use (default: pattern)
|
||||
pattern - three phases: fade in/out, fast flash,
|
||||
slow flash with colour cycling
|
||||
timer - simple on/off blink at staggered
|
||||
frequencies (10 ms .. 500 ms)
|
||||
heartbeat - kernel heartbeat trigger
|
||||
--duration=<sec> How long to run the test in seconds (default: 30)
|
||||
--version Print version and exit
|
||||
--help Print this help and exit
|
||||
|
||||
Examples:
|
||||
$(basename "$0")
|
||||
$(basename "$0") --trigger=timer --duration=60
|
||||
$(basename "$0") --trigger=heartbeat
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argument parsing (BusyBox-compatible, no getopt)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--version)
|
||||
echo "$(basename "$0") version $VERSION"
|
||||
exit 0
|
||||
;;
|
||||
--trigger=*)
|
||||
OPT_TRIGGER="${arg#--trigger=}"
|
||||
;;
|
||||
--duration=*)
|
||||
OPT_DURATION="${arg#--duration=}"
|
||||
;;
|
||||
*)
|
||||
err "Unknown option: $arg"
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate trigger
|
||||
case "$OPT_TRIGGER" in
|
||||
pattern|timer|heartbeat) ;;
|
||||
*) die "Invalid trigger '$OPT_TRIGGER'. Choose: pattern, timer, heartbeat" ;;
|
||||
esac
|
||||
|
||||
# Validate duration (must be a positive integer)
|
||||
case "$OPT_DURATION" in
|
||||
''|*[!0-9]*) die "Invalid duration '$OPT_DURATION': must be a positive integer" ;;
|
||||
esac
|
||||
[ "$OPT_DURATION" -gt 0 ] || die "Duration must be > 0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LED sysfs helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# led_path <name> -> /sys/class/leds/<name>
|
||||
led_path() { echo "${LED_SYSFS}/$1"; }
|
||||
|
||||
# led_write <name> <file> <value>
|
||||
led_write() {
|
||||
local path
|
||||
path="$(led_path "$1")/$2"
|
||||
if [ ! -w "$path" ]; then
|
||||
warn "Not writable: $path — skipping"
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$3" > "$path" 2>/dev/null || warn "Write failed: $path <- $3"
|
||||
}
|
||||
|
||||
# led_set_trigger <name> <trigger>
|
||||
led_set_trigger() { led_write "$1" trigger "$2"; }
|
||||
|
||||
# led_set_brightness <name> <value 0-255>
|
||||
led_set_brightness() { led_write "$1" brightness "$2"; }
|
||||
|
||||
# led_set_multi_intensity <name> <R> <G> <B>
|
||||
led_set_multi_intensity() { led_write "$1" multi_intensity "$2 $3 $4"; }
|
||||
|
||||
# led_set_pattern <name> <pattern-string>
|
||||
led_set_pattern() { led_write "$1" pattern "$2"; }
|
||||
|
||||
# Check which LEDs are actually present in sysfs
|
||||
check_leds() {
|
||||
local found=0 missing=0 name
|
||||
for name in $LEDS; do
|
||||
if [ -d "$(led_path "$name")" ]; then
|
||||
found=$((found + 1))
|
||||
else
|
||||
warn "LED '$name' not found in $LED_SYSFS — skipping"
|
||||
missing=$((missing + 1))
|
||||
fi
|
||||
done
|
||||
info "Found $found / $(echo $LEDS | wc -w) expected LEDs"
|
||||
[ "$found" -gt 0 ] || die "No M4 LEDs found in $LED_SYSFS. Is the driver loaded?"
|
||||
}
|
||||
|
||||
# Return only the LEDs that exist in sysfs
|
||||
present_leds() {
|
||||
local name
|
||||
for name in $LEDS; do
|
||||
[ -d "$(led_path "$name")" ] && printf '%s ' "$name"
|
||||
done
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cleanup: restore all LEDs to 'none' trigger and brightness=0
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
cleanup() {
|
||||
info "Restoring all LEDs (trigger=none, brightness=0) ..."
|
||||
local name
|
||||
for name in $(present_leds); do
|
||||
led_set_trigger "$name" "none"
|
||||
led_set_brightness "$name" 0
|
||||
done
|
||||
info "Cleanup done."
|
||||
}
|
||||
|
||||
# Trap SIGINT / SIGTERM so Ctrl-C always restores LEDs
|
||||
trap 'info "Interrupted — cleaning up ..."; cleanup; exit 130' INT TERM
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Trigger implementations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# --- HEARTBEAT --------------------------------------------------------------
|
||||
|
||||
run_heartbeat() {
|
||||
info "Setting trigger=heartbeat on all LEDs ..."
|
||||
local name
|
||||
for name in $(present_leds); do
|
||||
# White at full brightness
|
||||
led_set_multi_intensity "$name" 255 255 255
|
||||
led_set_brightness "$name" 255
|
||||
led_set_trigger "$name" heartbeat
|
||||
info " $name -> heartbeat"
|
||||
done
|
||||
}
|
||||
|
||||
# --- TIMER ------------------------------------------------------------------
|
||||
#
|
||||
# Stagger delay_on / delay_off across LEDs to create a "running" effect.
|
||||
# Pairs (on_ms, off_ms) cycle through a range from fast (50/50) to slow
|
||||
# (500/500) and back.
|
||||
|
||||
run_timer() {
|
||||
info "Setting trigger=timer on all LEDs with staggered frequencies ..."
|
||||
|
||||
# Pre-defined (on_ms off_ms R G B) tuples — one per LED
|
||||
# Colours: red, green, blue, cyan, magenta, yellow, white, orange, lime, teal
|
||||
set -- \
|
||||
"50 50 255 0 0" \
|
||||
"100 100 0 255 0" \
|
||||
"200 200 0 0 255" \
|
||||
"300 300 0 255 255" \
|
||||
"400 400 255 0 255" \
|
||||
"500 500 255 255 0" \
|
||||
"150 350 255 255 255" \
|
||||
"80 420 255 128 0" \
|
||||
"250 250 128 255 0" \
|
||||
"350 150 0 128 128"
|
||||
|
||||
local name idx on off r g b
|
||||
idx=1
|
||||
for name in $(present_leds); do
|
||||
# Extract the idx-th tuple from positional params
|
||||
eval "tuple=\$$idx"
|
||||
on=$(echo "$tuple" | awk '{print $1}')
|
||||
off=$(echo "$tuple" | awk '{print $2}')
|
||||
r=$(echo "$tuple" | awk '{print $3}')
|
||||
g=$(echo "$tuple" | awk '{print $4}')
|
||||
b=$(echo "$tuple" | awk '{print $5}')
|
||||
|
||||
led_set_multi_intensity "$name" "$r" "$g" "$b"
|
||||
led_set_brightness "$name" 255
|
||||
led_set_trigger "$name" timer
|
||||
led_write "$name" delay_on "$on"
|
||||
led_write "$name" delay_off "$off"
|
||||
|
||||
info " $name -> timer on=${on}ms off=${off}ms RGB($r,$g,$b)"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# --- PATTERN ----------------------------------------------------------------
|
||||
#
|
||||
# Three phases, each lasting 1/3 of the total duration:
|
||||
#
|
||||
# Phase 1 — FADE: smooth brightness ramp up/down (pattern trigger)
|
||||
# Phase 2 — FAST FLASH: rapid on/off (50 ms) in different colours per LED
|
||||
# Phase 3 — SLOW FLASH: slow pulse (400 ms) with colour cycling
|
||||
#
|
||||
# Pattern format: "<brightness> <duration_ms> <brightness> <duration_ms> ..."
|
||||
# brightness 0-255, duration in ms. The pattern trigger loops the sequence.
|
||||
|
||||
# Smooth fade: 0->255 in steps then 255->0, total ~2 s per cycle
|
||||
FADE_PATTERN="0 100 32 100 64 100 96 100 128 100 160 100 192 100 224 100"
|
||||
FADE_PATTERN+=" 255 100 224 100 192 100 160 100 128 100 96 100 64 100 32 100"
|
||||
|
||||
# Fast flash: full on 50 ms, off 50 ms
|
||||
FLASH_FAST_PATTERN="255 50 0 50"
|
||||
|
||||
# Slow flash: full on 400 ms, off 400 ms
|
||||
FLASH_SLOW_PATTERN="255 400 0 400"
|
||||
|
||||
run_pattern_phase() {
|
||||
local phase_name="$1"
|
||||
local pattern="$2"
|
||||
shift 2
|
||||
# Remaining args: "name R G B" tuples
|
||||
info " Phase: $phase_name"
|
||||
local name r g b
|
||||
for name in $(present_leds); do
|
||||
# Consume next R G B from positional args
|
||||
r="$1"; g="$2"; b="$3"; shift 3 2>/dev/null || true
|
||||
led_set_trigger "$name" none
|
||||
led_set_multi_intensity "$name" "$r" "$g" "$b"
|
||||
led_set_brightness "$name" 255
|
||||
led_set_trigger "$name" pattern
|
||||
led_set_pattern "$name" "$pattern"
|
||||
info " $name -> pattern='$phase_name' RGB($r,$g,$b)"
|
||||
done
|
||||
}
|
||||
|
||||
run_pattern() {
|
||||
local phase_dur total_leds phase_sleep
|
||||
total_leds=$(present_leds | wc -w)
|
||||
phase_dur=$(( OPT_DURATION / 3 ))
|
||||
[ "$phase_dur" -lt 2 ] && phase_dur=2
|
||||
|
||||
info "Pattern test: 3 phases x ~${phase_dur}s (${total_leds} LEDs)"
|
||||
|
||||
# --- Phase 1: FADE (all white, staggered start via offset pattern) ------
|
||||
# Give each LED a slightly rotated version of the fade pattern so they
|
||||
# don't all pulse in sync — we pre-build per-LED patterns with an initial
|
||||
# offset silence of (index * 200 ms).
|
||||
info "--- Phase 1/3: FADE ---"
|
||||
local idx=0 name offset_pattern prefix
|
||||
for name in $(present_leds); do
|
||||
offset_pattern=""
|
||||
if [ "$idx" -gt 0 ]; then
|
||||
# Insert a leading "0 <offset_ms>" step to stagger each LED
|
||||
prefix=$((idx * 200))
|
||||
offset_pattern="0 ${prefix} "
|
||||
fi
|
||||
led_set_trigger "$name" none
|
||||
led_set_multi_intensity "$name" 255 255 255
|
||||
led_set_brightness "$name" 255
|
||||
led_set_trigger "$name" pattern
|
||||
led_set_pattern "$name" "${offset_pattern}${FADE_PATTERN}"
|
||||
info " $name -> fade (offset ${idx}x200ms)"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
sleep "$phase_dur"
|
||||
|
||||
# --- Phase 2: FAST FLASH — different colour per LED --------------------
|
||||
info "--- Phase 2/3: FAST FLASH ---"
|
||||
run_pattern_phase "fast-flash" "$FLASH_FAST_PATTERN" \
|
||||
255 0 0 \
|
||||
0 255 0 \
|
||||
0 0 255 \
|
||||
255 255 0 \
|
||||
0 255 255 \
|
||||
255 0 255 \
|
||||
255 128 0 \
|
||||
128 0 255 \
|
||||
0 255 128 \
|
||||
255 255 255
|
||||
sleep "$phase_dur"
|
||||
|
||||
# --- Phase 3: SLOW FLASH — colour cycling (shift colours between LEDs) --
|
||||
info "--- Phase 3/3: SLOW FLASH ---"
|
||||
run_pattern_phase "slow-flash" "$FLASH_SLOW_PATTERN" \
|
||||
0 255 255 \
|
||||
255 0 255 \
|
||||
255 255 0 \
|
||||
0 0 255 \
|
||||
0 255 0 \
|
||||
255 0 0 \
|
||||
128 255 128 \
|
||||
255 128 128 \
|
||||
128 128 255 \
|
||||
200 200 200
|
||||
# Let the last phase run for the remainder of the duration
|
||||
local elapsed=$(( phase_dur * 2 ))
|
||||
local remaining=$(( OPT_DURATION - elapsed ))
|
||||
[ "$remaining" -lt 1 ] && remaining=1
|
||||
sleep "$remaining"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
info "============================================"
|
||||
info " WAGO M4 LED Performance Test v${VERSION}"
|
||||
info "============================================"
|
||||
info "Trigger : $OPT_TRIGGER"
|
||||
info "Duration : ${OPT_DURATION}s"
|
||||
info "LED sysfs: $LED_SYSFS"
|
||||
info "--------------------------------------------"
|
||||
|
||||
check_leds
|
||||
|
||||
case "$OPT_TRIGGER" in
|
||||
pattern)
|
||||
run_pattern
|
||||
;;
|
||||
timer)
|
||||
run_timer
|
||||
info "Running for ${OPT_DURATION}s ..."
|
||||
sleep "$OPT_DURATION"
|
||||
;;
|
||||
heartbeat)
|
||||
run_heartbeat
|
||||
info "Running for ${OPT_DURATION}s ..."
|
||||
sleep "$OPT_DURATION"
|
||||
;;
|
||||
esac
|
||||
|
||||
cleanup
|
||||
info "Test complete."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user