Over the years, I've tried dozens of different bedside lamps and alarm clocks. None were perfect... some were simply too bright for a dark bedroom at night. Others were too difficult to read from bed without glasses. Some had "jarring" alarms or buzzers that shocked you awake. Few had any integrations that could be used in automations. And the list goes on. So I decided to build my own version of the Ultimate Bedside Lamp and Alarm Clock.
Primary Features
To see an overview of features and highlights of the build process, you may wish to view this YouTube video first.
Before getting into the nuts and bolts of the physical build and firmware installation, let me cover some of the key features of the final project:
 |
| Click to enlarge |
- Can operate entirely locally
- WIFI is the only requirement for full functionality
- You can optionally use Internet connectivity for some features, such as NTP time sync or external weather data, but this is not required.
- Dual Lighting
- Main RGBW WIFI Bulb as primary light
- RGB LEDs in the lamp body as secondary or night light
- Dual-embedded customizable touch sensors in top of the base
- Functionality can be changed via app
- Toggle lights, change brightness, dim the display, etc.
- Secondary functions allows sensors to snooze/stop an alarm
- Time (12 or 24 hour) and Date with optional syncing to external source
- Customizable color and font
- Current outdoor temperatures (optional) in °C or °F
- Auto-dimming (customizable based on room ambient light level)
- Indicator icons show WiFi, internal link state, active alarms and settings
- Light control, display brightness and alarms can be set right on the display
- Up to Five Active Alarms
- Single event - programmable by date (can be set for the future)
- Repeatable by day of week, weekends or weekdays
- Select from a list of .mp3 sounds or use your own!
- Gentle-wake option sounds alarm at an initial low volume, slowly increasing over time.
- Set a maximum alarm volume.
- Customizable snooze time from 1 to 60 minutes
- Alarms can be set (and silenced) via touch panel, web app, MQTT or HTTP API
- Dual USB Charging Ports
- Up to 2A each / 4A Total
- Charge phone, tablet, smart watch, etc. and free up precious outlets
- Optional Integration with other Platforms
- MQTT
- Custom-designed HTTP API
- Home Assistant Discovery - add to Home Assistant with a single click
- Expandability for the Future
- Additional 5V power leads for adding devices like a smart speaker, such as the Atom M5 or the Home Assistant Voice Preview Edition.
Background and Important Notes
This is probably not a "beginner" project
This is the most complex project I've ever built. The wiring and physical builds aren't all that difficult, but the project uses 3 independent ESP-based controllers (one ESP8266 and two ESP32s) and the firmware to manage and facilitate both external and internal communications was a huge challenge. Because of this complexity, changes to the firmware if needed, will likely require experience with the Arduino IDE and C++. See more on this below.
This article primarily covers the build process
While I cover a handful of firmware-related steps, including the basics for setting up a bench test, full information on the installation, onboarding, configuration and use of the firmware is covered in detail in the Github Wiki. The latest information for all things "firmware-related" can be found in the Wiki.
This article will primarily cover the parts, wiring, bench testing and the build assembly of the final project.
Custom-Designed for MY Specific Needs and Wants
Prior to this project, my current alarm clock (and favorite to-date) was the original Lenovo Smart Clock:
Initially, this clock was near-perfect. It had auto-dimming and was fully integrated into Google Assistant/Home. I could get a full day's briefing upon awaking and run all sorts of automations via either Google Home or Home Assistant. But Lenovo and Google had a falling out and over time (like many other Google products), and I've slowly been losing features. But there were many features of the Lenovo clock that I really liked and wanted to carry over to my version:- Auto-Dimming based on ambient light (but I wanted mine to be configurable)
- Snooze/stop alarm by just slapping the top of the clock
- Ability to control room lights via the touch panel
But the Lenovo clock only has one USB charging port on the back. I generally need to charge both my phone and smart watch at night. And given the fact that the bedside lamp was independent, it meant that I needed power for the clock, power for the lamp and additional power for charging the second device. Plus other power needs near the bed (e.g. my bed sensor project, power for the SleepNumber bed, etc.) resulted in the need for power strips and USB hubs on the floor or under the bed. So another goal was to reduce the number of AC outlets required.
With all this being said, I did design the project for my particular wants and needs which may be different that yours. I tried to make the project somewhat 'modular', meaning you could just build the light/lamp portion without the clock, or just build the clock without the lamp. But any modifications (including part substitutions) will likely require at least some firmware code updates... and see the above section about the complexity of the firmware. Unfortunately, I simply don't have the bandwidth to create and maintain multiple versions of the firmware.
So if you wish to build a version that uses different components than the specific ones I list below, be prepared to make at least some firmware changes. I will be happy to point you in a direction, but cannot make or maintain multiple versions of the project. Any firmware changes that aren't in the official Github release will be up to you to implement. Also check out the "What I'd Do Different" section near the end of this article if you decide to design your own version.
Overall Architecture
Before delving into the parts and actual build, it is important to understand the overall architecture as three different ESP chips are involved. This section also introduces terms like 'primary controller' and 'display/secondary' controller that will be referenced throughout the rest of this article.
The smart bulb uses an onboard ESP8266 and comes with ESPHome preinstalled. Optionally, you can add this to your local ESPHome instance if you have one. This would create the entities necessary to directly control the light bulb (state, brightness, color, etc.). But ESPHome and/or Home Assistant are optional and not required for this project! All bulb functionality for this project are handled via the unified web app and API. You can either add or not add the ESPHome node to Home Assistant. Since this isn't used as part of this project, I won't be covering anything other than the required onboarding process for this project. If you want to know more about using ESPHome directly, see the links at the end of this article.
Primary Controller
This is just a standard 30-pin ESP32, WROOM-style dev board. Other ESP32s should work fine but have not been tested. Note that using an ESP32 variant (like an S3 or C5) will likely at least require a recompilation of the code for the particular variant, but may also require other code changes. Again, I cannot support other versions of the ESP32. If you wish to use one, you'll need to figure out the changes and compile your own firmware from the provided sources.
Display Controller
There are many varieties of the "Cheap Yellow Display" available. But they certainly aren't all created equal! As I show in this separate YouTube video, two boards that look very similar are worlds apart when it comes to functionality and GPIO pins:
As shown in the video, code that works with one variety of CYD doesn't work with another. This means if you use a different display model than the precise one linked below, you will most likely need to do significant updating of the firmware, including modification of the tft_eSPI library for your particular board. If the listed CYD undergoes changes by the vendor or manufacturer, I'll try to update the firmware accordingly. But if you use a different type of display, then you are 'on your own' for the firmware. Do note that the firmware ONLY supports capacitive touch boards and WILL NOT WORK with resistive touch displays.
GPIO Pin Selection
If you do opt for a different ESP32 or CYD, note that you need to maintain the same GPIO pin use as covered below. Because of the number of GPIO pins in use, it was impractical to provide some sort of setup process that would allow you to specify the GPIO pins you used. If you need/want to use different GPIO pins than specifically specified, you will need to modify and recompile the firmware. Fortunately, most GPIO pin settings can be found near the top of the controller's sketch. Just assure any GPIO pin changes are the proper type to support that pin's functions.
Finally here's another breakdown of the "controller triumvirate" by major function:
 |
| Click to enlarge |
With those caveats stated, let's look at the needed parts.
Parts List
There are, as expected, quite a few parts for this project
As covered above, the firmware for this project expects certain hardware. Some components may be freely substituted, while other may or will require modifications to the firmware. The project has only been tested with the specific items listed here. The first column is used to indicated the likelihood that substitution of that component will also require firmware modification:
(2) - Firmware modification highly likely
(1) - Firmware modifications may be needed
(empty) - Should be able to freely substitute, as long as functionality is similar
I'll group the parts by primary functionality or use, but note that there may be overlap in use of a particular component. In this case, the part is only listed once but may be used in other features.
Some of these links may be Amazon affiliate links. Use of these links will not affect your pricing, but as an affiliate this blog may earn a small commission if you make a purchase.
General Lighting and Body
Primary Controller
Display Controller
Power-Related Components
Connectors and Miscellaneous Components
3D Printed Parts
|
|
Item or Part
|
Notes
|
|
|
Base Enclosure
|
All .stl design files
in Github
Repo
|
|
|
Enclosure Lid
|
|
|
|
Bottle Platform
|
|
|
|
Mid-Platform Support
|
|
|
|
Center Shaft
|
|
|
|
Bottle to Lamp Kit
Adapter
|
|
|
|
USB Port Mount
|
|
|
|
USB Port Mount Nut
|
|
|
|
Bottle Sizer
(optional)
|
Can be used to test bottle
sizes
|
|
|
|
|
Obviously, you can substitute anything related to the enclosure. If you do not have access to a 3D printer, you could substitute something like a wood or aluminum box or even purchase an electronics project box and modify it. But here are the 3D printed parts I designed and used:
You can find the .stl design files for these parts, along with printing requirements, in the Github repo for this project.
Breadboard/Prototype Bench Test Components
Additional Parts Shown (but not used in initial project)
Of course you'll need standard electronics tools, like wire cutters/strippers, soldering iron and solder, etc.
Bench Test Preparation
I always highly recommend a full bench test before creating any soldered versions. This not only allows you to verify your wiring and test the firmware, but to also test all components to be used. It is much easier to diagnose, repair or replace parts at this stage instead of after everything has been soldered into place.
This project, however, makes bench testing just a little trickier than most due to the tri-controller design. When booting, the controllers look for and try to connect to the other controllers. If not found, then numerous features are disabled or the board sits and waits on other controllers. The boot process and how the boards connect is covered in more detail under the firmware section and in the Github Wiki.
Preparing the Controllers
Of course all three controllers need to be initialized and onboarded to your WiFi. The onboarding process for all three controllers are very similar:
- Power on the device
- Use a phone or other mobile device to search for a new hotspot (the hotspot name will vary by device - see specific onboarding info in Wiki)
- Open a browser and go to http://192.168.4.1
- Enter in your WIFI credentials and any other required information.
- Save the configuration and the device will reboot and join your WIFI.
Be sure to note the initial IP address assigned by your router after onboarding each device. Since the controllers communicate internally by using the IP address of the other controller, I strongly recommend that you assign static or reserved IP addresses for all three controllers. If a device IP address changes at some point, your bedside lamp will quit functioning. You will enter the IP addresses for the controllers as part of the initial setup (covered under the firmware section or in the Wiki), so you should set static/reserved IPs for the controllers before this point. Remember to reboot any devices after assigning an IP address so that the device gets the new IP address.
I recommend you onboard the device in the following order:
Kauf RGBW Light Bulb
Kauf bulbs come with ESPHome pre-flashed, so there is no firmware to install. Just place the bulb in a standard lamp socket (commandeer a small lamp - but choose one that you can use throughout bench testing - in other words don't steal a family member's favorite reading lamp!).
Kauf bulbs come with a pretty good instruction manual, along with more information both on their web site (links at end of article) and in the Github repo. Just follow the instructions to place your bulb onto your local WIFI. Adding to Home Assistant and optionally importing the ESPHome node is up to you and the project will work either way. Just note that if you opt integrate the bulb directly into Home Assistant and then later add the lamp/clock project to Home Assistant via Discovery, you will end up with two different entities to control things like the bulb state, brightness and color. Also note that a default name is given to the bulb and you'll need this name for the initial configuration of the three controllers. If you wish to rename the bulb device, it is recommended that you do it now and before configuring the controllers. Information on how to rename the bulb is included in the Kauf manual.
Note: For the bulb, and for the entire project, use of Home Assistant and/or ESPHome is entire optional. You do not need either for full project functionality.
After onboarding, leave the bulb in the powered lamp socket. You can toggle the bulb off if desired, but don't unplug or turn off the main power to the bulb. Nothing is going to be physically wired to the bulb, so leaving the power on for bench testing won't be a safety issue.
Primary Controller
Next, complete the firmware installation via USB and the onboarding process for the primary controller (standalone ESP32). Onboarding is very similar to the Kauf bulb, but the entire flashing and onboarding process, with screenshots, is covered in the Github Wiki.
Follow the instructions carefully and after onboarding remember to assign a static/reserved IP address using your router, note the IP address and power cycle the primary controller one time. After power cycling, you can remove power from the controller and set it aside for now.
Display Controller
The firmware installation and onboarding for the display controller is nearly identical to the primary controller, but step-by-step instructions are in the Github Wiki.
Just remember to assign/reserve an IP address for this controller and reboot it at least once to get the new IP. Be sure to note the assigned IP.
Note: After the initial onboarding, the display controller will enter its normal boot process. Output of this process is shown on the display. But during this initial process, you will likely receive error messages on the display. This is normal and expected! We haven't told the controller how to find the other controllers yet.
Initial Controller Configuration
After flashing and onboarding, you now need to tell the controllers how to find each other. Again, this guide isn't meant to cover the full use of the firmware. See the Github Wiki for full details. But for bench testing, you must complete at least the following steps:
1. Power on all three controllers if not already on.
2. Open up a browser on any device that is on the same WIFI network as the
controllers. Go to the IP address of the primary controller. (e.g. 192.168.1.205).
3. On the main page of the web application, select the System Integrations and Options.
4. There is a lot of information on this page, but for now, we are only interested in three settings:
A. Number of LEDs
- You likely won't know the final number of LEDs in your project yet. However, for bench testing, just enter in a non-zero number at this point. I recommend something like 15-20 for testing. You'll update this number later after the true number of LEDs is known.
B. Main Light Bulb Integration
- Bulb IP Address: Enter in the IP address of the RGBW bulb. If you assigned a static/reserved IP address to the bulb earlier (highly recommended), make sure the bulb has been power-cycled at least once.
- Bulb Name: This is also needed but needs to match the assigned (or updated) name from the Bulb onboarding. If you are not using Home Assistant/ESPHome, you can find the assigned name by just going to the IP address of the bulb in a browser.
However, note that you must "convert" this name to an ESPHome compatible name. This means all characters must be lower case and any spaces replaced with an underscore. As you can see in my configuration screen shot above, I've taken the actual name of the bulb and converted it to the ESPHome equivalent bedside_lamp_bulb. If these names don't match or you do not use the ESPHome equivalent, the other controllers will not be able to communicate with the bulb. If you change the bulb name at some later point, you'll need to update the configuration as well.
C. Touch Panel Display Integration
Just enter in the IP address given or assigned to the display controller.
D. Save and Reboot
To make the changes permanent, the new configuration must be saved. The controller will reboot and the new configuration will be loaded. Make sure the other two controllers (bulb and display) are all powered on and click the "Save and Reboot" button at the bottom of the configuration page.
Notes on initial configuration:
To facilitate initial testing, there are some default configuration values that are used to test the intra-device communication. You may see the following during the boot process:
- RGBW Bulb will briefly flash red, green and blue (bulb communication OK)
- LED strip will be powered on (LED strip setup OK)
E. Reboot the Display Controller
During the primary controller's boot process, configuration information is sent to the display controller (this contains the primary controller's IP address in addition to other information). However, to make this configuration permanent, you need to restart the display controller one time. You can restart the display controller using any of the following methods:
- Enter a restart command from a browser: http://display_ip/restart
- Return to the primary controller app, click the "Display" button from the menu. Scroll to the bottom of the display controller's page and choose "Restart".
- Power cycle the display controller.
Note that the display controller will now show its own boot process on the display:
You will likely see a number of items marked as "disabled" or "failed" for now because these items haven't been configured. The two important items for now are the successful connection to the primary controller and the OTA updates (so we can later update wirelessly if needed). The remaining items, and whether the boot completes or fails really doesn't matter at this point. As long as pinging the primary controller receives an "OK", that's all that is needed for now.
With these steps complete, you can power everything down (or leave the bulb powered if desired since it is completely independent) and start building the bench test.
Building a Bench Test Version
Even though the system is using three different "controllers" or ESPs, each is actually independent and their is no physical wiring linking them together other than the shared DC power source for the primary and display controllers. Instead, they communicate wirelessly using a special internal API. This means that we can build each controller independently.
Kauf RGBW Light Bulb
There is nothing to do here! Just place the bulb in a powered socket or lamp. I have a standard set of "AC Test Rigs" that I previously created for bench testing from parts that I picked up at the local hardware store.
Of course this is entirely optional and you can just place the bulb into a lamp or other light fixture. While not completely necessary, it would be best if the bulb was on or near your bench for testing. At a minimum, the bulb should be viewable from your work area. But that's all you need for the 'bulb' portion of the bench test.
Primary Controller Breadboard Version
The first minor issue to overcome is that a standard 30-pin ESP32 is just slightly too wide to fit on a standard breadboard, while still leaving a row of connection points on each side:
There are a number of ways to overcome this, including using pin headers to elevate the ESP32 and exposing the connection points underneath the ESP32 (in fact, this is the method we'll use for the soldered version). But pin headers don't work real well with breadboards. So there are a couple of alternative methods.
My preferred method is to use one of these protoboards:
These protoboards are invaluable if you start to do more complex DIY projects as it provides ample room and connection points for multiple components. What's more, the ESP32 can straddle any of the internal power rails, leaving multiple connection points on both sides of the ESP32.
However, if you don't have (and aren't ready to purchase) a protoboard, you can simply connect two breadboards together.
The ESP32 is just wide enough to span the joined power rails on the breadboards and allow multiple connection points on both sides of the board.
Regardless of the method used, you first need to get the ESP32 on a breadboard with connection points on both sides.
Here is the overall breadboard version wiring diagram for the primary controller:
 |
| Click to expand |
There's a lot going on there, so let me break things down a bit if you are newer to DIY electronic projects.
Power Routing and Use
All 5V components (except the USB charging ports) will operate off of a single power supply. But depending upon the number of LEDs in your strip, plus all the other components, the entire system is likely to draw more current than can be safely handled by the breadboard. So we will split off the 5V power and run in parallel to the LED strip and the primary controller. In fact, any time you are powering an LED strip, this is the recommended power method.
I'm showing 5-place Wago connectors here because we will also eventually power the display controller as well, so I have extra connection points available.
The incoming 5V from the power supply is connected to one of the breadboard power rails (preferably on the same side as the ESP32 VIN pin), creating a 5V power rail. This brings up the next important point... the ESP32's power and GPIO pins.
ESP32 GPIO and Power Pin Locations
If you are using a different ESP32 dev board than mine, it is possible that your power and GPIO pins are in different locations. It is critical that you use the same GPIO pins that I am using, regardless of where they are located on your board. Otherwise, you will need to modify the firmware and compile your own custom version. This means your wire routing might look different from mine as well, but the actual electrical connections to the proper component pins is the only thing that truly matters... not how the wiring is routed. For easier reference, these are the connections that should be made to the ESP32:
VIN: Connected to the +5V power rail
GND: Connected to the GND (-) power rail
3V3: Connected to the opposite power rail (+)
GND*: Connected to the opposite power rail (-)
GPIO4: LED Data Line
GPIO18: Touch1 Sensor
GPIO19: Touch2 Sensor
*Note: If your ESP32 only has one GND pin, simply connect the ground (-) on both power rails together using a jumper wire. DO NOT connect the positive power rails (+) together!
Level Shifter
Use of a level shifter when it comes to LEDs is always a controversial subject! While it is true that if you keep the wiring run between the ESP32 and the start of the LED strip relatively short, you can often get away without a shifter. But the use of a shifter is highly recommended. And I will be including one on the final soldered version because it is much easier to add it up front rather than discovering that there is a signal problem (indicated by flickering, dimming or otherwise misbehaving LEDs) and trying to add a shifter after-the-fact. But you will see in the bench testing on the breadboard in the video that I'm not using a shifter. If you wish to omit the shifter, then just connect the outgoing data line from GPIO directly to the LED strip and eliminate all wiring related to the shifter.
Then why am I showing the shifter with the temporary breadboard version? For one, it allows you to actually test the shifter itself. I have received bad shifters in the past. Testing it on the breadboard assures the shifter is working correctly before soldering it to a permanent version (although I mitigate this a bit by using a socket so the shifter could theoretically be replaced). Use or omission of the shifter is up to you, but I highly recommend you include it... if not on the breadboard version then at least with the permanent soldered version.
As I show in the video, you could power on the primary controller and do some bench testing at this point. But because the controllers look for, and try to establish communication with the others, it is best to go ahead and build the display controller before starting full testing.
Display Controller Breadboard Version
The display controller is a Cheap Yellow Display (CYD) with an onboard ESP32. A few peripherals are attached, including a DFPlayer DFMini that will use an SD card to hold and play various alarms and other sounds that we'll eventually place on a small mounting board. But for the bench test we won't need a breadboard. You should complete the initial flashing of the firmware and the onboarding process as described above before starting all the wiring. Here are the general pinouts and connections for the secondary controller.
 |
| Click to pop out |
Whoa! There's even more going on with this controller. But it's really not as complicated as it first appears, so I'll break things down below.
IMPORTANT: As mentioned under the parts list, there are many, many varieties of "CYD" type boards out there. Unfortunately, the display library used (TFT_eSPI) requires that the board be defined before the firmware is compiled. Using a different board will likely require custom modifications to the firmware and compilation of a custom version. At a minimum, to work with this project, the CYD must:
- Have a display resolution of 480 x 320
- Must be capacitive (and not resistive) touch capable
- Have access to the following GPIO pins:
- GPIO4
- GPIO16
- GPIO17
- GPIO21
- GPIO22
- GPIO27
- GPIO35
Note that in the above diagram, those GPIO pins listed in gray aren't used. Others, like the LED backlight are used in code, but aren't wired to anything. They are simply shown here in the event you want to customize your install. If your board has different GPIO pins, you will have to modify the firmware. You might be wondering why some built-in components aren't used and external components are used instead.
SD Card Reader & Speaker: While I could get the onboard card reader to successfully read files, I could not get reasonable sound output via the SPKR connection. I could only get clicking and static. This could be the board itself, but the ESP32's built-in DAC isn't the best to begin with. So I opted for an external SD card reader and player, the DFPlayer DFMini, which gives acceptable sound output. We'll also place the player in a more convenient location in the final enclosure for accessing the microSD card than would be possible if using the display's reader.
Onboard LDR: I found that the integrated light dependent resistor simply wasn't sensitive enough to be used for display brightness based on room ambient light level. This is probably due to the use of an analog pin on the ESP32. So a dedicated light level sensor is used instead.
From this point forward, I'll assume you are using a compatible CYD and the same GPIO pins.
Creating Cables
Since the CYD makes GPIO pins available via 1.25mm JST connectors, we will probably have to create a few custom cables. Your CYD may have included a couple of JST connectors, but they may not be the right number and we'll need female Dupont connectors on one end. This means creating a few cables (and you will be able to reuse these for the final soldered version).
For the most organized and reliable connections, I recommend that you use custom JST and Dupont connector kits (if not now, at least for the final soldered version):
I've found these kits invaluable for multiple projects, especially the Dupont kit as I not only allows you to create Dupont connections with the exact number of pins needed (as opposed to a bunch of individual single-wire connections) but also allows you to use larger gauge wire since most pre-purchased Dupont jumper use thin 26 gauge or thinner wire. If your CYD kit doesn't contain the proper connectors (highly unlikely), then you will need to build the following two connectors:
For the JST end, you need two female 1.25mm connectors. If using a kit similar to the above, you can just insert the pre-crimped wire leads into the connectors. They only fit in one orientation and you should feel them "click" into place. Wire colors are somewhat arbitrary, but I'd recommend matching the JST and Dupont wire colors. Note that the white and gray wires actually won't be used, but you'll want to at least insert the lead into the JST connector to assure a proper connection to the JST on the CYD. Later we'll trim these extra wires off, but for now either cut off the end connector or cap it in some manner to assure this bare metal lead doesn't come into contact with any other leads or conductive surfaces.
For the Dupont wires, if you don't want to build your own at this point, you can just take existing Dupont jumpers you may have on hand. These can be male-to-female or female-to-female as we will be cutting off one end. Just assure the remaining connector is female.
For the final soldered version, we will also solder these leads together and enclose the joint in some heat shrink tubing. If you do that now, you can probably repurpose the bench test cables. But if you aren't ready to solder these wires together, for bench test purposes, you can using something like inline Wago connectors:
Next, if you note the above wiring diagram, note that we are powering the CYD via its USB port. This is intentional. The display will be mounted so that the USB port is accessible when the lid is removed. If for some reason, the CYD firmware becomes corrupt or the board won't respond to wireless, over-the-air (OTA) updates, you can simply disconnect the internal USB cable (which will also remove the power) and connect a USB cable from a PC to reflash working firmware without needing to remove the display or disassemble any other part of the project. But this means we need to create a custom microUSB cable.
We don't need data for this cable (just power) so you can use a power-only cable or a data cable. Just cut-off one end (not the microUSB end!), strip it back and expose the two power wires (normally always the black and red wires).
If you have more than a red and black wire, your cable is also a data cable, but we won't be using the data lines, so they can just be cut off. The power leads will connect back to the Wago connectors for the main power supply. We will repurpose the cable in the final version, so it's better to have a longer cable at this point which can be trimmed back to final length later. For now, don't make the cable any shorter than about 10" / 25 cm.
With all our cables created, we can now start wiring everything together.
Wiring Notes
With the custom cables built, the connections are pretty straightforward. Connect the JST-to-Dupont cables to the proper JST connectors on the CYD. Then connect the Dupont end of these cables to the proper pin on the external component. For other connections, like between the level shifter and DFPlayer, female-to-female Dupont jumpers can be used for now.
Speaking of the logic level shifter, note that we are using a different type of shifter than we used for the LED strip on the primary controller. For this level shifting, we need a bi-directional shifter to 'translate' the logic voltage between the 5V used by the DFPlayer and the 3.3V used by the CYD/ESP32. This particular type of shifter is bi-directional while the one used for the LEDs is only one-way. In addition, while the LED shifter is theoretically optional (but recommended), this shifter is required. Omitting it will likely lead to an inoperable MP3 player and can even potentially damage the CYD/ESP32 by feeding it too much signal voltage. And while this type of shifter really isn't suitable for LEDs where precise signal timing is required, it is perfectly fine for this use case.
Also note that the shifter requires both a 5V and 3.3V power connection for reference. While I show splitting off other 5V or 3.3V leads, the truth is that you can source the power for the shifter from pretty much any source. 5V and 3.3V could come from the 5V power and 3.3V power rails of the primary controller. Or you could use the unused 5V/GND connections from the JST serial connection on the CYD. It really doesn't matter where you source the 5V/3.3V for the shifter.
The DFPlayer however, should be powered directly from the power supply. This is because the player can draw a bit of higher current when it is initialized or when audio firsts start playing. Just use care when connecting your RX/TX lines for the DFPlayer. It is common to mix these ups and then wonder why the sound isn't working. The RX line from the CYD (GPIO21) passes through the shifter and connects to the TX line on the DFPlayer. And TX connects to RX. If you end up attempting to connect RX-to-RX and TX-to-TX, you will not get any sound.
Once wiring is complete, you should now have all three controllers (bulb, primary and display) flashed and onboarded to your WiFi, you are ready to start actually testing the system.
Above is my original bench test setup. Do note that I omitted the LED shifter for the bench test (but will include it in the final version). But I do have the shifter between the CYD and DFPlayer (just off the bottom of the frame). Do not omit this shifter, even for bench testing or likely will not get sound and could even potentially damage the CYD/ESP32.
Preparing a microSD Card for Audio Sounds
For alarm sounds (or other sounds that can be played via MQTT/API), you must prepare an SD card and copy any desired audio files, in a very specific order and with very specific names. This is a requirement of the DFPlayer DFMini and not the firmware.
MicroSD Card
- First, you will need a microSD card between 8-32 GB in size.
- The card must be formatted as FAT32 and contain no other folders or files
- Since the card will primarily be read to play audio and have limited writes, you don't really need a high-end class 10 microSD card, but you can use one if you like.
Preparing the Audio Files
Any standard mp3 file can be used. During an alarm event, the sound will be looped. While you can certainly use a full .mp3 music track, shorter clips or sound effects work best. Ideally, any tracks used should be 1-60 seconds in length or trimmed to that length, although this technically isn't a requirement.
Unfortunately, for proper audio selection, all your files need to be named sequentially, starting with track 0001.mp3. So if you have five .mp3 files you wish to use, these files must be named:
0001.mp3
0002.mp3
0003.mp3
0004.mp3
0005.mp3
Theoretically, you can use up to 255 audio files, but only the first 20 will be available to select as your default alarm sound. Any files beyond the first 20 can still be played via MQTT or the API, but they won't be available to select as the alarm sound.
You'll be able to select from the list of audio files in the app to indicate the alarm sound you wish to use. But selecting from a list of numeric values, like 0003, isn't very descriptive and you may not remember what number each track is. Not to fear! We'll add track names back to the files within the app, so keep a note of which file is which track! But first we need to copy the files to the microSD card.
Copying the Audio Files
So not only do the files need to be named sequentially, they also need to be copied to the microSD card sequentially. If you 'bulk copy' all of your .mp3 files, there is no guarantee that the operating system will actually copy the physical files in order, so that 0001.mp3 is physically located as the first file on the microSD card. The only way to assure this is to, unfortunately, copy each file in order, one at a time to the root directory of the card. Luckily, you only need to do this once and don't need to repeat the process unless you later want to add or change the audio files.
Providing Track Names
As mentioned, having a sequential numeric list of audio files isn't very user friendly! But using the web app, you can assign track names back to the numeric audio files. Full use of the web app is covered in the Github Wiki, but I'll briefly cover the naming process here so you can go ahead and set it up for your bench testing.
From the main menu of the web app, select the 'Display' option (the alarms and sounds are handled by the display controller):
Then from the display controller's menu, select 'Alarms':
At the top of the alarm settings page are the sound settings.
If the Use SD Card box is unchecked, check the box to enable the card. Then you must Save & Reboot the controller to write this to the config file and enable the DFPlayer. After the reboot completes, return to the alarm and sound setting page. If the box is already checked, then skip step 1 and 1A.
Click the 'Library' button will is used to apply track names to your numeric audio files.
 |
| Click to enlarge |
For each of your audio tracks on the microSD card, provide the physical numeric file name and a descriptive track name. Note the index column. This represents the physical order on the microSD card and this may not align with the file name order if you bulk copied your files (see above). If you find during testing that file '0004.mp3' is actually physically located in index slot 7, then simply update your library list above to match the physical layout. This can generally be avoided by copying your mp3 files to the SD card one at a time and in order.
The track title can by any descriptive name you desire (it doesn't have to match the true track title). As you can see above, my first 8 tracks are music, while the last 9 are sound effects. I opted to add an "FX" to the track name to identify these as sound effects instead of music tracks. But the naming scheme used is up to you. While not technically enforced, I recommend that you generally keep your track names to around 20 characters or less. They just need to be descriptive enough to identify later.
You can name up to the first 20 tracks as alarm sound sources. If any tracks are unused, then the file name should be blank and the track title must be 'empty' (track names cannot be blank).
Once complete, be sure to click the 'Update' button to write your changes to the audio configuration file (you do not need to reboot after this step).
Once you have your 'library' defined, you now have track names available on the alarm setup page.
You can even test out your sounds (to assure your mappings are correct) by selecting any sound from the list and using the 'Test Sound' button. Just assure you have the alarm volume set loud enough to hear the sound!
Full details on sound setup and use can be found in the Wiki. But with the microSD card configured and inserted into the DFPlayer, you are ready for full bench testing.
Power Up and Bench Testing
First, assure the bulb has AC power. Then simply connect the single barrel jack lead to the power supply. This will power everything else. Do note that when powering on everything at once, the boot will take a few minutes to complete as each controller waits until it can "hear" the other controllers. This boot process is describe in more detail later in this article and in the Wiki if you are interested. But assuming you properly setup the integrations as shown earlier (and haven't changed any other settings), you should see the following during the system startup-process:
Bulb and Primary Controller:
- LED Strip will briefly flash red, green and blue, then turn off
- Bulb will briefly flash red, green, blue and white, then turn off
- Touch sensors are initially disabled by default
Note that these boot defaults can be changed via the app and you can disable the boot checks and set either the bulb or LED state to remain on after boot.
Display Controller:
- Boot checks and progress shown on the display
- Note that the boot will pause while waiting on the primary controller to respond
- Initially, many features may be disabled by default, such as touch, time synching, MQTT, etc.
- Without a time source and sync defined, time will initially be set to midnight, January 1, 2026. The time will always reset to the value until you define a source/sync method.
- If the clock is shown, then the boot process is complete.
If something fails the boot or the controllers fail to connect to one another, check out the Troubleshooting section of the Wiki.
Testing Methodology
If all three controllers (bulb, primary and display) and the boot process completed, all settings, options and features should be available via the web app (go to the IP address of the primary server in a browser). However, to test your hardware, wiring and various components, I recommend that you at least try the following (instructions for each of these processes are described in the Wiki, so I won't repeat the steps here):
- Set the time source or manually set the time
- Syncing to an NTP time server is the recommended, and most accurate, time source. If you do not want your project to connect to the Internet (or it is blocked by your network) and do not have a local NTP time server, you cannot use this option.
- MQTT and the API options require an external system that publishes the current time each minute to a broker (MQTT) or a system that publishes the time each minute to the clock's API. MQTT also require setup and configuration in the app. For initial testing, neither of these options are likely configured for use yet.
- Manually set the time. This can be used for testing, but isn't recommended for permanent use. When you manually set the time, it is kept internally until a reboot or power loss occurs. After powering back up, the time will be reset to the default midnight, January 1, 2026 and will have to be set again.
- Enable Touch Sensors and try a few different functions
- Each touch sensor has a primary function, which is active whenever an alarm is not sounding, and an 'alarm' function that is active only when an alarm is sounding. This means that for normal use, you can use the touch sensors to, for example, toggle the bulb and LED strip. But when an alarm sounds, these touch sensors can be used to snooze or stop the alarm.
- Try controlling the bulb and LED strip through the various options:
- The web app can be used to toggle a light off/on, set the brightness and color and in the case of the bulb, switch between color RGB mode and white temperature.
- Also try using the display touch screen. Touch the 'gear' icon in the upper right corner. You should be able to toggle the lights on from this initial settings page as well (note there may be a delay between the light illuminating and the state updating on the display... this is normal).
- Try controlling the display brightness from both the web app and the display touch screen settings page.
- Note that you will want to toggle off auto-dim for testing.
- You could also try configuring the auto-dimming ranges and test that by covering or shining a bright light on the light sensor and verify that the display brightness automatically adjusts.
- If you didn't try it earlier and you've setup your microSD card with sounds, try testing the sound output via the Alarm and Sound setup page.
- Be sure you've turned up the alarm volume.
- Be sure you've properly copied your sound files to the microSD card.
If all of the above are successful, you've pretty much verified all the hardware and wiring connections are working. Feel free to experiment with settings and options. See the
Github Wiki for complete details on using the system.
A few additional bench testing notes:
- Until a source is configured, the outdoor temperature will show 0°.
- Some features are disabled by default and you will need to enable before use.
- MQTT will not be available as a source or for any other use until the MQTT setup has been completed.
- The API can be used at any point by sending the API commands to the IP address of either the primary or display controller from any browser on the same network.
Creating the Final Soldered Version
Once you are satisfied with the bench testing, it's time to move off the breadboards and create the soldered versions that will make up final version. I highly recommend that you have the enclosure and approximate component layout available. Or at least a template (I used scrap cardboard as you'll see a bit later). This will assist in cutting final wire leads to length and also assure that everything fits as expected. I'll be using my 3D printed enclosure design and parts for the remainder of this article, but if you are using a different design, just adapt as needed.
In addition, and this is covered in a bit more detail under 'Assembly' below, I opt to use connectors between components. For example, I use JST connectors between the primary controller and the LED strip. I use Dupont connectors for the light level sensor, etc. This requires a little more upfront work, but down the road it would allow me to upgrade/replace various components without any desoldering required. I can replace the light level sensor, speaker, the display, the primary controller ESP32, touch sensors, etc. without any desoldering or removal of other components. Of course this is optional. If desired, you can omit most of these connectors and directly solder components together but this may make future maintenance or upgrades more difficult.
Wire Gauge
As a general rule and unless otherwise noted, I use standard 20 gauge solid core wire for all point-to-point connections on the ElectroCookie and either 20 or 24 gauge stranded wire for all external connections. The only exception are the primary power wires, which use 18 gauge stranded wire.
Primary Controller
First, we'll use a standard 1/2 size ElectroCookie board in place of the breadboard. To avoid the need for a double-wide board to support the width of the ESP32, we'll use 2 15-pin female headers.
If you are following my wiring diagrams, then the pin headers should have the first pin located in through-holes J3 and B3. Using pin headers not only allow wiring via the through-holes under the ESP32, but since my ElectroCookie board will be mounted on standoffs inside the enclosure, it also allow me to route wiring on the under side of the ElectroCookie.
I'm also opting to use a socket for the level shifter. This is optional, but if used, it also allows me to replace a faulty shifter without desoldering. Again, if following my wiring, the socket (or the shifter if omitting the socket) should have the first left most pins located in through-holes "F21" and "E21".
Now make all the point-to-point wiring connections on the ElectroCookie board to match the breadboard version. If your ElectroCookie will also be mounted on offsets (elevated from a flat surface), you can route wiring on the or bottom of the ElectroCookie.
 |
| Primary Controller - Click to enlarge |
Carefully verify that you are connecting the wiring to the proper GPIO pin for your ESP32, remembering that all ESP32s do not have the same pins in the same location. It is critical that your wiring connect to the proper pin. How you opt to route that wiring is arbitrary and up to you. A few notes on how I did it.
Because of no through holes on outside of the top pin header, the 5V and ground connections are made on the underneath side of the board and soldered from the top. All other wiring is on the top side and soldered from the bottom.
I initially made all external leads (power, LED data and touch sensors) around 10-12" long. If you already have your enclosure and know your component layout, you can cut the wires a little closer to size, but leave an inch or two of extra wire. We'll add any necessary connectors a bit later. For the touch sensors (and my design), these leads will need to be approximately 10" (25 cm) long as they will be attached to the underside of the lid and we want to be able to raise the lid to get to the internal components without disconnecting the touch sensors.
 |
| My primary controller soldered version |
Soldering Tip: If you are newer to soldering, use caution when soldering wires or pins that are adjacent to one another. It is very easy to inadvertently create a "solder bridge" between two points.This could also be caused by something as simple as a splayed wire from a stranded wire. An inadvertent bridge like this will likely cause your project not to work and worst case could damage components. If necessary use a magnifying glass to assure an air gap between adjacent solder points. Remember... heat the components and touch the solder to the component and don't heat the solder directly with the soldering iron!
Display Controller
If you created your JST and USB power cables for the bench testing, the good news is there isn't a lot we need to do for the display controller. We might need to trim/extend some of the lead wires and maybe add better connectors once final lengths are known. While theoretically not necessary, I broke out the DFPlayer and the associated level shifter and mounted those on a smaller 1/4-size ElectroCookie board.
This is done for a couple of reasons. First, for the bench test, you likely just have the shifter and DFPlayer connected via Dupont cables and just sort of dangling off the display board. This isn't very secure and a wire could easily be knocked loose. Secondly, we will mount this small daughter board inside the enclosure so that the microSD card slot is easily accessible to the SD card can easily be swapped out. Disconnect any temporary leads to the DFPlayer and the level shifter. Like the primary controller, I opted to mount both the DFPlayer and shifter on pin headers so that they can also be easily removed/replaced.
After mounting the pin headers, then complete the wiring as follows:
 |
| Click to enlarge |
- Dotted lines indicate where I ran the wiring on the underneath side of the board.
- Carefully note the orientation of both the DFPlayer and level shifter. They will fit the pin headers in either orientation, but if reversed, the system won't work or it could even damage the component.
- Remember that the RX-TX are 'flipped' between the display controller and the DFPlayer.
Trimming Wires and Adding Connectors
At this point, you should have soldered versions of the primary controller, the display board with JST/USB leads attached and the smaller break-out DFPlayer and shifter board. Now we need to trim these lead wires to length and add connectors so the system is "modular" and individual components can be removed or replaced if needed.
If you haven't printed or created your base enclosure yet, you can substitute something like a scrap piece of cardboard with the approximate component locations and use this to estimate wire lengths needed.
 |
| Click to enlarge |
Where you place your components and how you physically connect the wiring is somewhat arbitrary and up to you. If you are using a template to measure wire lengths, leave an extra 1"-2" to allow for potential routing issues in the final enclosure and to leave a little extra for stripping the wire and adding connectors.
You may also need to make some minor modifications to some components, like changing the pin header location or orientation. One change I had to make was to replace the photoresistor on the light module so that it could be extended through the enclosure (you can see it's position in the lower right corner of my cardboard template above).
I simply desoldered the original photoresistor and added one with full length leads. This allows mounting of the module inside the enclosure with enough lead length to extend the actual photoresistor to the external hole in the enclosure.
I'll get into the actual final assembly in a bit, but here's a photo that shows my initial wiring in the enclosure and the use of different types of connectors and the extended photoresistor.
 |
| Click to enlarge |
I personally like JST connectors. These are "keyed" to fit together in only one way, so you can't inadvertently reverse the connections. By using the number of pins and reversing the male/female leads, you can make everything nearly 'idiot-proof' and assure nothing gets connected inappropriately.
Regardless of whether you use JST, Dupont, spade connectors or even additional Wago lever nuts, you need to make the following connections between controllers and components. I'm showing the connectors that I used, but you can substitute other types, as long as the correct electrical connections exist.
 |
| Click to enlarge |
A few notes about the above:- All power connections directly to the power supply (primary controller, display controller, DFPlayer daughter board and LED strip) just have stripped wire on the lead since these will all connect to the Wago lever nuts.
- If you plan to mount the touch sensors under the lid as I did, then leave an extra 4"-6" of wire leads so that the lid can be lifted without pulling or disconnecting the wire leads.
- For other leads, I recommend leaving at least 1"- 3" of extra wire should you need to route some wiring around things like the bottle mounting posts. There is plenty of room in the enclosure, but you also don't want an extra 10-12" of wire for each of the connections.
- I tried to "key" my connectors so things would only connect in one manner. For example, the two 2-pin JST connectors from the DFPlayer board use a female connector for one set and a male connector for the other. This prevents me from inadvertently connecting the wrong leads together and damaging a component.
- The Dupont connectors, however, could still be "reversed", so I opted to mark these to assure I always oriented the connector properly.
- The larger 2.54mm JST connector was sourced by cutting the ending JST connector off the LED strip (which will have to be done eventually anyway... see the top assembly section below).
So that's all the 5V DC wiring. But you might be wondering, "What about AC power for the main light bulb?". Also note that I haven't really covered the USB charging ports either. That's because we are also going to power the USB charging ports off of the AC power.
Splitting DC and AC Power
My initial plans called for a single 120V AC power source for the entire build. I was going to use an AC-to-DC step down transformer to convert the AC to 5V DC for all the DC components.
 |
| AC-to-DC Transformer Module |
But part of the problem is that these modules can only handle up to 3A. This simply wouldn't be enough power for all the 5V DC components, plus the two USB charging ports. This means I'd need at least two of these transformers, which are bulky and can generate a fair amount of heat.
After quite a bit of discussion with @Radio on Discord, he convinced me that it would not only be simpler, but much more safe to have completely independent AC and DC circuits. And I wouldn't need the transformers. He convinced me that this was a better and safer route, so that's what I ended up doing.
Yes, this does mean that I will now need two AC outlets instead of one.
But one of my primary goals was to reduce the number of AC outlets needed. What's up with that? Well, I'm still reducing the number of outlets that required a minimum of three, since I need to charge two devices but my original Lenovo clock only had one charging port. By reducing the required outlets from three to two, it also allowed elimination of a power strip.
In the end, and based on discussions with @Radio, I opted for safety by isolating the AC and DC circuits. For me, safety overruled the reduction in AC outlets needed. Ultimately, it is your call whether you wish to use a single AC power cord and transformers inside the enclosure.
AC Circuit Wiring
By isolating the AC from the DC, it also greatly simplifies the wiring for the AC components.
 |
| Click to enlarge |
Wait... what? Yes, I'm still using two small AC-DC transformers for the USB charging ports. But these transformers are much smaller and are only used to support USB charging and none of the other DC components. Each of these transformers can handle up to 2A each for charging and will not produce any appreciable heat when not in use.
Note that I tested these before final installation to assure I was getting a reliable 5V DC output (I sure didn't want to destroy my phone by plugging it in to charge):
For the AC cord that powers the light kit, I simply took the power cord that came with the kit and cut off a portion on the end that will run from the Wago power connections in the corner of the enclosure, up through the hollow center shaft before connecting to the light socket. The remainder (the portion with the AC plug) when from the Wagos to the AC outlet. Of course you could build your own power code, but just assure the wiring used is rated for the expected voltage/current.
This does also mean that I'll need two holes in the enclosure for the two power cords:
Sharp-eyed readers may also notice a third hole. This is for future expansion that I'll mention towards the end of this article. But with all the wiring and connectors completed, it is time to start final assembly.
Final Assembly - Putting it all together!
The final assembly process will be highly dependent upon the enclosure you opt to use. For this article, I'll be using my original 3D printed parts. If you are using a different enclosure, simply adapt my methods for your design.
NOTE: If you are reusing the bulb and controllers from the bench testing, these should already be flashed and onboarded to your WIFI. But if you skipped the bench test or are using a different bulb, ESP32 and/or display, I highly recommend that you complete the flashing (primary and display controller) and the WiFi onboarding processes for all three controllers (bulb, primary, display) per the information above before you add them to the assembly.
Bottle Selection
The one thing I really haven't mentioned at all yet is the frosted bottle that makes up the "body" of the lamp and will also house the LED strip. Selection of a bottle is completely personal. I originally considered using a frosted 1/2 gallon (1.75L) liquor bottle, such as Grey Goose, Belvedere, etc.
 |
| Example Bottles |
I chose the half gallon (1.75L) size just because I felt the larger bottle looked better with the size of the base. You could use a normal fifth (750 ml) bottle if desired. You'd just need to adjust the size of the lid opening to fit the bottle. Of course there could be some serious expense in purchasing half-gallons of vodka just to use as a lamp body. Plus you'd have to either wait until all the vodka is gone or transfer it to something else. In addition, we are going to have to cut off the bottom of the bottle (or drill a hole large enough to accommodate the square center shaft). And as I found out, cutting the bottom off a bottle takes some practice. So if your first attempt doesn't work out, you are buying another bottle of liquor and either waiting until it is gone or transferring it to another container.
Since I designed the system so that the bottle could actually be replaced with minimal effort (and no desoldering or disconnection of anything but the lamp power), I opted to order some plain clear half-gallon clear bottles. They came in a six pack for less than the cost of one bottle of vodka. This turned out to be a good idea!
First, I wanted a frosted glass bottle so that the LEDs would give more of a glow and not show individual pixels (I also used a COB LED strip to help with this). But this takes a little practice to get a nice even "frosting" that isn't streaky. Of course you could just use a clear bottle if desired, but you probably want to stay away from green or brown bottles as they will mute the LED colors too much. I also discovered that I had to cut the bottle before painting or the frosted glass paint would haze up to a solid color during the cutting process. You can see this the third bottle from the right above... which also cracked when cutting. I also tried white spray paint, but it turns out trying to get a nice even coat of paint on a round surface isn't that easy!
As you can see, the frosted version is much less streaky and splotchy. The more coats of frosted paint you add, the more "glow" from the bottle. I lost count, but I think I applied between 6 and 8 coats (used about 3/4 of a can of the frosted spray paint).
IMPORTANT NOTE: If you are going to apply your own frosted paint to a clear bottle, cut the bottle before painting! If you use the same technique that I used to cut the bottle, this process will ruin the frost coating as shown in the previous photo above!
Cutting the Bottle Bottom
With a bottle selected, it is time to cut off the bottom so that we can run the center shift with the lamp's power and the LED strip up the middle. There are numerous techniques you can search for on the web, but the most common involves scoring the bottle and then shocking it by alternating between boiling water and ice water until the bottom simply cracks along your score line and falls off. Sounds simple, right?
I opted to purchase a bottle cutting kit. The process is more easily seen in the video, but here are the highlights of how it worked. Be sure to wear gloves and eye protection.
The first step is to use the kit to score a line completely around the circumference of the bottle.
You don't actually "cut" the bottle with the tool. You are just scoring a line. Just assure this line is straight and meets evenly.
Next, you place two rubber O-rings (provided in the kit) just on each side of the score line. Heat up a pot of water to boiling and have a large glass measuring cup on hand to scoop out the boiling water as needed. Also prepare a pitcher of ice water.
Now alternate, in about 30 second intervals, pouring the boiling water and ice water along the scored line. A second set of hands is helpful here. Continue alternating hot/cold water and eventually the bottom will simply fall off, hopefully cleanly along the score line, due to thermal shock.
Be prepared! It is actually a bit of "shock" when it happens. But as mentioned, this does take a bit of practice. It is critical to try to keep the water focused on the area between the O-rings and over the score line. If you don't keep the heated/cooled area primarily along the scored line, then something like this is very likely to happen:
I did not get a clean break in this example. This was partially due to the fact that I tried to paint this bottle before cutting, but mostly it's because I didn't evenly heat and cool the bottle just along the score line. It probably took three bottles before I got the technique down. But once I figured out the best process, I was able to cleanly cut three bottles. My kit also came with a small polishing cloth that, along with a little water, lets you smooth the cut edge a bit so it isn't too sharp.
Once the bottle is cut, THEN AND ONLY THEN, can you apply any paint, frosted or otherwise. Although spray painting a round bottle can be almost as difficult as cutting it!
After trying different techniques, I ended up 3D printing a small stopper with a hole so I could hang the bottle from a hook. This allowed me to rotate the bottle and try to get a nice even coat. Any streakiness will show when internally illuminated by the LED strip.
Base Component Assembly
With all parts prepared, it's time for the final assembly. The first step is to install the magnets that will hold the lid to the base (while still allowing it to easily be removed).
Both the base and lid have small offsets where the magnets go. Carefully note the magnet polarity before installing! You want the lid and base to attract to each other... not repel! But to install them, just touch the tip of a hot soldering iron to the magnet, pressing down just slightly. As soon as the magnet heats up, the plastic will melt and the magnet will embed in the plastic. Just don't press too hard or for too long! You just want the magnet flush with the top of the plastic surface and not countersunk. If any plastic oozes up around the magnet, just trim or sand it off. The goal is to have magnets that just touch without leaving a gap. Install the magnets in all four corners of the base and the lid.
 |
| Click to enlarge |
The above shows the general placement of the base components. There are small offsets for the primary controller board and the DFPlayer daughter board. These accept M2 screws.
Use care when working around the taller bottle platform posts! These have good vertical strength, but very little horizontal strength and bumping too hard could snap one off... and then you are looking at repeating a 40+ hour print job! We will add a mid-platform support to these posts shortly.
There is a small mount for the light level module on the right front side. This rests on the shelf and is held in place with a little hot glue. The actual photoresistor fits into the small opening on the front of the base and should be flush with the front face when installed. Apply a little hot glue to the back side if the photoresistor doesn't want to stay in place.
My 3D printed design does not contain pre-configured holes for mounting the display or speaker. This was intentional as I find a get a better fit by putting the component in place, marking the precise hole locations and then drilling the hole. The display can accept up to M3 screws and nuts, while the speaker will vary depending upon model, but those with mounting tabs will generally accept at least an M2 screw and nut.
 |
| All base components installed - Click to enlarge |
This is my project with all the components installed in the base, but without all connections made yet. Notice the addition of the mid-shelf support. I recommend adding this right after installing the primary controller. This part adds a little horizontal support to those mounting posts. If I were to print the base again, I'd add infill and a more robust attach point to these posts. Go ahead at this point and make your wiring connections, but obviously don't connect the system to main power yet.
USB Charging Ports
The 3D printed parts are designed to house a female USB port with a small flange on the front and two bendable tabs on the rear.
Insert the USB Female jack into the front side of the mount until the flange is flush with the front edge. Then bend the two tabs outward against the back side of the mount to hold it in place and assure it doesn't get pulled out of the mount when unplugging a USB cable. Then simply insert the mount into the large round mounting holes at the back of the base enclosure and tighten into place with the 3D printed nut.
Complete the wiring for the USB ports, including the lines from the transformer back to the power junctions in the corner of the base. Once power is connected and the build is complete, I recommended testing both ports for the proper voltage (as I showed above) before connecting any devices. If you don't have a USB tester, you can just use a multimeter to assure the port is outputting the expected 5V.
Lid components
Next, attached the touch sensors to the recessed locations in the lid.
Remember that since you are looking at the bottom of the lid, the sensor position will be reversed when the lid is flipped over. Assure the sensors are placed with the "touch side" facing down or towards the top of the lid. Apply a little hot glue to hold the sensors in place, but do not attach the wiring leads yet. We'll need to slide the lid over the bottle before attaching the leads.
At this point, other than the touch sensors, all component wiring should be completed, this includes the incoming DC and AC power lines... but don't connect either source to the actual power yet.
Bottle Platform and Center Shaft
The center shaft is printed it two parts that must be joined together. Use a little super glue to hold the parts together and let it dry completely. The shaft is intentionally a little longer than needed so it can be cut and used with bottles of slightly different heights.
Note that all the top components are designed to be a snug fit. No glue will be used, (with the exception of the two center shaft pieces and possibly the bottle adapter) so the top portion can easily be disassembled to access the base components or even to replace the bottle or LEDs.
To determine the length of the shaft, first place the bottle platform, top side up, on a flat surface.
As you can see, the center shaft is inserted into the larger square on the top of the bottle platform. The smaller notch is designed to accommodate a 5mm wide LED strip*, such as the COB strip I'll be using.
*If you want to use the more standard 10mm wide LED strip, you'll need to adapt the platform design or pass the three LED wires through the notch and then attach these wires to the LED strip and/or JST connector. If you use this method, note that you will not be able to remove/replace the LED strip without desoldering the wires from the LED strip or by removing the JST connector. I recommend either a 5mm wide LED strip or that you modify the bottle platform design to permit a 10mm wide strip to pass through the notch.
 |
| Click to enlarge |
Insert the center shaft into the bottle platform. This is a snug fit, but assure the shaft is resting on the bottom of the shaft opening. Gently place the cut bottle over the shaft, noting the the shaft will likely stop at some point as the neck of the bottle tapers and before the bottle rests on the platform. Note where the shaft stops and use a hacksaw, rotary tool, etc. to cut the shaft off approximately 1/8 - 1/4" shorter than the 'stop point'. You want the shaft to extend as far up the neck of the bottle as possible while still allowing the bottle to fully rest on the platform.
By assuring the shaft extends as far as possible into the neck of the bottle (yet not actually supporting any of the bottle's weight), it not only allows the LEDs to extend as far as possible up the neck, but the neck of the bottle also laterally supports the top of the shaft and prevents it from wobbling at the top.
 |
| Click to enlarge |
There's a lot happening in this photo, so let me break it down. First, I while the photo shows the platform already installed in the base, I recommend that you install the LED strip and pass the AC power lines up through the center of the shaft before mounting the platform into the base.
For the COB LED strip, you must first cut off or remove the ending JST connector. This will be the larger of the two connectors. You could theoretically use the ending JST connector as the connector for the joining power and data line to the start of the strip (see connector section above). But note that the COB LED strip can't be cut just anywhere.
 |
| Click to enlarge |
It might be a bit hard to see in the photo, but if you look closely at the top of the COB LED strip, you will see small notches that indicate safe cut points. Alternatively, you can remove a small portion of the adhesive covering on the back. The strip can be cut in the middle of any of the copper pads. But it important that you cut the proper end. With addressable LEDs, the data only flows in one direction. If you cut off the wrong end, only the first LED in the strip will light up. You want to cut off the end with the larger JST connector (shown in photo above). Another check is to look carefully at the back of the strip. When cutting the portion that will discard should be on the 'OU' or 'OUT' side of the copper pads.
Once the ending connector is removed, you can feed the cut end up through the bottom slot of the platform. Feed the LED strip through until the point that the starting JST connector is just at the top of the platform. You can then determine the final length of the LED strip that will just fit onto the center shaft and cut off any excess LEDs from the end of the strip. See the photo above.
I opted to place a strip of double-sided tape between the shaft and the adhesive on the back of the LED strip to assure they don't come loose over time, but this is optional.
Pass the AC wiring from the the power block area of the base up through the hollow center shaft and out the top. Leave around 4-6" of extra wire so that there will be enough slack should you need to disassemble the top portion.
You can now carefully install the bottle platform onto the mounting posts. Be sure you've already installed the mid-platform support first.
 |
| Click to enlarge |
The platform is designed to only mount on the posts in one orientation. The LED strip should be pointing at the rear of the enclosure, opposite of the display. Be sure to support the vertical posts to prevent any horizontal stresses during the platform installation. The platform should seat all the way down, be level and around 1-2" lower than the top of the base enclosure. The fit is intentionally snug, but if you have issues getting the platform to be level and fully seated on the mounting posts, you may need to slightly drill out or clean up the holes in the platform just a bit. This gives you an idea of how it should look before the bottle is installed.
Don't connect the touch sensors or actually install the lid yet. You can check it as above, but then remove the lid and set it aside again.
If you haven't yet done so, be sure to make all your electrical connections are complete, including the incoming AC and DC power lines, but don't connect the actual power yet.
Now gently place the bottle over the center shaft and let it rest on the platform, assuring you still have at least a couple inches of the AC wire extending out of the top of the bottle. Test the lid again and assure it slides over the bottle and mates flat with the base. There should not be a large gap between the bottle and the lid opening, but you don't want it to bind when sliding up and down. If the fit is good, go ahead and remove the lid one last time.
While optional, I recommend that you place a rubber O-ring on the base of the bottle so that this rests just against the underside of the lid (without preventing the lid from completely mating with the base).
This serves two purposes. One, it seals any small gaps between the lid and the bottle. Next, it can serve as an "early warning". The bottle isn't physically attached to the base... it just rests on the base. This facilitates disassembly should you need to work on or replace any components in the base, or even swap out and replace the bottle. But because of this, the entire system must be lifted by the base. If you attempt to lift the entire build by the bottle, the O-ring will cause the lid to pop off (and remind you not to lift by the bottle) before any wiring gets pulled or anything else gets damaged. But adding the O-ring doesn't impact functionality, so the decision to include or omit it is yours.
Now place the bottle back on the platform, slide the lid over the bottle and this time go ahead and connect the touch sensor leads. Assure the lid seats properly into the base and that there are no visible gaps between the lid and the base.
Lamp Kit and Final Assembly
The last thing to do is to wire and add the lamp kit to the top of the bottle. The 3D printed bottle adapter should screw onto the lower portion of the light socket assembly.
Feed the two AC leads from the top of the center shaft through the shaft of the light fixture, then insert the fixture with adapter into the top of the bottle.
The adapter should be snug enough that the light fixture isn't 'wobbly' nor should it easily rotate. If your fixture or bottle are slightly different, you may need to modify the 3D design or adapt the existing part with a bit of tape or even a little hot glue. Do not use more permanent methods like super glue or you won't be able to disassemble should the need arise.
Wire the socket to the AC wire leads, and assembly the light fixture according to its instructions. Place the RGBW bulb in the socket and add a lamp shade if desired.
Final Steps and Results
I added rubber feet to the bottom of the base to prevent the device from sliding or scratching when sitting on thee wooden surface of the nightstand, but this is optional.
 |
| Final assembled project |
Power Up!
If your lamp assembly has a switch, assure it is in the "on" position (test with standard light bulb if needed). You will always leave this switch in the "on" position, but you can use it to manually reboot the bulb if necessary by switching to the "off" position and then back to 'on'.
Connect the AC power to mains, followed by the DC power supply. There is an internal boot process that established communication between the three controllers. The general process works as follows (more details in the Wiki):
- The RGBW light bulb boots up
- The Primary Controller waits on the RGB bulb to complete its boot
- The Display Controller waits on the Primary Controller to complete its boot.
As you can see, the RGBW bulb must complete its boot process before the other controllers can complete theirs. So when powering up the system, either apply AC power first or power on both sources at the same time. If you power on the DC power first, the controllers wait until the bulb comes online (or will eventually time out).
Using the System
If you completed the bench testing, you already have a bit of familiarity with accessing the web app. While the web app is the primary source, it isn't the only way to control many of the features.
Web Application
The web application is the primary interface for the system. In fact, it is the only way to configure some settings, such as configuration and system defaults. The web application can be reached by simply entering the IP address of the primary controller in a browser of a device on the same WIFI network.
While this is the main page from the primary controller, the display controller also has its own configuration and web app. As a general rule, you don't need to worry about this. If you use the app's buttons and links, it will automatically direct you to the proper controller page. You don't need to use the primary's IP address for primary functions and the display's address for display functions. Just go to the primary's IP address and the buttons will handle switching between controller apps for you.
However, there are a few functions and options where it is important to know which controller you are working with. This would include things like the command to reboot the controller. But there are multiple indicators to let you know which controller you are actively working with.
You can tell which controller you are using based on the following: - The web page will have a light grey background for the primary controller and a pale yellow background when accessing the display controller.
- The device name you assigned during onboarding will be shown in the tab title.
- The display controller will always have DISPLAY shown at the top of the page.
- You can also look at the IP address in the browser's address bar.
As mentioned, you generally don't need to worry about this. But there are a handful of exceptions. For example, on the main page of each controller's app, there are a system of system commands.
While noted on the page itself, most of these commands only apply to the particular controller is use. So when using these system commands, it is important to know which controller you are accessing. Again, it is easy to tell just from a glance that the top commands impact the primary controller (grey background), while the bottom commands apply to the display controller (yellow background).
See the Github Wiki for full information on installation, configuration and use of the web applications.
Touch Sensors and Touch Display Panel
Control of some aspects of the device can be done via the touch sensors and the touch display. The touch sensors can be configured for various control options, such as toggling the bulb or LED strip, changing the brightness of the bulb, LED strip or display, etc. Each touch sensor also has a secondary function for when an alarm is sounding to that it can be snoozed or stopped.
The touch panel can also be used to toggle the lights, change the display auto-dim and brightness and to schedule alarms.
MQTT (optional)
If you have an MQTT broker configured, you can control nearly every feature of the device (and receive current states) via MQTT. About the only thing you cannot do via MQTT are the basic configuration items (e.g. time source, weather source, etc.) or alter the general boot defaults... but there's even a way to do this too! Note that MQTT is entirely optional and not required for full system functionality.
See the Wiki for complete details, including all available MQTT topics and payloads.
Home Assistant Discovery
If you have an MQTT broker and are also a Home Assistant user, you may wish to integrated the device into Home Assistant for control and automation use. Instead of manually creating a bunch of MQTT entities via YAML, you can click a button to have the device and all selected entities automatically appear in Home Assistant with absolutely zero configuration on the Home Assistant side!
 |
| Click to enlarge |
As you can see from the above screen shot (subject to minor changes in later versions), you can select which entities you want integrated into Home Assistant and those you wish to omit. A single button click will add the device to Home Assistant. You can also remove the device from Home Assistant via a single 'Disable' button click.
Again, see the Wiki for full information on using Home Assistant Discovery, including a few prerequisites.
HTTP API
Finally, there is an HTTP API that can be used to interact with the device. Many of the same commands that can be used with MQTT can also be used via the API. The API can also be used to integrated the device into other automation platforms if MQTT is not available or supported. The primary difference is that the API is primarily used to send commands and not receive data back (there are a few exceptions).
An API command can be sent to either controller's IP address. It will be routed to the proper controller for you. So, for example, the command to turn the light bulb on can be sent to either the primary or display's IP address:
http://192.168.1.205/api?bulbstate=on #using primary controller IP
http://192.168.1.51/api?bulbstate=on #using display controller IP
So unless noted in the documentation, an API command can be sent to either controller. Under the hood, when a command is received by a controller, it checks to see if that command was for itself. If so, it executes it. If it doesn't find a match, then it forwards the command on to the other controller for processing.
Full details of the available commands and how to use them are covered in the Wiki.
Modifying and Compiling Your Own Firmware
As I mentioned at the beginning of this very long article, I built this device for my particular wants and needs. There is a good possibility that yours may be different from mine. Heck, maybe you only want the lamp portion or just the clock portion. This article, the video and even the Github repo are really meant more as a resource for developing your own version more so than building a one-to-on exact clone of my project (although you can do so and as long as you use the same hardware, firmware modifications shouldn't be required).
I tried to make both the physical device and the firmware as modular as possible. Many features, like the touch sensor and LED strip can simply be disabled in the firmware if not using. But other hardware additions, subtractions or replacement will likely require at least some modification of the firmware.
The source code for the controllers (and other files, such as the 3D part design files) can be found in the Github repo. I've tried to include substantial inline documentation in the code itself.
 |
| Code includes substantial inline documentation |
While I'm always happy to answer questions about the firmware, or to respond to bug issues, I simply do not have the bandwidth to develop and maintain multiple versions of the firmware to support different hardware or configurations.
Other than bug reports or feature enhancements (utilizing the same hardware), any other changes to the firmware are left to the reader to determine and implement. Sorry!
What I'd Do Different
Hindsight is always 20-20! Now that my project is complete, if I were starting from scratch, there are some design changes I'd implement.
Size of Base Enclosure
The overall size of the base could definitely be reduced, both in width/depth but also in height. When I was first planning the system, I was concerned about the overall design being top-heavy and having a tendency to tip over. So I wanted as wide of a base as I could fit on my 3D printer bed. Turns out that the overall design really isn't top heavy, so I could have reduced the overall footprint. I also didn't know how I was going to initially mount the bottle and I was concerned that if the bottle took up "floor space", there wouldn't be enough space for the other components. Turns out, I'd end up elevating the bottle, so the overall size of the base enclosure could have been reduced.
Bottle Platform and Mounting
I spend a lot of time trying to figure out the best way to mount the bottle in the system. I wanted as much of the bottle exposed as possible. I ended up using the mounting posts and platform as covered above. But this has a couple of major flaws:
- While having good vertical (z-axis) strength, the mounting posts are extremely fragile in the horizontal (x-axis/y-axis) direction. Even a slight bump could snap one off. I added the mid-platform support to assist with this problem, but if designing again, I'd either redesign these posts for better horizontal strength or even replacing these posts with wooden dowels.
- The bottle isn't physically attached to the base. This means that you cannot lift the device by the bottle or it will simply separate from the base. You have to lift the entire device by the base. My original plan was to drill two small holes in the bottle where cotter pins could be inserted. With a modification to the platform, this would allow the bottle/lamp assembly to be attached to the base, yet still be removed for maintenance. But to be honest, after the trial-and-error process of cutting and painting the bottle, I was too scared to try to drill holes in the bottle near the cut point for fear of cracking the already-cut bottle.
But the base enclosure was a 40+ hour print! Not something that I wanted to print a half dozen times, so I went with my initial design. But I'd definitely alter the enclosure and bottle mounting system if starting over. There are some firmware design changes I'd also make, but those are beyond the scope of this article and could be pushed out via a later firmware update.
Potential Future Enhancements
While I can't guarantee if or when I might add these to the project, I've already considered making the following enhancements to the system:
Adding Voice
Remember WAY up above when I talked about an extra power hole in the enclosure for future expansion?
One of my next big projects will be to move away from Google Assistant/Gemini to local voice. When complete, I want to add a voice speaker to the project using either an M5Stack Atom Echo or a Home Assistant Preview Edition.
 |
| Local voice options |
Both of these devices require a 5V DC power supply. Instead of using up yet another AC outlet, the voice device can be powered from the same 5V supply as the the rest of the project. This won't replace the internal alarm buzzer nor interact with the existing controllers, but it will turn the lamp into a satellite voice device that can also make announcements or even play music or white noise.
Additional MQTT/API commands
While the current MQTT and API commands offer a lot of control, there are still some potential commands that I've considered and may add in the future.
More touch panel control and options
Currently, the touch panel options are somewhat limited. You can toggle the bulb or LEDs off/on, change the display auto-dim and brightness levels and set or cancel alarms. That's about it. It would be nice to use the display for more control functions, but graphics and processing touch is a resource-intensive operation and the display controller's ESP32 is already near the maximum of what it can handle, so it may not be possible to add a lot of additional touch functionality.
Final Thoughts
This was, by far, my most complex single project to date. The wiring isn't overly complicated (although there is a fair amount of it), but the firmware is definitely the most complex Arduino/C++ code I've ever developed. And again, the odds of you wanting to build an exact clone of my version may be rare. But my version and the related video, code and this article can serve as a resource for developing your own version.
Certainly let me know if you build a version and how it worked out down in the comments. And of course I'm always happy to answer questions here, in the video comments or even in the Discussion area of the Github repo.
As always, thanks for reading!
Links and Additional Information
Github Repo (source code, 3D design files, application wiki, more)
Supporting this blog and related YouTube channel
It takes significant time and effort (and often expense) to create these articles and videos. If you'd like to support future content on this blog and the related
YouTube channel, or just say thanks for something that helped you out, you can consider buying me a one-off cup of coffee or two at:
No comments:
Post a Comment
To help eliminate spam and to keep all conversations civil, submitted comments are moderated. Therefore your post may not show up immediately. Please be patient as most reviews are completed within a few hours.