Buckle up, this will be a long one!

The Struggle-

As part of this whole thing, I really wanted something fairly 'generic', yet good enough at what its supposed to with it's native hardware. Even this far in my building stuff career (professionally and personally), I still find the pull of 'do all of the things' to be incredibly strong. I can hear the echos in my ears of coworkers and colleagues past talking about edge cases, future-proofing, chance circumstances that bordered on acts of god... Every decision had these voices in the back of my head - is this a one-way decision, will I hate myself for this in the future. I think as time goes on, I more internalize the simple realization that in times or in projects where I fully let this pull take me out to sea and proceeded at a snail's pace covering all of the things, it consistently resulted in two things:

  1. I began to hate the project.
  2. I still hated myself for it later. .

Only now am I starting to really draw on this realization to make my projects things I can enjoy to the fullest. Does this mean that every project isn't improving my personal and professional development to the fullest? Probably. Am I happier though? Probably. So with this project especially, with all of the layers, integrations, tradeoffs and general avoidance of spending any more money while unemployed, I really tried to just let go of impressing my future self and the voices of old, and just build what makes me happy. Will that have negative consequences for the customers later? Probably.

I used to paint a lot. Oil paint, and all things considered I was half okay at it. But I had a thing I did that almost everyone close to me did not at all understand - I would always burn them. After finishing every painting, I would put it in my main room and generally let it dry, occasionally stopping in to take a look and just appreciate it as much as I could. At a certain point though near the beginning of painting, every rough edge and every imperfection started to get bigger. This began to almost paralyze me while painting, to the point where both the enjoyment of the activity and the quality of the outcome tanked. What was my solution? I decided that I would burn the paintings once they were done. Dramatic, I know, but the act of painting was so helpful and therapeutic to me that the outcome and its permanence didn't really matter. Of course over the years, with enough general feedback and support from people who loved me, not all paintings went to the burn heap, but there is still a part of me that harbors that tendency.

BYH isn't exactly a "burn after I'm done" thing - but at the same time, it's not an ode to engineering perfection and foresight. It's something in the middle. Something that gets me through the time I'm in and maybe might make some other people happy someday. So anyway. End therapy.

The Requirements

Core Requirements

First thing was that my system still needed to work with the BILUSOCN (generic chinese) system I had. Given I'd done the work of reverse engineering it (here in an article i need to write), I still wanted to make use of it and the ~15 cue boxes I still had there. But to really get some parity with more professional systems, I really needed to rally the system around my own native hardware and protocols to get things like:

  • Delegated on-device firing capable of synchronized, simultaneous fire within < 20ms.
  • Range, all of the range, but not excessive as backyards are only so big. Lets say 1000ft.
  • Heavy checking and safety things - as ultimately these are explosives.. expensive ones at that.

But really past that is where I wanted to differentiate this system, aiming more at people like me who just want to make and watch a pretty show and dont want to shell out $$$$ for the same system that can do rock concerts and monster truck rallies. Things like:

  • Low cost
  • A fully integrated, easy to use show creation and management system
  • A small set of highly modular hardware that can scale as the user sees fit, and allows me to make a few good types of hardware vs. a lot of types of shitty hardware.
  • DIY friendly, allowing the above to be delivered in varying degrees of finish to allow people to make the system into what fits their use case.

Stretch Requirements (Letting the voices in)

In addition to the above, within reason, I want to at least have the system not completely disqualify the idea of building and synchronizing shows to music. The real bind here is the vast majority of consumer fireworks aren't capable of the timing required to pull that off... So no matter how accurate the firing system is, the sloppy fusing and inconsistency will just muck it up.

The High Level Plan

Screenshot%202025-02-14%20at%2011.09.52%E2%80%AFPM So here it is. The big plan. The general idea is that there is always a laptop involved. I figure all the stuff running on the pi, and the RF frontend hardware are the real deliverables here. Whether the 'base' is in a case, or in a dongle is really something I can chase down later:

Screenshot%202025-02-14%20at%2011.36.28%E2%80%AFPM Screenshot%202025-02-14%20at%2011.36.53%E2%80%AFPM

I think largely, the idea is that you have either the dongle or host (lets just call it "the host" from here on in) with you and can fire it up in a passive state to run its webserver and build/view your shows. Out in the field, the UI should make setup easier, providing clear depictions of the devices and the cues on them that are being used.

Screenshot%202025-02-14%20at%2011.49.35%E2%80%AFPM

The Host thing

The host box, or dongle... oh that's right, we just call it 'the host' now... Pulls a lot of weight and consists of 2 main logical components with some supporting components:

Screenshot%202025-02-15%20at%2012.25.25%E2%80%AFAM

The client side React/NextJS is a Single-Pane-of-Glass-ish app that runs on the user's browser. The advantage here is the browser provides a more or less standardized (lets just ignore old IE) modern runtime for a UI. The days of passing Java JARs or any thing similar thought to be "platform agnostic" are long behind us.... The websocket server is written in python and exists entirely to stream realtime system state data to the app. The Daemon is also written in python and is responsible for managing the actual runtime of the entire system. There are some sub-components within it (such as the ProtocolHandler class that adapts common runtime concepts like load, start, stop, pause, fire etc. to a given protocol).

But hey lets go into some of the components

Host components

The App

The client side app is served from the Next.js app, and is your run of the mill, insanely cluttered app. It has a single page and does not route any other pages. For maintaining state, it uses a few different Zustand stores. The app is responsible for the frontends to setup inventory, configure the system, build shows and play shows. The app talks bidirectionally with the Next.js server for CRUD, and listens to the websocket server for streaming system state updates.

The Next.js server

The Next.js server is tightly coupled to the app, and provides both static content for it as well as API endpoints for inventory/show CRUD and commanding the daemon. The server uses a SQLite instance for persisting inventory and shows.

The websocket server

The websocket server is maybe a 200 line python script that reads a single JSON file that is continuously updated by the daemon. This is a simple and effective solution vs some kind of socket/TCP/whatever thing. Typically it will stream an update to the app every second. It supplies pretty much every meaningful system state parameter/measure in its payload.

The daemon

The daemon is more or less the beating heart of the system. It maintains a connection to the TX/RX module and, given the signature of that module will assign a protocol handler. It then receives commands from the app via. a mechanism where the app sends commands as single .json files in a given directory, and the daemon continuously checks this directory for command files and runs them (deleting when successful). It gets updates from the receivers from the TX/RX module and via. it's protocol handler translates those into the native states and actions necessary. When a show is loaded, it will query the SQLite instance to pull down and parse the show to load into the system.

Example Flow: Loading and Starting a show

Screenshot%202025-02-15%20at%2012.09.17%E2%80%AFAM

One little idea

Really, this needs to be run on some reasonable processing power. At full tilt, it takes about 25-30% of the resources of a pi5. Per the cheap charter, I really want to find a way to avoid more hardware like this (pis are 60-70bucks alone, and need active cooling here). So one thing I'll look into is containerizing the host - that way the only thing folks need to install on a laptop is docker (easy). This offloads any processing power a dongle needs and makes it viable to power off just the USB 5v bus.

I should explore this a bit more - the most disruptive detail is I either need to remove the 'physical button' parts of the system, or add that responsibility to the TX/RX module... which seems like a violation of concerns, but in the interest of frugality maybe it's justified.

what about the receivers and TX/RX module?

I cover those elsewhere ...link?...