
${\color{#ff0000}S}{\color{#ff6e00}m}{\color{#ffdd00}a}{\color{#b2ff00}r}{\color{#48ff00}t}\ {\color{#00ff26}M}{\color{#00ff95}u}{\color{#00fbff}s}{\color{#0091ff}i}{\color{#0022ff}c}\ {\color{#4d00ff}L}{\color{#b700ff}a}{\color{#ff00d9}m}{\color{#ff006a}p} $
Table of Contents
Thinking it over, it was a shame to waste so much power on a simple task of turning a lamp on and off.
So what should I add to truly leverage the ESP32 and end up with a more complete and functional product?
Watching my wife cook while listening to music on her phone, which she would leave anywhere, the idea came to me: give the lamp an audio system (Bluetooth speaker). I told her, wouldn't it be great to cook while listening to music without having to leave your phone somewhere random? You'll be able to control the lamp and stream music from your phone via Bluetooth to the lamp, having music in the kitchen.
If I already have a lamp that plays music, why not make it more cheerful and add colors?
To do this, I added a Neopixel strip that is also controlled via the web from your phone, offering lighting effects including effects that sync with the music the lamp is playing.
Still not satisfied, I added a temperature and humidity sensor to display that data on the web interface embedded in the ESP32.
Since I live in a country where power outages are very common, and I had the ability to play music through the lamp, why not take advantage of it?
I implemented a dual power supply system (mains and battery) that allows you to keep controlling the lamp via the web and listen to music after a power outage. Of course, I couldn't use the Neopixels since they consume too much power.
- Control the lamp on/off from your mobile phone.
- Play music through the lamp via Bluetooth from your mobile phone.
- Control the Neopixel strip for different lighting effects.
- Monitor battery charge and usage.
- Monitor temperature and humidity.
- Web-based control of the Bluetooth speaker's physical buttons.
- Embedded web interface for real-time control from any mobile device.
- Multi-device control: multiple phones connected at the same time, one as master and the rest as slaves.
- Defined behavior on power loss, WiFi disconnection, and low battery.
Place a video of SML in action here
The ESP32 was chosen for this project because of its power, number of analog inputs, and Wi-Fi connectivity. It features dual power supply, using a 5V power source and a 3.7V battery. A protected charger handles battery charging, and a DC-DC step-up converter boosts the battery voltage to 5V to power the ESP32 and all modules except the Neopixel strip, which is always powered from the mains supply.
A dual relay module controls the LED lamp and the Bluetooth module. A DHT22 sensor measures temperature and humidity. Additional components implement supporting circuits.
For audio playback, I used one of those common portable Bluetooth speakers that can be found very cheap, like the one in the image below.
Required components:
- ESP32, any variant
- Battery charger module, TP4056 5V 1A with protection
- Mini DC-DC step-up booster MT3608
- 5V power supply
- Dual relay module
- WS2812B Neopixel LED strip, 24 LEDs
Note: The number of LEDs depends on the lamp size and power supply capacity. You can change this value in the code.
- LiPo 3.7V battery or 18650 3.7V Li-Ion
- Temperature and humidity sensor DHT22
- Resistors:
- 5 x 1K
- 3 x 10K
- 2 x 4.7K
- 1 x 220 ohm
- Capacitors:
- 2 x 0.1 uF
- 1 x 1 uF /10v
- Diodes:
- 2 x 1N4148
- 1 x 1N581x Schottky
- MOSFET transistors
- 1 x P-Channel MOSFET
- 3 x N-Channel MOSFET
-
Bluetooth speaker control board with its speaker
-
LED lamp to transform
Note: For this project I used a circular LED lamp, considering it more aesthetic and elegant. You can use any lamp that suits your needs and taste.
Below is a table showing the ESP32 pins to connect and their designations.
| ESP32 PINS | NAME | FUNCTION |
|---|---|---|
4- D4 |
STRIP_PIN |
LED strip control |
5- D5 |
VOLUMENUP_PIN |
V+/FF Bluetooth button |
18- D18 |
SWITCH_PIN |
Bluetooth power control |
19- D19 |
VOLUMENDOWN_PIN |
V-/REW Bluetooth button |
21- D21 |
PLAY_PIN |
Play/Pause Bluetooth button |
23- D23 |
DHTPIN |
DHT sensor control |
32- D32 |
LAMP_PIN |
Lamp power control |
33- D33 |
ADC_PIN |
Battery monitor |
34- D34 |
CHARGE_PIN |
Charging signal |
35- D35 |
FULL_CHARGE_PIN |
Full charge signal |
36- VP |
AUDIO_IN_PIN |
Audio input |
Note: Since Wi-Fi is enabled on the ESP32, you cannot use any ADC2 pins for analog readings.
The following schematic illustrates the power connections for the project.
Note: The output voltage of the lamp's power supply that feeds the high-brightness white LEDs is not the one shown in the schematic; it is much higher. It is not important to know it for this project.
👉 You can find the schematic here. ⭐
For a better understanding of the dual power supply (load sharing) used, here is the following diagram.
When 5V power is applied, this circuit turns off the MOSFET and stops current flow from the battery to the load (DC-DC module), effectively disconnecting the battery. As long as the
The chosen MOSFET must have the lowest possible RDS(on) to minimize power loss, must handle the current your circuit draws from the battery, and must have a VGS(th) between 0V and -2.4V.
The diode prevents current from flowing from the battery back to the 5V power supply. It must be a Schottky diode that can handle the maximum current draw of the loads. The RG resistor (10K) ensures the MOSFET turns on and connects the battery to the load when the 5V power supply is removed.
The DC-DC boost converter must be adjusted so its output voltage is 5V whether powered from mains or battery.
To monitor the battery voltage, place a resistive voltage divider at the output of the TP4056 charger module and connect its output to pin D33 of the ESP32.
With the excellent library by danilopinotti/Battery18650Stats, the battery voltage is read and expressed as a percentage. Since batteries in use don't always reach the ideal 4.2V maximum, danilopinotti's library didn't give practical values. So I modified the library to include configurable maximum and minimum voltage values in its parameters for more accurate percentage readings.
The TP4056 module has 2 indicator LEDs — one to indicate charging
As shown in the diagram, if pins 6 or 7 output more than 0V, the LEDs are off and diode (D17) does not conduct, so 3.3V reaches the ESP32 input. When the module is charging or the battery is fully charged, there will be a logic low on those pins. The corresponding LED turns on and diode (D17) conducts, reflecting a low level on the ESP32 pin. In code, this is represented by the variables
During testing I faced another challenge: when the Bluetooth speaker is turned on, the volume is always at half. This seems to be normal behavior for these Bluetooth speakers, so to control the volume we need to use the physical buttons. But since the lamp will be in an inaccessible location, we can't. And even if we turn up our phone volume to max, we may not achieve the desired level. So how can we remotely control physical buttons?
Analyzing the schematic of some common Bluetooth speakers on the market (Chinese ones), I noticed the buttons switch to ground (GND) on an IC pin. This makes it easy to emulate a button using the ESP32. The following diagram shows the solution.
The solution basically uses MOSFETs as simple switches — when activated, they ground the Bluetooth IC pin. In this Bluetooth speaker model, each button has dual functions depending on the press duration, which is also emulated in code. We only need to control 3 buttons: Volume+(FF), Volume-(REW), and Play(Pause). By controlling the pulse duration to the MOSFET gate in code, we simulate the button press duration.
The diagram also shows how to sample audio from the module's audio output. A voltage divider circuit is implemented, and the signal passes through a resistor and electrolytic capacitor to pin D36(VP).
As seen in the power connection diagram earlier, turning the Bluetooth module on and off is done through a relay (IN2) controlled by the ESP32 from pin D18.
Finally, the DHT22 temperature and humidity sensor connects to pin D23. This sensor can be powered with 5V or 3V, so there's no issue choosing either. For better temperature and humidity monitoring, it is recommended that the sensor be exposed to the outside air.
Here are some photos of the finished lamp:
The project essentially boils down to controlling the lamp via the web from any connected device. The ESP32 stays in station mode, waiting for another device to enter AP (portable hotspot) mode with the correct credentials to connect. Why did I choose station mode?
On the web interface, you'll notice Wi-Fi signal strength indicators in the bottom right corner showing the connection quality between your device and the lamp. This can only be achieved if the ESP32 is in station mode.
The ESP32 creates an asynchronous server (ESPAsyncWebServer), meaning it can handle multiple clients simultaneously (8) and sends data in JSON format via WebSocket. With this setup, up to 8 devices connected to the lamp receive real-time updates, and changes are reflected on all devices simultaneously.
The ESP32 hosts an embedded website in SPIFFS memory; the web content is in the project's
Three objects are created:
StripLed— Neopixel strip controlLed— ESP32 onboard LED controlBattery— Battery control and monitoring
The remaining libraries handle lighting effects and music-synchronized effects (VU), along with the web server, battery monitoring, and DHT22 sensor. Below are the most important variables you may need to adjust.
- Debug variables
#define DEBUGLEVEL— set to DEBUGLEVEL_DEBUGGING for serial debug output#define DHT— comment out with (//) to hide DHT22 messages#define BATTERY— comment out with (//) to hide battery monitoring messages
Note: Some debug messages, such as connection and web service info, are mandatory.
- Neopixel strip variables
#define N_PIXELS— number of LEDs in your stripint brightness— initial brightness (default 130, a bit over half brightness)
- Battery management variables
#define MAXV— Maximum voltage when battery is fully charged#define MINV— Minimum voltage to start charging (default 3.2V)
Note: For accuracy, charge the battery using the TP4056 module. When the full charge LED turns on, measure the battery voltage and use that as the maximum value. For the minimum value, the DC-DC step-up module specifications state that to achieve 5V output, the input must be at least 2V. However, 3.2V is recommended for safety.
- Web variables
const unsigned long refresh— delay for sending information (default 3 seconds)
Note: The project currently uses PlatformIO as its development environment. The
platformio.inifile in the project root contains all build configuration, dependencies, and serial port settings.
- VS Code with the PlatformIO extension
- Or PlatformIO Core via command line
# Compile the project
pio run
# Upload firmware to ESP32
pio run --target upload
# Monitor serial port
pio device monitor
# Clean build files
pio run --target cleanThe HTML, CSS, JS, and image files are in the data/ folder and are stored in the ESP32's LittleFS (SPIFFS). After modifying any web files:
pio run --target uploadfsIn src/config/debug_config.h you can control which messages appear on the serial port:
| Macro | Description |
|---|---|
DEBUGLEVEL_DEBUGGING |
Enables all debugging |
#define DHT |
Shows DHT22 sensor messages |
#define BATTERY |
Shows battery monitoring messages |
#define DEBUG_POWER_MANAGEMENT |
Shows power manager state transitions |
The ESP32 runs an asynchronous web server (ESPAsyncWebServer) with real-time WebSocket communication. It supports up to 8 simultaneous clients, and changes are reflected instantly on all connected devices (ideal for family control).
The interface is organized into 8 tabs accessible from the sidebar (or bottom bar on mobile):
Main lamp control:
- On/Off for the main LED lamp
- On/Off for the Neopixel strip
- Color picker with color wheel (iro.js)
- Brightness slider for the Neopixel strip
- Light effects (see Effects section)
Bluetooth speaker control:
- Play / Pause
- Volume + / Volume -
- Fast Forward / Rewind
- On/Off for the Bluetooth module
- VU effects — music visualization
Real-time LED strip preview via a canvas updated over WebSocket (~30 FPS). Displays the 24 LEDs interpolated as 33 virtual LEDs with two view modes:
- Strip — horizontal linear layout
- Circle — circular layout (simulates the lamp's real shape)
- Quick config button for the active effect
- FPS indicator and stream status
DHT22 sensor data:
- Temperature (°C)
- Relative humidity (%)
- Updates every 3 seconds
Detailed battery status:
- Charge percentage
- Current voltage
- Status: charging / fully charged / discharging
- Historical voltage graph
- Visual power source indicator (AC or battery)
Advanced configuration panel:
- Themes: SML Classic, WLED Dark, Midnight AMOLED
- WiFi: change SSID and password without re-flashing firmware
- Firmware Update: direct link to ElegantOTA
- System Info: uptime, free heap, WiFi RSSI, firmware version, MAC and IP address
- Connected clients: list of active WebSocket connections
- Activity logs: record of actions performed from the web interface
Quick reference guide for all lamp features.
Project information, version, and links.
The project includes over 40 visual effects organized into categories. Access them from the Lamp tab → Effects section.
| Category | Description |
|---|---|
| ⭐ Favorites | Your favorite effects (star-marked) |
| 📋 Playlist | Custom playlist for random mode |
| 🔧 Fundamentals | Base effects: solid, flash, fade |
| 🏃 Moving | Motion effects: dot, comet, sinelon, scan |
| ⚡ Dynamics | Dynamic effects: fire, lightning, aurora, popcorn |
| 🎨 Patterns | Patterns: rainbow, theater chase, sweep, dissolve |
| 🌡️ States | Status effects: battery, temperature |
| 🔀 Random | Random mode (see next section) |
In addition to traditional visual effects, there are VU effects that react to audio playing through the Bluetooth speaker. They are located in the Music tab and activate automatically when there's audio input. Examples include:
- Rainbow VU — multi-colored bars in sync with the music
- Ripple VU — concentric waves reacting to the rhythm
- Frequency Spectrum — equalizer-style visualization
The effect cards work as an on/off switch, and the settings live behind a separate ⚙ gear button.
| Action | Desktop | Mobile |
|---|---|---|
| Turn the effect on | Click the card | Tap the card |
| Turn the effect off | Click the card again (the active one) | Tap the card again (the active one) |
| Open the effect settings | Click the ⚙ gear in the card's top-left corner, or the gear in the Peek panel | Tap the ⚙ gear on the card, or long-press the card (~0.5 s) |
Turning an effect off returns the strip to Solid: the Neopixel keeps running, so color and brightness still apply. To cut the strip itself, use the power card in the Lamp tab.
The configuration panel lets you adjust:
- Speed — effect speed
- Color — palette or primary color
- Intensity — brightness or effect intensity
- Effect-specific parameters
The Random FX and Random VU cards behave the same way: tap/click to start or stop the cycling, gear (or long-press) to open the cycle settings.
Any client can turn effects on and off, but only the master client can open the settings panel (see the Master / Slave Mode section below). The strip has to be on for the cards to respond.
The lamp can cycle through effects automatically without intervention. There are two modes:
Cycles through visual effects on the Neopixel strip with configurable duration per effect.
Selection modes:
- All — any available effect
- Favorites — only your star-marked effects
- Categories — choose one or more specific categories
- Playlist — custom sequence that you order yourself
Similar to above but with VU effects (music visualization). Ideal for leaving the lamp in ambient music mode.
- On/Off — tap/click the Random FX or Random VU card (same on/off behaviour as the effect cards)
- Settings — the card's ⚙ gear (or long-press on mobile): cycle duration and, for Random FX, the selection mode and the category / playlist pickers
- Duration per effect — how long each effect stays active (configurable)
- Preview of the current effect in the Peek panel
The ESP32 implements an automatic power management system with a state machine that detects the power source and optimizes consumption.
| State | Power Source | WiFi | CPU | Neopixel | Consumption |
|---|---|---|---|---|---|
| AC_MODE | Mains | Always ON | 240 MHz | On | ~180 mA |
| BATTERY_CONNECTING | Battery | 10s ON / 30s wait | 240 MHz | Off | ~120 mA |
| BATTERY_ACTIVE | Battery + client connected | Always ON | 240 MHz | Off | ~120 mA |
| BATTERY_SLEEP | Battery, no client | 10s ON / 60s OFF | 80 MHz | Off | ~25 mA |
- AC loss → try to connect 10s → wait for client 30s → sleep if no client
- Client connects → instant wake from any state
- Client disconnects → wait 30s for reconnection → sleep
- AC restored → immediate return to full operation
- Battery < 15% → force maximum power saving
Uses the TP4056 pins: if charging or battery is full, it assumes AC power is present. Includes 3-second debounce to prevent rapid state changes from power flickers.
When mains power is lost, the load-sharing circuit automatically disconnects the battery from the mains path and the ESP32 runs from the battery through the DC-DC boost converter. In that situation:
- The Neopixel strip turns off because it is powered only from the mains supply and not from the battery (the Neopixels consume too much for battery operation).
- The main LED lamp, the Bluetooth speaker, and the ESP32 itself keep running on battery power, so music continues playing and the web interface remains controllable.
- The device automatically enters battery mode: first it tries to reconnect WiFi for 10 s, then waits up to 30 s for a WebSocket client to connect, and if nobody connects it falls back to power-saving cycles (WiFi 10 s on / 60 s off, CPU at 80 MHz).
- Consumption in battery drops from ~120 mA (battery + connected client) to ~25 mA (battery, no client).
- Below 15% the state machine forces maximum power savings.
- The voltage history is automatically saved to /battlog.json every N readings and at least once per minute, so when mains returns you can see how the battery behaved during the outage.
There are two situations that are not the same:
- The device loses the WiFi link (the ESP32 loses WiFi). This happens if the router goes down, the device goes out of range, or the router reboots. The ESP32 reports
wifiOk:falseto all clients, and if you are in AC mode the master client sees a countdown on the web interface before the strip is turned off. The strip does not turn off immediately; it waits for the configured WiFi-loss timeout (default 30 s, configurable in the Config tab) and only turns off if the link is not restored. - A client disconnects (for example, your phone goes to sleep or you leave the network). If the device still has WiFi, the strip does NOT turn off, because the ESP32 is still connected. Only the clients that are actually connected to the device see and control the strip.
The web interface supports up to 8 simultaneous clients connected to the same ESP32 at once, but there is one important concept: among all those clients there is only one master.
- The first device that connects becomes the master.
- The master is the only one that can turn the strip off when mains power is lost (the WiFi-loss countdown is shown only to the master).
- All clients see changes in real time (color, effect, brightness, music, battery), but the strip's critical power-level actions (turn off on mains loss, turn off on WiFi-loss timeout) are controlled only by the master.
- When the master disconnects, the next client in the list becomes master automatically (handover). A new client does not need to be "chosen": the ESP32 decides.
- Turn the main LED lamp on/off.
- Turn the Bluetooth speaker on/off and control it (play/pause, skip forward/backward, volume).
- Change the strip color, brightness, and effect (the master reflects these too).
- Activate/deactivate the status effects (battery, temperature).
- View all real-time state: battery, temperature, WiFi, connected clients, activity log.
- View the client list and who is master.
- Turn the strip off on mains loss or on WiFi-loss timeout: that decision is made by the master. If you are a slave and mains is lost, the strip turns off because the master orders it (or because the device switches to battery and the strip no longer has 5V).
- Turn the strip off if you are a slave and the ESP32 still has WiFi: the strip keeps running because the master is still controlling it.
- Force a reboot or a factory reset: those actions are reserved for the master.
- See the WiFi-loss countdown banner: that banner appears only on the master.
In practice this means several family members can control the lamp at the same time without conflicting, but the "turn off on power loss" or "turn off on WiFi loss" behavior is always decided by whichever device is currently the master.
You can update the ESP32 firmware without a USB cable, directly from your browser:
- Connect to the ESP32's WiFi network
- Open
http://<esp32-ip>/update - Select the compiled
.binfile - Click Update and wait for the ESP32 to reboot
Or from the web interface, go to Config tab → Firmware Update section.
- Scheduled alarm system (turn on lamp at set times)
- Push notifications to mobile when battery is low
- Collaborative effects (multiple synchronized ESP32s)
- Home Assistant integration
- Native Android/iOS app
- User profiles (save complete configuration)
- Support for larger addressable LED strips (>100 LEDs)
- Real-time graphic equalizer on the web interface
- Alarm clock mode (simulate sunrise with LEDs)
| Alexminator |
|---|
|
The SML project is released under the GNU General Public License v3.0.
Need help? Contact me 📨 alexminator99@gmail.com
- VSCODE — Code editor.
- PlatformIO — C/C++ programming IDE for hardware.
- danilopinotti for the Battery18650Stats library
- me-no-dev for ESPAsyncWebServer and AsyncTCP
- FastLED for the incredible LED library
- ElegantOTA for hassle-free OTA updates
- PlatformIO for the excellent embedded development ecosystem
- The Cuban Arduino community.
- To everyone who helped me when I had doubts, especially my son.
- To my wife, for being the initial inspiration for this project ❤️






























