This is the second article about custom additions I've made for my Prusa MK3S+ 3D printer. The first article is Prusa Enclosure.
IMHO the physical controls on the printer aren't great: besides the hard reset button, it provides only a single click wheel to navigate a big on-screen menu tree. When I built the enclosure I added four pushbuttons to provide direct access to certain functions, but they didn't work very well. Physically, the way I chose to solder them together wasn't great, so they had operational failures. But also, I just didn't like the feel of the buttons that much. I used 16mm illuminated pushbuttons from Adafruit, and they're fine, but they have a higher actuation force and longer travel than I wanted, giving them a squishy feel.
When I discovered Adafruit's NeoKey 1x4 QT I2C, I realized that would provide a much nicer "clicky" feel, so I decided to build something around them, and it worked out great. Here's how the finished project looks:
I used transparent keycaps with custom decal legends and a 3D-printed (of course!) enclosure that mounted onto the wire shelf next to the printer. It controls four functions:
- Lights — the strings of white LEDs in the custom enclosure
- Heating — the nozzle and bed
- Stop (interrupt a print) and home (move the head up & center it)
- Unload filament
Construction
Attached to the aforementioned keyboard PCB are Kailh-style white keyswitches, with translucent keycaps to help provide a good view of the RGB LEDs underneath. The keyboard is connected to a tiny Adafruit QT Py S3 ESP32 dev board.
The keycaps don't have any legends, so I used Koala Waterslide Decal Paper to create custom legends. Apparently most decal paper is designed for inkjet printers, but this kind is specifically safe for laser printers, and it worked great. It's the same stuff you use to put little details on plastic models of cars & planes & stuff, but with your laser printer you can make any image you want.
The case is printed in black PLA, but it's shown here with the front pieces in light beige and the back pieces in dark gray.
I need a way to hold the tiny keyboard and ESP32 PCBs, and instead of trying to use tiny screws that might not have much purchase in plastic, I designed the case as kind of a sandwich. Here's a cross-section, including the keyswitches and one keycap for reference:
The keyboard PCB presses against the top part of the case, then there are a couple of small plastic "inserts" that sit underneath it. The ESP32 sits underneath one of them, and then the bottom of the case sits below that. The top and bottom of the case are held together by M3 hex bolts and heat-set inserts, creating the friction that holds the PCBs in-place.
I modeled a portion of the wire shelf, so I could shape part of the case to mate with it. More M3 hex bolts help it grab onto the shelf and hold firmly.
Software
I interfaced this to the Raspberry Pi running the OctoPrint in a novel way. Initially I thought I'd connect it directly via USB, and write firmware for the ESP32 that presented itself as a keyboard. But that would require dealing with the text console on the Raspberry Pi, so I decided it might be less trouble to use the ESP32 WiFi instead. So the ESP32's USB-C port is used only for power, and the firmware advertises itself on the network. My OctoPi is running a plugin that looks for that device via mDNS (Bonjour/Zeroconf), and communicates with it using a custom line-oriented JSON protocol. The keyboard sends key-down and key-up events to the host (the OctoPi plugin) and the host sends commands to change the colors of the LEDs of each key.
I was a little concerned that the latency would be too high sometimes to make the keyboard feel responsive, because when I press a key, I want the keyboard LEDs (and the sound effects from the OctoPi) to feel immediate. Luckily, it almost always responds that quickly... it feels like one device, not two devices connected by a WiFi network.
Implementing the desired functions in the OctoPi plugin was fairly straightforward, except for filament unloading. The Prusa firmware doesn't send commands to the host when the filament sensor turns on or off, and without that there's no way to accurately update the color of the unload button. I wanted it to glow green when there was filament that could be unloaded, and dark when the extruder is empty. Luckily, Claude Opus has no difficulty downloading the Prusa firmware source code and figuring out how to add the additional gcode events, which the plugin can then listen for and translate into network commands to change the LED color.
Questions or comments? Email me at .
2026 Oct 24