Commercial smart alarm clocks run $50 to $150 for a device that does exactly what the manufacturer decided it should do. You can't expand it or change it, and what you bought is what you get. Mine, on the other hand, cost about $15, does exactly what I want, and ties into my Home Assistant setup.
As is the case with many of my projects, this is built around an ESP32 microcontroller, using daisy-chained MAX7219 LED matrix panels for the display, and it runs ESPHome firmware I configured in YAML. It sits inside an enclosure I designed in Onshape and 3D printed. Here's how I built it and how you can do the same.
Interested in more maker-related content? We recently launched the XDA Maker Weekly newsletter, featuring unique and original content you won't find anywhere else on XDA. Get subscribed by modifying your newsletter preferences!
The hardware was astonishingly cheap
And it's better than more expensive alternatives
A store-bought alarm clock is a closed box, and one with additional "smart" features can cost you a decent amount. With one of those, you can set the time, set an alarm, maybe adjust the brightness, and that's more or less it. An ESP32-based alarm clock connected to Home Assistant can do things no commercial clock offers, and the beauty of it is that I control what those features are.
The parts list is extremely short, and the build itself is very simplistic. All I'm using is an ESP32 development board that cost about $5, and a set of four MAX7219 8x8 LED dot matrix modules on a single PCB. It's all powered from a single USB-C cable, and it's been running for several days straight without any issue.
The ESP32 is a microcontroller made by Espressif Systems. It has built-in Wi-Fi and Bluetooth, and a dual-core processor. For a clock project, Wi-Fi allows for over-the-air updates and time synchronization. This means I can add more features remotely while also ensuring that the time stays consistent.
The MAX7219 is a display driver chip that controls 64 individual LEDs, arranged as an 8x8 matrix. It communicates over SPI, so it needs only three data wires: DIN (data in), CLK (clock), and CS (chip select), plus power and ground. These modules also daisy-chain easily. You connect the DOUT pin of one module to the DIN of the next, share the CLK, CS, and power lines, and you've got a longer display with no extra wiring. You can buy preassembled modules that have four connected in a series, which is exactly what I did here for roughly $10.
Designing the 3D print
Onshape makes things simple
Despite having extensive experience in Solidworks from my school days, I tend to gravitate towards Onshape as my CAD software of choice. It's an entirely in-browser experience, and fits the bill perfectly for simpler designs like these. I used a digital callipers to take measurements of all of the most important parts of the build, then got to work designing a model that could house all of it.
I went through three iterations; the first wasn't wide enough to house the MAX7219 with the DuPont connectors hooked up to the display series. The second I discovered I had miscalculcated the total length of the four panels in series, but the third was perfect and is the one that I'm using now. I also know that I could solder the MAX7219 series to the ESP32 and cut down on space, but I like the flexibility that I have to remove it and do something else with it in the future.
How I hold everything in place is, more or less, just physics. The ESP32 can slot into a wedge that I cut into the inside of the casing with a hole for the USB-C connector, and when a USB-C cable is connected, it holds it firmly in place. The MAX7219 series of panels stays in place as I added a lip that goes around the the edge of the panel, meaning that it's held in place. I wanted a screwless, glueless, and solderless design, and while there were many online that I could find, practically all of them required screws or soldering.
It's basic, but it's functional, and that's all I needed. The print was quick and simple once it was designed, and I printed it with the front-facing part facing downwards in order to avoid needing supports. I bought three of these four-in-one module kits, so I'll likely tighten the MAX7219 series holder before printing future iterations. I would possibly also make the wedge that holds the ESP32 in place go just a little bit deeper. My USB-C cables connect to the ESP32 just fine, but it's a tight fit for sure.
Programming in ESPHome
A very basic configuration does a lot of lifting
ESPHome is the reason this project doesn't require writing C++. For those not in the know, ESPHome is an open-source firmware framework where you configure ESP32 devices using YAML files. You describe what hardware you have and what you want it to do, and ESPHome generates the firmware, compiles it, and flashes it to your board. My ESPHome code is available on GitHub.
The core of the ESPHome configuration defines the SPI bus, the display, a time source, a brightness control, and the display lambda that controls what appears on screen. The display has a functioning clock on it pulled via SNTP, and there's a second display page can be used to show text. Right now, this text can be written to from Home Assistant, but it can be used to show notifications, temperature sensors, or anything else you can think of that you can poll for data from. Thanks to the max7219digit platform, text automatically scrolls when it's longer than the display.
The display configuration is pretty simple, especially given that ESPHome has native support for it. My pages look like this, where I select a page from Home Assistant and the appropriate page is set based on that variable.
display:
- platform: max7219digit
id: max_display
cs_pin: GPIO25
num_chips: 4
intensity: 15
lambda: |-
// Display off
if (!id(display_power).state) {
it.intensity(0);
return;
}
// Apply brightness from number entity
int brightness = (int)id(display_brightness).state;
bool is_night_mode = id(clock_night_mode).state;
it.intensity(is_night_mode ? 0 : brightness);
// Text
if (id(display_page).state == "Text") {
it.invert_on_off(false);
std::string text = id(display_text).state;
it.print(0, 0, id(text_font), text.c_str());
return;
}
// Clock
if (!id(time_sync_done)) {
it.print(0, 5, id(digit_font_basis), ". . . . . . .");
it.invert_on_off(true);
return;
}
The above code sets the display to the lowest brightness at boot up, and will also set it to the lowest brightness at night. Night time scheduling is defined with datetime sensors in the YAML which have night time start and night time end variables that can be configured in Home Assistant. In terms of brightness options, there are 16 steps of brightness, and it gets surprisingly bright given that it's being powered by the 5V VCC pin on the ESP32.
For wiring, I only need to use five pins on the ESP32, and the below list shows where each pin on the MAX7219 is connected on the ESP32.
- VCC: VCC
- GND: GND
- DIN (Data In): GPIO27
- CS (Chip Select): GPIO25
- CK (Clock): GPIO26
Once I plugged it in it worked instantly. There's no need for an external power supply so long as you have a 5V VCC pin that can output to the MAX7219. The 5V VCC pin is directly connected to the 5V USB-C pin that powers the board, meaning that you're limited by current rather than voltage, but the MAX7219 doesn't require a lot of power to work in the first place. Using my in-line power measurement tool, I measured 0.14A at 5V, meaning that it pulls just under 1W of power consistently. If you're limited to 3.3V outputs, though, you may need an external power supply.
From start to finish in just a few hours
A deceptively simple project
This whole project cost me about $15 and took me just a few hours to go from loose components to a finished clock in my living room. It does exactly what I need it to, it looks good, and I can modify it from any device that can connect to my ESPHome instance. Truth be told, it's hard to beat for the price.
There are things I might add down the line, like a passive buzzer for an alarm tone or a temperature sensor so it can double as a room monitor. But honestly, I keep looking at it and not finding much I want to change. It tells the time, it dims itself at night, I can push text to it from Home Assistant, and it fits cleanly in an enclosure I designed myself. That's enough for me, and and I think from a hardware perspective, it's more or less complete as-is. In terms of software, I might add support for notifications or other pieces of information, but otherwise, I'm incredibly happy with how it turned out.
If you want to build your own, you can check out the ESPHome configuration on GitHub and the 3D printable model on Printables, though keep in mind that the model isn't necessarily perfect, and is merely good enough for my needs. Still, the build is simple enough that if you've ever flashed an ESP32 before, you'll have this running in a short amount of time.
