a build log
Darklands
Control System
A whole remote-control setup that lets people at Darklands drive my wearable gear from their phone: LEDs, collars, and a few things that bite back.
Open the live control panel fen.bio/dl/control · drive it yourselfthe idea
Say nothing, do something
Darklands is huge, and I am shy, so I wanted a way for people to interact with me without either of us having to say a word. The answer was a control panel on my website. Anyone can open it, pick one of my wearable devices, and set it off from across the room: change the color of my harness, scroll a message on my collar, play a GIF on my pendant, or send a shock to something a lot more personal.
It is one small web page sitting on top of a chain of servers, radios and microcontrollers, and this is how the whole thing hangs together.
the plumbing
How it is wired up
A rough map of the system, still being drawn up properly.
the gear
The devices
Six things you can drive from the panel, from harmless and pretty to distinctly not.
RGB matrix harness. Solid colors, rainbow waves, scrolling text, pulse and fire effects.
A small LED display pendant that plays looping GIFs (hypno spiral, fire, paw print, and more).
A scrolling-text collar. Type up to 64 characters and it runs across the front.
Shock, vibration and beep, with adjustable intensity and duration.
Electroshock plus vibration, with intensity, patterns and duration.
Shock-enabled cage. Pick an intensity and send a zap.
build notes
How it actually holds together
The command flow
Every button on the panel does the same basic thing: it posts a small JSON command to the API, which drops it on a queue. My home server is constantly polling that queue, and the moment a command shows up it figures out which transport that device uses and fires it off. The LED gear listens over WiFi, the shock gear listens over radio. The panel then flips its status dot to green if it hears back.
Why a home server in the middle
The website cannot talk to a microcontroller or a Flipper Zero directly, and I did not want to expose any of that hardware to the open internet. So the website only ever writes to a queue, and a trusted little server I control does the actual talking. If my phone loses signal at the venue, the queue just backs up harmlessly instead of anything misbehaving.
Safety and consent
The spicy devices all cap their intensity and duration in firmware, not just in the interface, so a maxed-out slider still stays inside a range I set. Everything is opt in, it is my own gear on my own body, and if the connection drops you can always just come find me and use my phone instead.
a look at the code
From a tap to a zap, in four hops
// fen.bio API · queue a command for a device
app.post('/api/devices/:id', async (req, reply) => {
const { id } = req.params; // 'harness' | 'shock' | 'cage' | ...
const cmd = req.body; // { action, intensity, duration, ... }
if (!ALLOWED.has(id)) return reply.code(404).send();
await queue.push({ device: id, cmd, at: Date.now() });
return { ok: true, queued: true };
}); // home server (runs in my apartment) · pull + route
setInterval(async () => {
const jobs = await api('/api/devices/pending'); // grab what's waiting
for (const job of jobs) {
switch (transportFor(job.device)) {
case 'esp32': sockets[job.device].send(JSON.stringify(job.cmd)); break;
case 'subghz': flipper.tx(signalFor(job.device, job.cmd)); break;
}
}
}, 500); // poll twice a second // ESP32 firmware (harness / pendulum / collar) · receive over WebSocket
void onMessage(uint8_t *payload) {
StaticJsonDocument<256> doc;
deserializeJson(doc, payload);
const char *mode = doc["mode"]; // "color" | "text" | "rainbow"
if (!strcmp(mode, "color")) setAll(doc["r"], doc["g"], doc["b"]);
else if (!strcmp(mode, "text")) scrollText(doc["text"]);
else if (!strcmp(mode, "rainbow")) rainbowWave();
FastLED.show();
} # Sub-GHz replay for the RF shock devices
# each button on the original remote was captured to its own .sub file
flipper subghz tx shock_collar_zap.sub # 433 MHz, decoded from the remote
flipper subghz tx cage_zap.sub
flipper subghz tx plug_vibe_pulse.sub These are simplified for the writeup. The real thing has a lot more error handling, reconnection logic, and duct tape.
take it home
Get the files
Everything to build your own: the server code, the ESP32 firmware, the captured RF signals, wiring notes, and the 3D models for the harness, pendant and mounts.
files and models coming soon, poke me if you want them now