Overview
Modern electronics are capable of incredible things. Even the simplest gadgets now have clocks, gyroscopes, radios, GPS, touch screens, literally anything you can imagine, built right in. Sometimes though, the devices that are capable of doing several things don't do all of them particularly well. Touch screens are often bolted onto things that really don't benefit from them, or even worse, detract from the experience. Anyone who has accidentally grazed the touch panel on an Apple TV remote while grasping for it in the dark knows exactly what I'm talking about. Sometimes I just want to have a remote with a few simple buttons I can press to make things happen, and that's it!
Most of the lights in my apartment are WiFi-connected, and offer MQTT capability. I took advantage of this and set up a local MQTT broker (https://mosquitto.org/) on a Raspberry Pi 4 wired to an old Apple Airport router/hub I had lying around (my internet provider makes me use their router for my internet). Now, it's easier than ever to control my lights...from a browser. Or some phone interface. Or an app. Wait a second...each of those still requires multiple steps just to get me to the point where I can actually control anything. And I probably still have to use a touch screen. Leading me back to my original point, and thus the motivation for the imaginatively named:
WiFi Matrix Keypad Remote
I've been a DIY remote control enthusiast for almost as long as I've been hacking around with microcontrollers (at least a few years now). One thing that I've found really hard to source as a hobbyist is good, pre-made button panels intended to be mounted into a small space, such as a remote control. So far, one of my favorite sources for a dense panel of decent quality, inexpensive buttons are these old-school 3x4 matrix keypads:
Using only 7 GPIO they pack 12 separate buttons into just a few square inches. Combine that with a tiny WiFi microcontroller and rechargeable battery, and you've got a pretty small and simple (but effective) WiFi remote. These keypads do have a tradeoff: their matrix circuit does not include diodes, so you can't mash every key at once without "ghosting" effects. Most people don't use remote controls that way though, and I've found that this hasn't been an issue in practice.
One other piece of simple-but-effective technology used in this project is the humble tilt-ball switch. It may be crude and a little noisy, but when properly positioned and paired with the ESP32-S2's excellent deep sleep capabilities we can build a remote that will go to sleep when set down, and wake up automatically when picked up again. Handy! Since the majority of the time remotes are just lying around, this also means that depending on usage patterns, it can go anywhere from a few days to a few weeks on a charge with a small 500 mAh battery.
You have two choices for the NeoPixel: you can either use a breadboard NeoPixel, or a through-hole NeoPixel. The through-hole NeoPixel is already nicely diffused and pretty much ready to go, if you like that old-school LED aesthetic. For the breadboard NeoPixel, the case was designed to provide about 1cm of space between the NeoPixel PCB and the top surface of the remote, allowing a generous amount of hot glue to be applied into the diffusion cavity to give the NeoPixel a nice soft glow. Several variations of the top part of the 3D-printed remote case have been provided with different shapes for the cutout, allowing you to choose between a circle, a triangle, a diamond, a heart, or a star shape for the diffuser. The through-hole NeoPixel is mounted into place with hot glue, while the breadboard NeoPixel is held in place by a piece of "backstop" geometry on the case's end panel.
NOTE: Nearly every connection in this project requires soldering, so this guide is intended for makers of intermediate skill level or higher. Additionally, an intermediate level of Arduino/C++ knowledge or higher may be necessary to use the supplied sample code.
Hardware
Required materials for assembly
- 4x M2 x 8mm screws
- 4x M2 hex nuts
- 4x M2.3 x 5mm self tapping screws
- Heat shrink tubing
Tools
- Flush cutters
- Wire stripper
- Soldering iron and solder
- Small Phillip's head screwdriver
- Hot glue gun and hot glue
- Hot air gun for heat shrink tubing
Circuit Diagram
The diagram below provides a general visual reference for wiring of the components once you get to the Assembly page. This diagram was created using the software package Fritzing. Note that we will be using all available GPIO pads on the QT Py, so there will be a good bit of soldering involved.
Wired connections
- The following pins are soldered together between the QT Py and the LiPo BFF:
- BFF 5V to QT Py 5V
- BFF GND to QT Py GND
- BFF A2 to QT Py A2. This will provide us with a basic battery voltage monitor using a voltage divider.
- The QT Py is powered by a LiPo battery plugged into the LiPo BFF via the JST battery port, and the battery is charged by providing power to the QT Py's USB-C port.
- The matrix keypad's 3 column and 4 row pins are soldered to the QT Py like so:
- Keypad C2 to QT Py A0
- Keypad R1 to QT Py A1
- Keypad C1 to QT Py A3
- Keypad R4 to QT Py SDA
- Keypad C3 to QT Py SCL
- Keypad R3 to QT Py TX
- Keypad R2 to QT Py SCK
- The two leads from the tilt-ball switch are soldered to the QT Py's GND and RX pins.
- The following pins are soldered together between the QT Py and the breadboard NeoPixel:
- NeoPixel GND to QT Py GND
- NeoPixel IN to QT Py MI
- NeoPixel + to QT Py MO. This allows us to power the NeoPixel on/off using a GPIO. It'll only be 3 volts instead of 5, but we don't need our NeoPixel to be very bright anyway, so that should be fine.
- Since there is only 1 GND pin on the QT Py, and 3 components that need to share it, wires are soldered into each component's GND pin and will all be spliced together in the Assembly step.
Breadboard
Below is the breadboard circuit I built for testing, for an additional wiring reference. Note that I am using the momentary button component of the step switch as a stand-in for the tilt-ball switch here, because it is annoying to pick up the breadboard and wiggle it around when testing.
CAD Files
The files needed to 3D print the case can be found on Printables. I've provided 2 designs, the main one using a breadboard NeoPixel, and an alternative for a through-hole NeoPixel if you'd prefer that aesthetic.
Both designs share the following two parts:
- wifi-numpad-remote-bottom.stl
- wifi-numpad-remote-front.stl
For the NeoPixel design, you'll need 2 more parts:
- wifi-numpad-remote-back-neopixel.stl
- any one of the following parts, depending on which diffuser shape you'd like:
- wifi-numpad-remote-top-neopixel-circle.stl
- wifi-numpad-remote-top-neopixel-triangle.stl
- wifi-numpad-remote-top-neopixel-diamond.stl
- wifi-numpad-remote-top-neopixel-star.stl
- wifi-numpad-remote-top-neopixel-heart.stl
If you'd prefer the through-hole LED design, use these two parts instead:
- wifi-numpad-remote-back-thru-hole.stl
- wifi-numpad-remote-top-thru-hole.stl
I took advantage of this 4-piece design to add "back stops" on the end panels, to hold the battery and breadboard NeoPixel in place without the need for adhesive or screws (if you are using the through-hole design, sorry, you'll have to use hot glue to mount your LED).
Filament and slicer settings
You can use any filament you wish, though I used PLA with these designs, so if you use a different type your tolerances may be slightly different. Most people will probably prefer an opaque color, to prevent light-bleed from the NeoPixel, but you could use translucent filaments if you don't mind that as much. You can print all 4 parts in one color, or use different colors for each part for a funky look.
- Layer height: 0.2mm
- Infill: 15% infill for all parts except the case top. You'll probably either want to print the whole part at 100% infill (to help prevent light bleed from the NeoPixel), or at least selectively set the infill for the NeoPixel mounting block to 100%.
- Supports: none
Design source files
The general design is based on the very cool Ultimate Box Maker template, which I hacked apart to suit my purposes. I've included my modified version of the .scad file on Printables, which you can use to further customize the remote with a tool like OpenSCAD if you wish. The modular and parametric design of the template makes it very easy to experiment and iterate quickly, especially if you are a programmer-y type like me.
Also, special thanks to the Ruiz Brothers for the QT Py snap-fit geometry that I borrowed from their QT Py Snap Fit case learn guide. It's incredibly handy and I use it in a lot of my QT Py projects.
Adafruit IO
For the example code, we're going to use Adafruit IO as our MQTT broker. If you don't already have one, you can create an Adafruit account for free at io.adafruit.com. If you already have an Adafruit account, you already have an Adafruit IO account. While Adafruit IO is very feature rich, the main feature we're concerned with for this guide is the MQTT capability. Adafruit IO further abstracts the idea of MQTT topics into "feeds". For this example, we'll create 3 feed topics:
- "test-led": A feed for a theoretical LED that we'll be toggling on/of with the remote (you could actually use another microcontroller with an LED and subscribe it to the feed/topic if you want, but we can just use the Adafruit IO dashboard we'll create below to see the state changes for the purpose of this guide).
- "test-stream-1": A feed for a theoretical stream of data posted to a topic. We'll use one of the remote buttons to publish messages to this feed, and watch in Adafruit IO as they are displayed.
- "test-stream-2": Same idea as "test-stream-1", mostly here to demonstrate that you can publish to multiple topics/feeds.
Create test feeds
If you haven't already, sign in to your Adafruit account in your browser. Then, navigate to io.adafruit.com. You should be greeted by your account overview page.
Click the "hamburger" menu in the top left of the page, and there will be a slide-in menu. Click Feeds.
Click the New Feed button.
In the Name field, enter test-led, and click the Create button.
Repeat the previous two steps to create two more feeds named test-stream-1 and test-stream-2.
Now we'll create a simple dashboard so we can see the results of the messages posted by the remote to our test feeds. Click on the hamburger menu in the top left again, and this time click on Dashboards. On the dashboards page, click the New Dashboard button.
In the Name field, type in WiFi Remote Test. Press the Create button.
Click on the newly-created WiFi Remote Test dashboard.
You should now be looking at an empty dashboard.
Click on the drop-down menu with the gear-shaped icon in the top right, and click Create New Block.
In the Create a new block page, click on the Toggle item.
In the Connect a Feed page, click on the checkbox next to the test-led feed, and click the Next step button.
In the settings page, enter Test LED for the Block Title, enter 1 for the Button On Value, and enter 0 for the Button Off Value. Click Create Block.
You should now have an on/off switch in the dashboard with the title Test LED. Click the gear icon in the top right again and click Create New Block.
In the Create a new block page, select the Stream block.
In the Connect Feeds page, Click the check box next to the test-stream-1 feed, then click Next Step.
In the Block Settings page, enter Test Stream 1 for the Block Title, then click the Create Block button.
Repeat the above 4 steps to create one more stream block named Test Stream 2.
You should now have a dashboard built with our 3 test feeds. Feel free to lay it out however you wish.
Adafruit IO Rate Limiting
The free tier of Adafruit IO is rate-limited to publishing 30 messages per minute. While this should be adequate to demo the features of the remote, it's important to be aware of, because the remote may briefly stop functioning correctly if too many messages are sent too quickly and your Adafruit IO account gets throttled. This could also be an issue if you already have lots of active Adafruit IO feeds publishing messages.
Example source code
The purpose of the provided example code is to demonstrate methods for doing the following:
- Connect to WiFi
- Connect to an MQTT broker
- Read the voltage monitor from the LiPo BFF to tell if the remote is plugged in to power
- Monitor user activity and put the microcontroller into deep sleep when inactive
- Use a NeoPixel to display status information
- Use a 3x4 keypad matrix
- Use a key on the keypad as a "toggle" button, by both subscribing to and publishing to a feed/topic to keep track of its state.
- Use a key on the keypad as a "macro" button by publishing one or more messages to one or more topics that we don't need to subscribe to.
Arduino
Currently there is only Arduino example code, though CircuitPython should also be capable of everything in this example. The main reason I've chosen to use Arduino is the near-instant boot time. I've found that CircuitPython takes about 2-3 seconds to boot, and that can feel like forever in use cases when you need a snappy launch. I consider this to be one of those use cases, especially as it already takes 2-3 seconds after boot to establish a WiFi connection, sync any necessary state, and be ready for input.
ESP32 board package
You will need the ESP32 board package installed for Arduino. You can find instructions on doing so on ESPRESSIF's site. Also, beware: the current version of the ESP board package (3.0.0-alpha-2 at the time of writing this) has many breaking changes from the previous stable version, and many libraries still need to be updated to account for them. For now I recommend installing the latest stable release, 2.0.14.
Library Installation
We'll be using the EspMQTTClient library to handle our WiFi and MQTT connection. EspMQTTClient provides a nice wrapper around the popular PubSubClient, which also happens to support Adafruit IO. EspMQTTClient provides some nice quality-of-life features, among them being a really nice callback system for subscribed topics. It also automatically attempts to reconnect to WiFi and/or MQTT when the connection is lost, however I've found that doesn't matter much for this particular project since the remote is usually only active for short periods of time anyway.
You can install the EspMQTTClient library for Arduino by use the Library Manager in the Arduino IDE. Search in the library manager for EspMQTTClient, select the EspMQTTClient library (make sure to pick the one by Patrick LaPointe), and click Install. If asked about dependencies, click Install All. If not, you already have the dependencies installed.
For the matrix keypad and the NeoPixel, we'll also need two other libraries: Adafruit_NeoPixel and Adafruit_Keypad. Search for those in the Arduino library manager and install them if they are not already installed, as well as any dependencies.
Source code
The example source code for the remote can be found on github at https://github.com/apendley/wifi-matrix-keypad-remote-sample. Download and unzip or clone the repo into a folder in the your Arduino directory called wifi-matrix-keypad-remote-sample. There are a few things you'll need to do to get the code ready to run with your setup. Let's start by opening the wifi-matrix-keypad-remote-sample.ini project in Arduino.
Config_Secrets.h
Navigate to the Config_Secrets.h file in the project. Replace the example WIFI_SSID and WIFI_PASS strings with your WiFi router's SSID and password.
// Your network's WiFi info. #define WIFI_SSID "your-wifi-ssid-here" #define WIFI_PASS "your-wifi-password-here"
Now, change the example IO_USERNAME and IO_KEY strings with your Adafruit IO username and key.
// Visit io.adafruit.com if you need to create an account, // or if you need your Adafruit IO key. #define IO_USERNAME "you_adafruit_io_username_here" #define IO_KEY "your_adafruit_io_key_here"
You can find these at io.adafruit.com. Your username is in the URL after "io.adafruit.com/" (for example, in the image below, mine is squid_jpg). You can click on the key icon in the upper right of the screen to get your key information.
Config_Actions.h
Now navigate to the Config_Actions.h file. This is where the keypad buttons are mapped to actions. If you set up your dashboard and feed according to the example earlier in the guide, you shouldn't need to make any changes here to get the example to work. You'll want to be familiar with this file, since if you continue using this code for your own remote, this is where you'll eventually define your own custom action mappings. This project provides two types of configurable actions:
- Toggle Actions
- Macro Actions
Toggle actions
Toggle actions have both a subscription and a publishing component. They subscribe to the feed so they can keep the toggle state synchronized when something besides the remote changes it When a key mapped to toggle action is pressed, it publishes to the feed with the opposite of the current state. Then, the remote waits for a "confirmation" from the MQTT server when it receives the updated value in the subscription handler.
The first parameter for a ToggleAction is the key on the keypad we're assigning the action to. The second parameter is the topic/feed we are going to publish to. Note the use of the AIO_FEED_TOPIC macro. This is necessary because Adafruit IO's topics have a path-like structure: "[username]/feeds/[feed-name]". The macro (ab)uses the C preprocessor to handle the task of of interpolating the user name and feed name into the appropriate path string, leaving us only needing to specify the feed name in the macro.
const ToggleAction toggleConfigs[] = { ToggleAction('1', AIO_FEED_TOPIC(test-led)), };
Macro actions
Macro actions are more "fire and forget". The remote firmware does not track any state for macro actions. Additionally, a single macro action can publish more than one message to more than one topic. Here we've defined 2 macros: the first publishes the message "2 key pressed" to the test-stream-1 topic when the '2' key is pressed. The second publishes the message "3 key pressed" to both the test-stream1 topic and the test-stream-2 topics when the '3' key is pressed.
const MacroAction macroConfigs[] = { MacroAction('2', { {AIO_FEED_TOPIC(test-stream-1), "2 key pressed"}, }), MacroAction('3', { {AIO_FEED_TOPIC(test-stream-1), "3 key pressed"}, {AIO_FEED_TOPIC(test-stream-2), "3 key pressed"}, }), };
Config_Preferences.h
Navigate to Config_Preferences.h, and look around briefly. These are easy-to-modify constants for various timing and LED brightness/color settings. I've set these to what I believe are reasonable defaults, but generally it should be safe to make changes to these values to suit your preference.
// #define LOGGER Serial
to
#define LOGGER Serial
Be aware that turning the logger on imposes a 2-second delay when booting/waking the remote, so keep that in mind when testing, and when you are finished assembling and testing everything, don't forget to turn the logger off.
Flash firmware to QT Py
Plug your QT Py into your computer with a USB cable, and choose it in the board selector in Arduino. Then, compile and upload the code by pressing the Upload button. Once the firmware is uploaded, we're ready to assemble the remote!
Assembly
Solder wires to the matrix keypad
Cut a piece of ribbon cable with at least 7 wires into a strip about 13 cm long. If you have more wires in your strip than 7, peel away the remaining ones, trying to leave the wire with the white stripe attached. We'll want this for reference when soldering the wires to the QT Py in a bit.
With the wire with the white stripe on the left side, peel away all of the wires from each other on the top edge of the ribbon, about 2 cm.
On one side of the ribbon cable, gently peel the wires apart about 2 cm. Strip about 1/2 cm of insulation from each of the wires, and lightly tin the exposed wire with your soldering iron. Repeat for the other side of the ribbon cable.
Starting with the wire with the white stripe, with the keypad facing up, solder the first wire to the second pin on the left. Repeat for the remaining 6 wires, soldering the second wire to the third pin, the third wire to the fourth pin, etc, until all the wires are soldered. CAREFUL: the soldering pads on the keypad are pretty delicate. Try to be swift yet gentle while soldering the wires to the pads.
After all wires are soldered, your keypad should look similar to this:
Solder the first wire (with the white stripe) into pin A0 on the QT Py, and trim the excess wire with your flush cutters. Make sure you are soldering the wire into the bottom side: we want the wires to stick out the top of the QT Py.
Continue on, soldering and trimming:
- The second keypad wire into pin A1
- The third keypad wire into pin A3
- The fourth keypad wire into pin SDA
- The fifth keypad wire into pin SCL
- The sixth keypad wire into pin TX
- The seventh and final wire into pin SCK
Once all of the wires are soldered you should have something that resembles this:
LiPo BFF
Next we'll solder the LiPo BFF to the QT Py. First, make sure the power switch on the BFF is in the OFF position. We'll set it back later after we install the battery.
Cut 3 wires:
- A red wire about 5 cm
- A black wire about 7-8 cm
- A green (or any other colored) wire about 5 cm
Strip about 1/2 cm from one end of each of the wires, and lightly tin the exposed parts of the wires with your soldering iron.
Solder the tinned edge of the red wire into the 5V pin on the BFF and trim the excess wire with your flush cutters. Again, solder on the bottom so that the wires stick out the top.
Solder the black wire into the GND pin on the BFF and trim. The GND pin is not marked, but it is the one right next to the 5V pin.
Finally, solder the third wire into the A2 pin and trim.
Your BFF should look something like this:
Solder BFF to QT Py
Strip about 1/2 cm of the insulation from the red and green wires on the other end of the BFF. Lightly tin the exposed wire with your soldering iron.
Solder the tinned edge of the red wire into the QT Py 5V pin and trim the excess wire.
Solder the tinned edge of the green wire into the A2 pin of the QT Py and trim.
Don't worry about the black wire yet, we're going to solder it to all of the other ground wires when we're finished assembling everything. The hardware assembly so far should look something like this:
NeoPixel
Since I separated the 10-wire ribbon cable earlier into 7 wires for the keypad I still have a 3-wire piece laying around, so I'm going to use that for the NeoPixel, but if you'd prefer, use red, white, and black wires between 24 and 30 AWG. Cut them about 8-10 cm in length.
Trim about 1/2 cm of insulation from the wires on one side of the ribbon cable (or the red, white, and black wires if you're using those), and lightly tin the exposed wire with your soldering iron.
Breadboard NeoPixel
Follow these instructions if you are using a breadboard NeoPixel. If you are using a through-hole NeoPixel, skip ahead to the Through-Hole NeoPixel section.
When soldering, we're going to solder onto the TOP of the NeoPixel PCB (the side with the LED). This might be a bit awkward, but we need the wires to come out of the back of the PCB, or we won't be able to mount the NeoPixel.
If you're using a ribbon cable:
- Solder one of wires on the edge of the ribbon into the + pin on the NeoPixel and trim off the excess wire. If your ribbon cable has a white stripe still, that's a good one to use. Otherwise, either edge will do.
- Solder and trim the remaining two wires into the G and I pins.
If you're using colored wires:
- Solder the red wire into the + pin and trim any excess wire
- Solder the black wire into the G pin and trim
- Solder the white wire into the I pin and trim.
Clip the Data out pin off with some flush cutters, we won't need it. Be careful to cut off the correct pin! Then, gently bend the remaining legs of the NeoPixel 90 degrees, as closely as possible to the LED diffuser.
Tin each remaining leg of the NeoPixel with solder. Then, solder the wires you prepared in the previous step to the NeoPixel:
If you're using a ribbon cable:
- Solder one of the wires of the ribbon to the 5V leg the NeoPixel. If your ribbon cable has a white stripe still, that's a good one to use. Otherwise, any wire will do, just remember which wires go to which leg.
- Solder and trim the remaining two wires to the Ground and Data in legs. Again, remember which wires you soldered to which leg!
If you're using colored wires:
- Solder the red wire to the 5V leg.
- Solder the black wire into the Ground leg.
- Solder the white wire into the Data in leg.
Solder NeoPixel to QT Py
Follow the remaining instructions for both types of NeoPixel.
If you're using ribbon cable, gently separate the wires from each other at the end opposite of the NeoPixel about 2-3 cm. Strip about 1/2 cm of the insulation from the end of each wire and tin lightly with your soldering iron.
If you're using ribbon cable:
- Solder the wire connected to the I or Data IN pin of the NeoPixel to the MI pin of the QT Py and trim off the excess wire.
- Solder the wire connected to the + or 5V pin of the NeoPixel to the MO pin of the QT Py and trim.
If you're using colored wires:
- Solder the white wire into the MI pin of the QT Py and trim off the excess wire.
- Solder the red wire into the MO pin of the QT Py and trim.
Again, don't worry about the remaining wire for now, we're going to solder it to the other ground wires when we're done.
We're getting there! Your assembly should now look something like this:
Now would be a good time to plug the QT Py into power and make sure everything works. If you didn't build with the LOGGER option enabled earlier, you may want to enable it and re-flash the firmware. Once plugged in, the LED should turn on (it will take 2 seconds if the logger is enabled). If you've got the logger enabled, press every button and look at the debug console to make sure a message is printed when each button is pressed. If not, you might have a loose solder joint somewhere. Inspect the connections and make any necessary fixes.
Tilt-Ball switch
Using a pair of needle-nose pliers, gently bend the leads on the tilt switch about 90 degrees.
Cut two wires (any color) about 8-9 cm in length.
Tin both legs of the tilt switch. It's okay to be a bit generous with the solder here.
Strip off about 1/2 cm of insulation from end of the wires opposite the tilt switch, and lightly tin the exposed wire with your soldering iron.
Solder a wire to one of the legs of the tilt switch. Since the wire and tilt switch are both tinned, you should be able to hold the wire in one hand and your soldering iron in the other. Touch the iron to both the leg and the wire to melt the solder around the wire and the leg. Remove the iron and hold the wire in place for a bit longer until the solder cools. Repeat for the other wire and leg.
Cut two small pieces of heat shrink tubing about 1 cm in length.
Slide each piece of tubing over the joints where the wires and the legs are soldered, and apply heat to the heat shrink tubing with a heat gun to shrink it.
The tilt assembly should look something like this:
Strip about 1/2 cm off of one of the tilt switch wires, and tin the exposed wire with your soldering iron.
Solder the wire to the RX pin of the QT Py.
Strip about 1/2 cm from one end of the wire, and lightly tin with your soldering iron. Solder it to the GND pin on your QT Py.
Gather all 4 loose ground wires (one from the QT Py, one from the BFF, one from the NeoPixel, and one from the tilt switch). Strip about 1 to 1.5 cm of insulation from each of the ground wires.
Twist all 4 of the ground wires together as tightly as possible.
Solder the wires together.
Cut another piece of heat shrink tubing about 2 cm in length. Slide it over the ground wires enough to cover the solder joint and leave about 1/4 to 1/2 a cm at the end.
Apply heat with your heat shrink gun to shrink the tubing. Then, use some pliers to pinch the other end while the tubing is still warm, so that the melted glue seals the opening shut.
We're finally done with the soldering! Before we can install the hardware into the case, there's one last thing we need to take care of.
Breadboard NeoPixel Diffusion
If you are using a through-hole NeoPixel, skip ahead to the Mount Keypad section.
Place the top part of the case face down on a surface such as a silicon mat, or something that can withstand a little bit of heat. A silicon mat is nice because it also provides an organic texture for the hot glue diffuser when it dries. Use your hot glue gun to carefully inject as much hot glue as possible as quickly as possible into the cutout on the NeoPixel mount at the top. The important thing is to make sure we are filling the cavity fully, so there are no gaps on the front side of the remote.
Hold the case in place for 30 seconds to a minute. Then, flip it over to see how it turned out. The hot glue should be evenly distributed around the diffusion cutout. If it's not, or your not happy with it, you can pry the hot glue out of the cavity and try again (you might want to use your hot air gun on the LOW setting to QUICKLY soften the glue a little to remove it. Be careful not to melt/warp the case plastic).
Now we need to clean out the cavity a little so we can fit the LED in there. You'll have noticed that there is a bit of an inset into the plastic where the LED should go. You may need to remove some excess hot glue in order to get the LED to sit firmly in there. To do this, I took a small flat head screwdriver (like a glasses repair kit screwdriver), heated up slightly with my hot air gun, and used it to carve out the excess hot glue. It took 3 or 4 cycles of lightly re-heating and carving before there was enough clearance to fit the LED.
Mount Keypad
Take the keypad and place it in the top part of the case, aligned with the screw holes. Use the 4 M2 x 8mm screws and 4 M2 hex nuts to fasten the keypad to the top of the case.
Mount QT Py
Take the QT Py, and insert the front corners (on the side with the USB port) into the grooves at the front of the QT Py mount on the bottom of the case. Using the tip of some needle nose pliers or some BLUNT poking instrument, gently apply downward pressure on the back corner of the QT Py (the corner without the antenna) and snap the back part of the QT Py into the snap fit mount.
Mount LiPo BFF
Insert the BFF into the other snap fit mount, with the battery JST connector facing the opposite direction of the QT Py's USB-C port. Place the front two corners of the BFF into the grooves (the side with the JST connector). Again, using your needle nose pliers or a blunt poking instrument, apply gentle downward pressure on one of the opposite corners of the BFF until it snaps into place.
Mount Battery
Plug the 500 mAh battery into the JST port of the QT Py (Make sure the polarities match first! If you bought your battery from Adafruit you should be good to go). Gently place the battery into the battery compartment so that it lies flat.
Mount Breadboard NeoPixel
If you are using a through-hole NeoPixel, skip ahead to the Mount Through-Hole NeoPixel section.
Use your finger to insert the NeoPixel LED into the mount, and hold firmly while sliding the "back" panel of the case (the one with the smaller "back stop" and cutout for thr USB-C port) into the grooves on the top of the case. The back stop should hold the NeoPixel firmly in place. The USB hole should be to the top right relative to the NeoPixel.
Mount Through-Hole NeoPixel
If you are using a breadboard NeoPixel you can skip ahead to the Mount Tilt-Ball Switch section.
Insert the NeoPixel LED into the LED cutout, with the legs going to the right.
Gently glue the NeoPixel into place, and hold firmly until glue is completely dry.
Now do one more pass with the glue, taking care to cover the exposed wires. Once glue is dry, cut away an excess. Insert the back panel into the grooves at the top of the remote, with the USB-C cutout to the top-right of the NeoPixel relatively speaking.
Mount Tilt-Ball Switch
This part is a little tricky. I didn't build specific geometry to hold the tilt switch because in my experience each one has slight variations that may require slight repositioning to optimize the wake effect when picking up the remote. I started with the switch at about a 45 degree angle, pressed to the left side of the bottom of the case, and applied some hot glue. I held it in place until it dried COMPLETELY. Then, I switched the power switch on the BFF to the ON position, waited for the remote to go back to sleep, and tilted the front of the remote upward slightly until the remote woke (make sure you have LOGGING turned off, or there will be a 2 second delay when waking!). Lucky for me, with this remote (I've built 2 previous), the sensitivity was great on the first try, and I barely had to tilt the remote to wake it. But, on previous attempts, I had to remove the hot glue and re-mount again, at a slightly steeper angle, until I was happy with the sensitivity.
Once you're satisfied with the placement of the tilt switch, slide the "front" panel into the grooves on the opposite side of the bottom case as the QT Py and BFF. The back stop should hold the battery down. If there is a gap between the back stop and the battery, you've inserted the front panel upside down. Make sure the switch on the LiPo BFF is in the ON position.
Applying light pressure to the back and front panels (so the NeoPixel, battery, and front and back panels don't fall out), slide the panels into the grooves on the top and bottom halves to connect them, making sure none of the wires are being pinched by the closing case in the process. Use the 4 2.3x5mm self tapping screws to fasten the halves together using the screw holes on the sides of the case.
If everything went well, your remote should now be fully assembled!
Using the remote
When the remote first boots, you should see the status LED light up blue (remember that if the LOGGER is enabled, the remote will take an extra 2 seconds to wake up). Once connected to WiFi and the MQTT broker, if you have any toggle actions defined, the LED should turn white while it waits for an update from the MQTT broker. Once received (or if there are no toggle actions defined), the LED should turn green, indicating it is ready to have its keys mashed. Typically it takes about 2-3 seconds for this whole process to complete in my experience. When plugged into power via the USB-C port, the LED should slowly pulse green. If the WiFi or MQTT connection is lost at any time, the LED will turn red for 10 seconds, and then go to sleep, unless the connection is restored before the 10 seconds expire.
Open up the dashboard we created earlier on Adafruit IO, and then press the '1' key on the remote. You should see the Test LED switch change it's state! Now, toggle the switch on the dashboard, and notice the LED on the remote: it should briefly flash white, indicating it has received the message from the MQTT broker and synchronized its state with the feed.
Press the '2' key and you should see the "2 key pressed" message pop up in the Test Stream 1 control block. Press the '3' key and you should see the "3 key pressed" message pop up in both the Test Stream 1 and Test Stream 2 control blocks.
Once we've verified everything works, if you had the LOGGER enabled, now would be a good time to disable it and re-flash the firmware. This way your remote will boot/wake quickly.
Now that the hardware is assembled, the firmware is installed, and you've seen it in action, you're good to go! Customize the actions to suit your needs, and you'll be remote controlling all the things in no time.
Customization ideas
- While the circuit is designed for a NeoPixel, it is fairly simple to modify the circuit to use a traditional through-hole RGB LED if you'd rather. Just keep in mind that removing the breadboard NeoPixel from the circuit only frees up 2 GPIO, and you need 3 for a traditional RGB LED. Still, you have a few options:
- Use a single-color LED, which uses only one GPIO. You'll need to modify the code to support this, and figure out a way to deal with the limitation of only 1 color to display the remote's status. Don't forget the current-limiting resistor!
- If you're willing to sacrifice the voltage monitor, you can skip soldering the A2 pins of the QT Py and the LiPo BFF together, yielding the third pin needed for a full RGB LED. You'll need to modify the code to use analog outputs for the LED instead of the NeoPixel library. Don't forget the current-limiting resistors!
- I get great reception with the stock antenna on the QT Py ESP32-S2 in my apartment, but not everybody's situation is the same. One possible option if you're having reception issues is to use the QT Py ESP32-S2 with uFL connector, and a small external antenna. You could probably mount it somewhere inside the case and still get a pretty good signal boost.
- The case is roomy enough that with slight modifications to the .scad file, you could use a larger battery to further increase the time needed between charges. With a 500 mAh battery I have been getting around 3 weeks of use each charge, but with perhaps an additional cm or two of length you might be able to fit a battery as large as 2000 mAh, and extend that to a couple of months.
- If you aren't happy with or just don't want to use the tilt-switch mechanism to wake the remote, you can modify the design files to put a hole somewhere in the case to mount a momentary button to use as a wake button instead. The wiring and code for the button should be exactly the same as the tilt switch.
- While the voltage monitor provides basic capabilities such as detecting whether the device is plugged into USB power, it can be difficult to translate the readings into actual battery percentage remaining. Even though we've used all the GPIO pads on the QT Py, the Stemma QT port on the ESP32-S2 is actually connected to an entirely separate I2C peripheral. This means with a case modification (or maybe even just a little bit of trusty VHB adhesive tape) you could add an I2C battery monitor to give more accurate readings, and use the NeoPixel to indicate that it's time to charge the remote! You could even periodically have the remote publish to an MQTT topic with the battery level, so you can monitor it via, for example, an Adafruit IO dashboard.
- While the example code provided is based on MQTT, you are not limited to using MQTT for your remote. Arduino and CircuitPython provide libraries to interact with many other devices and services using common protocols such as HTTP. Or, if you have other ESP-based devices you'd like to control, you can use ESP-NOW. If you have any WLED devices, you might be excited to learn that they've recently added support for ESP-NOW remotes, and will be expanding it soon, making this a possible choice for a custom ESP-NOW-based WLED remote!
Conclusion
I hope the WiFi Matrix Keypad Remote works well for you! If you found any errors in this guide, or have any suggestions for improvements, you can find me lurking on the Adafruit discord (squid.jpg is my Discord user name), and you can find me on Twitter using the handle @aaron_pendley. Happy making!