I do insane things with Christmas Lights, and I'd like to talk about how I used an event-based serverless architecture to implement my voting system.
xlights
But first we have to understand how the lights work. Firstly, I use xlights to import or make the song/light sequences for my house. It's the most amazing piece of open source software, and completely free.
Falcon Player (FPP)
I use Falcon Player to run my show. This is software running on a Raspberry Pi that I upload my songs and light sequences to from xlights. FPP is configured to know where each light is plugged into on which pixel controller (small lighting computer). These controllers sit on my network and FPP pushes the data to them to play the songs/light sequences.
Last year my christmas lights were a free-for-all, it used a timer (based on knowing how long each sequence ran for), and the very first person to tap on a sequence name on my website once the previous timer had finished, got to pick the next song that played. So I'd have 50 people out the front of my house all mashing their phones trying to 'win' selecting the next song. Myself included. It wasn't very fair. So I decided to build a voting system.
FPP Command Presets
FPP has one very important function which has enabled the ability to use an event-based architecture. It's a feature called "command presets", where it will run commands when certain events fire. I found one called SEQUENCE_STOPPED which runs whenever a sequence stops, whether it's me clicking stop or it just finishes playing a song, and the commands it can run includes shell scripts.
So I wrote a shell script called "songfinished.sh", and that's triggered whenever a song finishes.
No longer am I at the whim of a timer that may or may not be accurate, if a song finishes playing on my house by FPP, it immediately runs my script.
The contents of my script are simple, it's a single line. A POST to my API Gateway, which triggers an AWS Lambda function:
curl -X POST https://blah.execute-api.ap-southeast-2.amazonaws.com/songfinished
The AWS Lambda function being triggered means a song just finished playing on my house. There's no other possible way for it to be triggered. And so, it becomes the brains of my application.
My song finished AWS lambda function does the following:
- As a double-check, it ensures there's nothing in my status table that thinks it's playing on my house.
- It then ensures no sequences were triggered less than 5 seconds ago. This was due to a race condition I once had during development where things were being triggered multiple times in succession and causing infinite loops. It's not used anymore, but I'm happy to keep it in there just in case.
- It checks my "administrator" table. This is an override where I, and only I, am able to choose the next song that's played on my house, no matter what the users vote on. It leaves their votes in the voting table however, so they aren't lost, just delayed by a few minutes.
- It clears the voter IP table, allowing people to vote again due to the current song finishing.
- If there are votes, it tallies them, determines what has the most and triggers the next sequence on my house, and then clears the votes table.
- If there are no votes, it plays my background sequence.
As you can see, it's quite a busy function, but split into these smaller steps it wasn't too difficult to implement.
At the time of writing it's currently the 21st of December and my event based system is working flawlessly each night as hundreds of people visit my house.
Here's the current vote tally, with the theme to Bluey coming in at over 100 requests already!
Wait up... I mentioned I did this serverlessly.. which bit is serverless? well, all of it. I implemented it all using Serverless Stack and Svelte. Both are amazing pieces of technology to build a web app with, even for a newbie developer like me. Check out their tutorials, I literally built my entire app starting from this simple example.
Want to see the results in action? check out my dev:Ember video!
A final call to all my readers: is there a better way to do this? I'm always interested in learning about features or better ways to implement something. It's all part of my learning process! Please reach out on twitter or leave a comment here if you have any suggestions on how to improve my workflow!
Two things I'm already aware of that I'd like to improve:
- Auto expiring records
- Using step functions instead of a fat lambda function
Merry Christmas!
Nick
Top comments (0)