BRUH Playhouse
A cedar swing-set clubhouse in the back yard, with a control panel bolted into one wall that does something when a kid touches it. The panel is a printed box: five lids in the top face, a big bat-handle toggle, a strip of pixels down the middle, a speaker, and standard servos underneath with arms that reach up and flip the switch back. It is a useless box at playhouse scale, plus a separate ESPHome node for a fan.
Read this before you budget a weekend. The CAD in this folder is real and
measurable, and the parts list below comes out of it. The ESPHome configs are
not finished: between the three of them they assign exactly two pins. The fan
node is complete and will build as-is; bruh-playhouse.yaml is a shell — WiFi,
API, OTA, a status LED and one saved calibration value — with the servo, audio
and light blocks still to write. Everything the header comments in that file
name (DFPlayer over UART, PCA9685, addressable strip, joystick, encoder) is
intent, not configuration, so no pin numbers for those parts are published here.
Nobody is served by a confident wrong pin.
Parts
Some links below are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases — at no extra cost to you. It helps keep these guides free. Outside the US, they should send you to your own Amazon store; if one doesn't, searching the part number there will find it.
- ESP32 dev board, 38-pin ESP-WROOM-32
— every config in this folder is
board: esp32devon the Arduino framework, which is exactly this DevKitC-style board. Ships as a 2-pack, and you want two anyway: the fan is a second node. - MG996R standard servos, 6-pack — three bays in the box, so a 6-pack leaves spares for the ones you strip. The model’s file name says MG995 and the component inside it is an MG996R; they share the standard 40 × 20 × 40 mm case and the bays are cut 53.6 mm across the mounting tabs, so either drops in. Buy the MG996R — metal gears, and the arm is going to get slapped.
- PCA9685 16-channel servo driver, 2-pack — I²C, two pins for all three servos, and a screw terminal for servo power that is not the ESP32’s regulator. That last part is the whole reason it’s here.
- DFPlayer Mini, 4-pack
— serial MP3 playback off a microSD, with a 3 W amp already on the board.
These are YX5200 clones rather than DFRobot originals; ESPHome’s
dfplayercomponent drives them fine. - 32 GB microSD card — small on purpose. A DFPlayer reads FAT32, and a 256 GB card formatted exFAT out of the packet is a silent module and an afternoon gone.
- 2-inch 4 Ω 3 W speaker, 2-pack — the pocket in the CAD is 52.5 mm square and 22.9 mm deep with a 37.4 mm cone, which is a 2-inch driver. The DFPlayer drives it straight off SPK1/SPK2.
- SK6812 RGBW strip, 5 m, IP65 — two channels run down the centre of the box, 13 mm wide and about 352 mm long each. That width is a sleeved strip, not a bare 10 mm one, and IP65 is what you want in something a hose will eventually reach. It is RGBW, so the light block you write needs the RGBW chipset type and a real white channel to play with.
- Heavy-duty bat-handle toggle switch, IP65 — 30 × 17 mm in the panel with the lever standing 20 mm proud, which is what the model reserves. Get one with a rubber boot and not one with a flip guard: the guard is exactly where the servo arm needs to be.
- Four-position monolever joystick switch — spring-return, four normally-open contacts, industrial. It is the input a six-year-old can’t break, which is the specification that matters here.
- EC11 rotary encoders, 10-pack — bare panel-mount encoders with a push switch, for the knob.
- 22 mm mushroom-head momentary buttons, IP65 — big, sealed, and satisfying to hit. Buy more than you think.
- 5 V 60 A rainproof supply — far more than this build draws, and it is the 5 V chassis supply rated to live outdoors. Three MG996Rs stalling together is where a 2 A USB brick browns out the ESP32 and you spend an evening blaming the WiFi.
- Silicone wire, 20 AWG spool for the 5 V runs and 24 AWG in ten colours for signal, plus Dupont jumpers on the bench and JST-SM 3-pin pigtails at the strip.
- Lever nuts inside the box and solder-seal butt connectors for anything that leaves it — a crimp in a playhouse wall is a corroded crimp by next spring.
- IP65 hinged junction box for the ESP32 and the supply, and liquid-tight conduit for the run out to the house.
- M3 heat-set inserts and an M3 screw assortment — the lids come off more than once during calibration, so tapped plastic isn’t good enough. Short #8 wood screws hold the finished box to the cedar.
- PETG filament for everything printed, and see the material note below before you reach for PLA+ instead.
For the fan node
- A second ESP32 from the pack above, a MOSFET trigger module, 10-pack — the config puts an LEDC output on a GPIO at 1 kHz, which is PWM on the fan’s power, so it needs a low-side switch and not a 4-pin fan header — and a 12 V inline blower, which is the part that actually moves air through a closed cedar box in August. Add a buck converter to take the 12 V rail down to 5 V for the board.
And the playhouse
- Backyard Discovery Skyfort cedar swing set — the elevated clubhouse with the covered roof and bay windows. This is the structure the panel goes into. Obviously fit it to whatever you already have; it’s here because “outdoor playhouse” is doing a lot of work in the title.
The box
Every number below is measured off smart_useless_box_box_mg995_copy_v2.stl in
the download section.
The shell is 269 × 353 mm in plan and 100 mm deep, with a separate 260 × 344 × 3.5 mm base plate closing the bottom. The top face carries five lids, each 71.5 × 59.7 mm and 8 mm thick, in two columns — two on the left, three on the right, on a 60.7 mm pitch. The toggle sits in its own 30 × 17 mm opening at the servo end.
Inside: three servo bays at 53.6 mm across the tabs and 20 mm wide, one printed arm 44 mm long, a 52.5 mm square speaker pocket 22.9 mm deep, and two 13 mm channels 2.5 mm deep running almost the full 353 mm length for the pixel strips.
The STL is an assembly, not a print. It contains 24 bodies, and only about
half of them are plastic — the three servos, the speaker with its cone, the two
strips of LEDs and the toggle switch are all modelled in it. Slice that file and
you get a solid MG996R. Open the .f3d or the .step, export the shell, the
lids, the base plate and the arm, and print those.
While you’re in there, split the shell. At 269 × 353 mm it does not fit a Bambu A1’s 256 mm bed, and it does not come close to an Ender’s 235 mm. Either cut it into panels with a locating joint, or print the mechanism — bays, lids, arm, speaker pocket — and build the outer box out of ply to match the cedar.
Print settings
| Setting | Value |
|---|---|
| Layer height | 0.2 mm |
| Walls | 4 |
| Infill | 25% |
| Supports | None for the lids, base plate or arm; the shell needs them under the internal servo and speaker shelves |
| Material | PETG |
| Orientation | Lids and base plate flat; shell top-face-down |
Print it in PETG. This lives outside, in a wooden box, in the sun, and a PLA lid will bow out of its recess by the end of the first summer — the same summer the servo arm starts missing the switch because the geometry moved. PLA+ is fine for a bench prototype and nothing else here.
Four walls rather than the usual two is not fussiness. The lids get slammed and the arm gets grabbed, and the failure is always a wall splitting at a screw boss.
Wiring
The shipped configs assign two pins between them. This is all of it:
| Function | GPIO | Config | Notes |
|---|---|---|---|
| Fan PWM output | 14 | bruh-playhouse-fan.yaml | ledc output at 1000 Hz, driving fan.speed |
| Status LED | 2 | all three | The board’s own blue LED, claimed by status_led |
GPIO2 blinking is not a fault. status_led flashes it when WiFi or the API
connection is down and leaves it dark when everything is healthy, so you can
read a node’s health from across the yard without opening anything.
Everything else — the PCA9685’s I²C pair, the DFPlayer’s UART, the strip’s data
pin, the joystick and encoder inputs — is yours to assign and add to
bruh-playhouse.yaml. Pick pins that are not strapping pins, keep the DFPlayer
off the pins the ESP32 chatters on at boot, and write them into the file rather
than into a notebook.
Assembly
-
Melt the inserts first. Every M3 hole in the lids, the servo bays and the base plate gets a heat-set insert before a single wire goes in. You will take this apart more times than you expect while you are getting the arm sweep right.
-
Fit the servos and the arm dry, with no power anywhere, and swing the arm through its travel by hand. The arm has to clear the lid on the way up and land on the toggle’s bat handle square. If it catches the lid, fix it in CAD now — not with a percentage in the config later.
-
Mount the toggle with its rubber boot on, then re-check the arm. The boot adds a few millimetres and it is enough to change where the arm hits.
-
Drop the strips into the two centre channels and take their power and data out of the box on a JST-SM pigtail, so the lid assembly can come off without unsoldering anything.
-
Wire servo power to the PCA9685’s screw terminal, not to the ESP32. Share the ground. This is the single most common way this build fails: the board resets every time three servos start together, and it looks exactly like a flaky WiFi problem.
-
Calibrate before you close it — below — then screw the base plate on and mount the box to the wall with the short wood screws.
Calibration
bruh-playhouse.yaml carries one global, and it is the interesting line in the
file:
globals: - id: s_toggle_1_top_max_pct type: float restore_value: yes initial_value: '0.65'That is how far up the servo’s travel the arm is allowed to go — 65% — and
restore_value: yes means it survives a reboot, so you tune it once from Home
Assistant and it stays tuned. Every servo, arm and switch combination lands on a
different number, and 0.65 is one particular box’s.
Start low and creep up. An MG996R makes about 10 kg·cm and it does not know the switch has stopped moving; tell it to travel past the end of the bat handle and it will snap the printed arm, or the handle, and it will do it in under a second. Command a low percentage, watch where the arm stops, raise it a little, repeat. Stop at the first value that reliably throws the switch, not the value that throws it hardest.
The fan node
bruh-playhouse-fan.yaml is a separate device — its own name, its own
credentials, its own ESP32. It is also the only complete config in the folder:
an LEDC output on GPIO14 at 1 kHz, a fan.speed platform on top of it, and the
status LED. Flash it, and a speed-controllable fan appears in Home Assistant
with nothing else to write.
The 1 kHz matters. That frequency is PWM on the fan’s supply, through the MOSFET module — it is not the 25 kHz control signal a 4-pin PC fan expects on its blue wire. Wire a two-wire fan through the MOSFET’s output and drive the module’s trigger input from GPIO14.
bruh-playhouse-mini.yaml is a third node with nothing on it but WiFi, OTA and
the status LED. It is a starting point for a second panel, not a build.
Firmware
-
Copy the configs from the download section into your ESPHome folder.
-
Fill in
secrets.yaml—wifi_ssid,wifi_password,api_key,ota_password. There is asecrets.yaml.examplein the repo listing every key these files use. -
Change
nameandfriendly_nameat the top of each file so the nodes don’t collide. Note thatbruh-playhouse.yamlandbruh-playhouse-mini.yamlship with device names ofbruh-playgroundandbruh-playground-mini, which is what will show up on your network until you change them. -
Flash over USB the first time; everything after that is OTA, which is the entire point once the box is screwed to a wall eight feet up a ladder.
Troubleshooting
The board reboots whenever a servo moves. Servo power is coming from the ESP32. Move it to the PCA9685’s V+ terminal, fed from the 5 V supply, and tie the grounds together.
The DFPlayer plays nothing and reports nothing. Three things, in order: the
card is FAT32 and not exFAT; the files are named as four digits in an mp3
folder; and there is a 1 kΩ resistor in series with the module’s RX pin. That
last one is a DFPlayer quirk, not superstition — driven straight from a 3.3 V
TX it picks up enough noise to swallow commands.
One servo hums and gets hot at rest. It is being commanded past its mechanical stop. Lower that toggle’s percentage and check the arm clears the lid by hand with the power off.
The strip’s far end is the wrong colour. 5 V over 350 mm is fine, but two runs off one skinny pair of wires is not. Take 20 AWG to each run separately from the supply rather than daisy-chaining them.
It drops off WiFi at the bottom of the garden. Before you buy a repeater, turn the box. An ESP32’s PCB antenna is directional enough that 90 degrees of rotation is worth more than most of what you’d spend money on.
Files & downloads
Printable parts
ESPHome configuration s (3)
Copy this into your ESPHome directory and adjust the substitutions at the top.
Secrets are referenced by name — see secrets.yaml.example
.
bruh-playhouse.yaml
# BRUH Playhouse Controller# ────────────────────────────────────────────────────────────# Complex playhouse entertainment and control system with# servo-controlled features, DFPlayer audio, LED effects,# and multiple input controls (buttons, joystick, rotary encoder).# Features interactive animations and automated mechanisms.## Hardware:# - Board: ESP32# - Audio: DFPlayer Mini via UART# - Controls: Multiple buttons, joystick, rotary encoder# - Servos: PCA9685 servo controller# - LEDs: Addressable LED strips via GPIO# ────────────────────────────────────────────────────────────
esphome: name: bruh-playground friendly_name: "BRUH Playhouse"
esp32: board: esp32dev framework: type: arduino
logger:
api: encryption: key: !secret api_key
ota: platform: esphome password: !secret ota_password
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
captive_portal:
globals: - id: s_toggle_1_top_max_pct type: float restore_value: yes initial_value: '0.65'
status_led: pin: number: GPIO2 bruh-playhouse-fan.yaml
# BRUH Playhouse Fan Controller# ────────────────────────────────────────────────────────────# Fan speed controller for playhouse with variable speed control# via PWM and multi-click button interface.## Hardware:# - Board: ESP32# - Motor: DC fan with PWM speed control# ────────────────────────────────────────────────────────────
esphome: name: bruh-playhouse-fan friendly_name: "Playhouse Fan"
esp32: board: esp32dev framework: type: arduino
logger:
api: encryption: key: !secret api_key
ota: platform: esphome password: !secret ota_password
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
output: - platform: ledc pin: GPIO14 id: fan_motor frequency: "1000Hz"
fan: - platform: speed output: fan_motor name: "Playhouse Fan" id: playhouse_fan
status_led: pin: number: GPIO2 bruh-playhouse-mini.yaml
# BRUH Playhouse Mini Controller# ────────────────────────────────────────────────────────────# Simplified version of the full playhouse controller.# Focuses on core features with reduced complexity for# smaller installations or testing.## Hardware:# - Board: ESP32# ────────────────────────────────────────────────────────────
esphome: name: bruh-playground-mini friendly_name: "Playhouse Mini"
esp32: board: esp32dev framework: type: arduino
logger:
api: encryption: key: !secret api_key
ota: platform: esphome password: !secret ota_password
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
status_led: pin: number: GPIO2CAD source
The design itself, if you want to change it rather than print it. A STEP file is exact geometry and opens in almost anything; a Fusion 360 archive keeps the modelling history, so you can go back and edit the sketch that made the part.
- smart_useless_box_box_mg995_copy_v2.f3d Fusion 360 archive — the editable design, with its history
- smart_useless_box_box_mg995_copy_v2.step STEP — opens in Fusion 360, FreeCAD, Onshape, SolidWorks