A project to create a show controller system using RGB LED light strips controlled by a Raspberry Pi. The Pi acts as the show controller with the Java LED Agent. Then, there is a React Frontend Interface for playing/synchronizing music, and a Java "Sync Server" to enable communication between the Pi and all clients.
The LED Agent is a Java program that controls addressable WS2812B RGB LED light strips according to provided show files, using the https://github.com/jgarff/rpi_ws281x Java library. This Agent runs as a system service and listens for instructions from the Sync Server to start and stop shows.
The Frontend Interface is a React app to allow multiple local devices to start/stop shows and listen to the show music at the same time in (near) perfect synchronization. It communicates with the LED Agent through the Sync Server.
The Sync Server is a Java WSS using Netty to facilitate communication between the LED Agent and multiple Frontend Interfaces.
- Raspberry PIs don't have a built-in RTC clock, which computers use to keep track of time that passes when the computer isn't running. The PI uses the
systemd-timesyncdservice to sync with time servers, but this doesn't finish running until 20-30 seconds after startup (NTP calculations can take some time). To compensate, the systemd service file I used for the LEDAgent (included below) specifiesAfter = time-sync.targetandWants = time-sync.target. This means theLEDAgent.servicewon't run untilsystemd-time-wait-syncfinishes running, which is a service specifically built to only run oncesystemd-timesyncdfinishes. Yes, it's complicated.- Without waiting for
systemd-timesyncd, the LEDAgent will start up and get a certainsyncServerTimeOffsetvalue (mine was consistently around -10000ms). Then, whensystemd-timesyncdfinishes running, the clock will change, meaning the previoussyncServerTimeOffsetis now wrong. - The only solution for this is to either (a) restart
LEDAgent.serviceafter the clock is accurate, or (b) wait to startLEDAgent.serviceuntil the clock is accurate.
- Without waiting for
[Unit]
Description = Java LEDAgent
After = network.target time-sync.target
Wants = time-sync.target
[Service]
Type = forking
ExecStart = /usr/local/bin/LEDAgent.sh start
ExecStop = /usr/local/bin/LEDAgent.sh stop
ExecReload = /usr/local/bin/LEDAgent.sh reload
[Install]
WantedBy=multi-user.target
#!/bin/sh
SERVICE_NAME=LEDAgent
PATH_TO_JAR=/home/pi/LEDAgent.jar
PID_PATH_NAME=/tmp/LEDAgent-pid
case $1 in
start)
echo "Starting $SERVICE_NAME ..."
if [ ! -f $PID_PATH_NAME ]; then
nohup java -jar $PATH_TO_JAR >> /var/log/LEDAgent.log 2>&1&
echo $! > $PID_PATH_NAME
echo "$SERVICE_NAME started ..."
else
echo "$SERVICE_NAME is already running ..."
fi
;;
stop)
if [ -f $PID_PATH_NAME ]; then
PID=$(cat $PID_PATH_NAME);
echo "$SERVICE_NAME stoping ..."
kill $PID;
echo "$SERVICE_NAME stopped ..."
rm $PID_PATH_NAME
else
echo "$SERVICE_NAME is not running ..."
fi
;;
restart)
if [ -f $PID_PATH_NAME ]; then
PID=$(cat $PID_PATH_NAME);
echo "$SERVICE_NAME stopping ...";
kill $PID;
echo "$SERVICE_NAME stopped ...";
rm $PID_PATH_NAME
echo "$SERVICE_NAME starting ..."
nohup java -jar $PATH_TO_JAR >> /var/log/LEDAgent.log 2>&1&
echo $! > $PID_PATH_NAME
echo "$SERVICE_NAME started ..."
else
echo "$SERVICE_NAME is not running ..."
fi
;;
esac