Nightlight
There are two configs in this folder and they are two different lights. The mono one is a single dimmable channel — one PWM pin, one MOSFET, one run of white LEDs under a kitchen counter, turned down far enough that you can cross the room at 3 a.m. without waking up. The RGB one is the kid’s-room version: a warm bulb on its own dimmer plus 54 addressable RGBW pixels around the roof, for colour wipes, twinkles and a police-car mode nobody asked for.
Both are ESPHome: they are discovered by Home Assistant with no YAML to write on its side, and both update over WiFi after the first flash.
Parts
Some links below are Amazon affiliate links. As an Amazon Associate I earn from qualifying purchases — at no extra cost to you. It helps keep these guides free. Outside the US, they should send you to your own Amazon store; if one doesn't, searching the part number there will find it.
The mono build — “BRUH Countertop”
- ESP32 dev board, 38-pin ESP-WROOM-32 — a DevKitC-style board rather than a literal NodeMCU-32S, but the same ESP-WROOM-32 module on the same 38-pin pinout, and the config’s
board: nodemcu-32skey builds for it unchanged. A 2-pack, so the spare covers a second light - Dual MOSFET PWM driver module — 5–36 V, 15 A. A 10-pack; you need one per PWM channel
- 24 V natural-white high-density strip — 16.4 ft, cut to the counter at the marked lines
- 24 V 5 A power supply
- Isolated 24 V → 5 V buck with USB out — runs the ESP32 off the same brick so there is one plug, not two
- Right-angle 1 ft micro-USB cable — from that buck into the board, in a box shallow enough to hide
- Aluminium U-channel with clear lens — 5 × 1 m. A bare strip under a counter reflects off the worktop as a row of dots; the channel is what makes it a line of light
The RGB build — “Lev Nightlight”
- ESP32-S3-DevKitC-1 — the config names this board specifically and uses its onboard RGB LED on GPIO48. A plain ESP32 will not do; the pins are different and GPIO48 does not exist on one
- SK6812 RGBW strip, 5 V, 144 LEDs/m — 1 m is 144 pixels, so the 54-pixel run is about 37 cm of it. For a bigger shade, the 5 m 60 LEDs/m reel makes 54 pixels about 90 cm
- 12 V dimmable warm-white G4 LED bulb — 3 W, 2700 K, 5-pack. This is the
bulb_ledschannel - 12 V 4 A power supply — ships with a screw-terminal jack, which saves cutting the plug off
- MP1495 buck module, 5–16 V → 5 V 3 A — makes the 5 V rail for the board and the strip
- Dual MOSFET PWM driver module — same module as above, for the bulb channel
- JST-SM 3-pin pigtails — 20 pairs, so the shade unplugs from the base
Both
- 18 AWG two-conductor silicone wire for power runs and 24 AWG silicone hookup wire for signal
- Lever-nut connectors — solderless joins where the strip meets the wire
- A resistor assortment for the 330 Ω on the pixel data line
Nothing here plugs an LED strip into a GPIO pin. An ESP32 output sources a few tens of milliamps and the mono strip wants amps — the MOSFET module is not optional, it is the part that does the switching. The pin only ever drives the module’s gate input.
Wiring
Mono — ESP32 to a single-colour strip
| Signal | ESP32 pin | Goes to |
|---|---|---|
| PWM out | GPIO32 | MOSFET module signal input |
| Ground | GND | MOSFET module signal ground, and the 24 V supply’s negative |
| Board power | 5V / VIN | 5 V from the buck, or the micro-USB socket |
| Status LED | GPIO2 | The board’s own blue LED. Nothing to wire |
The MOSFET module sits between the supply and the strip: 24 V in on its input terminals, strip on its output terminals, GPIO32 and GND on its signal header. The buck taps the same 24 V and hands the board 5 V over USB.
RGB — ESP32-S3 to a bulb and a pixel strip
| Signal | ESP32-S3 pin | Goes to |
|---|---|---|
| Bulb PWM | GPIO20 | MOSFET module signal input → 12 V G4 bulb |
| Pixel data | GPIO21 | SK6812 strip DIN, through 330 Ω |
| Board LED | GPIO48 | The DevKitC-1’s onboard addressable LED. Nothing to wire |
| Strip power | 5V / GND | 5 V rail from the MP1495, shared ground with the board |
Put the 330 Ω resistor in series with the data line, at the ESP32 end, and keep that lead short. Everything shares one ground — the 12 V negative, the 5 V negative and the board’s GND are the same net, and the strip will do nothing at all if you forget it.
Assembly
-
Set the buck’s output before it is connected to anything. Feed it the supply, put a meter on the output and dial it to 5.0 V. The MP1495 arrives at whatever the last person left it at, and 12 V into an ESP32’s 5 V pin is a dead board.
-
Cut and re-terminate the strip. Cut on the copper pads, tin them, and solder a JST-SM pigtail on so the shade lifts off the base without a soldering iron. Check the arrows on the strip — data flows one way, and a strip wired backwards is silent, not dim.
-
Wire the MOSFET module — supply in, load out, signal from the GPIO. Then bench-test it with the board on USB, before anything is glued or screwed down.
-
Flash it and confirm the entities appear in Home Assistant, then close the box. Every flash after the first is over WiFi, but only if the WiFi credentials were right the first time.
-
Mount the mono strip in its channel before sticking the channel down. Peel, press, cap the ends, then fix the channel to the underside of the counter. Doing it the other way round means working upside down with a strip that only sticks once.
Configuration
Both configs read from secrets.yaml: wifi_ssid, wifi_password,
ota_password, and api_key for the RGB one.
In the mono config, the two substitutions at the top are the only lines you have to change:
substitutions: device_name: "BRUH Countertop" device_id: "bubba-nightlight"In the RGB config, name: and friendly_name: do the same job, and the number
that matters is num_leds: 54 on the led_strip_roof light — set it to however
many pixels you actually cut. is_rgbw: true stays: an SK6812 pixel has a real
white chip and ESPHome needs to know it is there. rgb_order: GRB is correct for
these strips; if red and green come out swapped, that is the key to change.
Two things worth fixing while you are in there. The mono config’s api: block
has no encryption key — give it one, the way the RGB config does. And there is a
globals: block declaring dim_up that nothing in the file ever reads. It is
harmless, and you can delete it.
restore_mode: restore_default_on on the mono light means it comes back on by
itself after a power cut. For a nightlight that is the correct behaviour, and it
is worth copying into the RGB config, which does not have it.
What you get in Home Assistant
The mono build is one light entity with pulse, fast pulse, slow pulse, strobe and
random effects, plus a Fade Alert effect that breathes between 100% and 75% on
a two-second cycle — that is the one to trigger from an automation when you want
the light to tell you something without changing colour. There is also a WiFi
signal sensor, a status binary sensor and a restart switch.
The RGB build gives you three lights: the warm bulb, the 54-pixel fill strip, and
the board’s own single onboard pixel, which is genuinely useful as a “did it
boot” indicator you can then turn off from Home Assistant. The fill strip carries
the whole ESPHome addressable effect set plus tuned variants — Color Wipe Mist
(a blue three-pixel gradient running backwards), Police, Fireworks Custom,
Twinkle BRUH, Flicker BRUH and a blue-then-green Strobe Custom. The last
entry in the list is wled:, which turns on WLED’s UDP realtime protocol, so
anything that can drive a WLED device — an app, xLights — can take the strip over
across the network without reflashing.
Troubleshooting
The pixel strip does nothing, or the first pixel lights and the rest don’t. In order: ground not shared between the 5 V rail and the board, data soldered to the output end of the strip instead of the input end, or 3.3 V data that the first pixel is not reading cleanly. The first two are free to check. For the third, a TXS0108E level shifter on the data line fixes it, though a short lead and the 330 Ω resistor usually make it unnecessary.
Colours are close but wrong — white looks pink, or red and green are swapped.
rgb_order and is_rgbw. A GRB strip declared as RGB swaps two channels; an
RGBW strip declared as RGB throws away the white chip and mixes a worse white out
of the other three.
The strip browns out or shifts colour at high brightness. 54 RGBW pixels at full white pull well over 3 A, which is more than the MP1495 will give you. A nightlight never asks for that, so cap the brightness in Home Assistant — or, if you want the full output, feed the strip from its own 5 V supply and keep the buck for the board alone.
The bulb whines at low brightness. That is the bulb’s own driver ringing at
the PWM frequency. Raise frequency: on the ledc output — 20000 Hz is above
hearing, but you lose resolution at the very bottom of the dimming range, which
is exactly where a nightlight lives. Try 5000 Hz first.
ESPHome rejects rmt_channel:. That key belongs to the older RMT driver.
The config pins framework: type: arduino deliberately — leave that alone. If a
newer ESPHome still refuses the key, delete both rmt_channel: lines and let it
allocate the channels itself.
Files & downloads
ESPHome configuration s (2)
Copy this into your ESPHome directory and adjust the substitutions at the top.
Secrets are referenced by name — see secrets.yaml.example
.
bruh-nightlight.yaml
# BRUH Nightlight (Monochromatic)# ────────────────────────────────────────────────────────────# Simple LED nightlight with PWM brightness control and# pulse effects. LEDC PWM output for smooth dimming.## Hardware:# - Board: ESP32# - Output: LEDC PWM on GPIO32# ────────────────────────────────────────────────────────────
substitutions: device_name: "BRUH Countertop" device_id: "bubba-nightlight"
globals: - id: dim_up type: float restore_value: yes initial_value: '1.0'
esphome: name: ${device_id}
esp32: board: nodemcu-32s
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
ota: password: !secret ota_password platform: esphome
api:
status_led: pin: number: GPIO2
logger:
output: - platform: ledc pin: GPIO32 id: gpio_32 frequency: "1220Hz"
light: - platform: monochromatic output: gpio_32 restore_mode: restore_default_on name: "Nightlight" id: nightlight default_transition_length: 0.5s effects: - pulse: - pulse: name: "Fast Pulse" transition_length: 0.5s update_interval: 0.5s - pulse: name: "Slow Pulse" update_interval: 2s - strobe: - random: - automation: name: "Fade Alert" sequence: - light.turn_on: id: nightlight brightness: 100% transition_length: 1s - delay: 1s - light.turn_on: id: nightlight brightness: 75% transition_length: 1s - delay: 1s
binary_sensor: - platform: status name: "${device_name} Status"
sensor: - platform: wifi_signal name: "${device_name} WiFi Signal" update_interval: 300s
switch: - platform: restart name: "${device_name} Restart" bruh-nightlight-rgb.yaml
# BRUH Nightlight RGB# ────────────────────────────────────────────────────────────# RGB LED strip nightlight with addressable color effects.# Multiple RGB lighting modes with pulse and custom effects.## Hardware:# - Board: ESP32-S3# - Output: RMT LED strip (WS2812/NeoPixel)# - Additional: LEDC PWM monochromatic LED# ────────────────────────────────────────────────────────────
esphome: name: lev-nightlight friendly_name: "Lev Nightlight"
esp32: board: esp32-s3-devkitc-1 framework: type: arduino
logger:
api: encryption: key: !secret api_key
ota: password: !secret ota_password platform: esphome
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
output: - platform: ledc pin: GPIO20 frequency: 1000hz id: bulb_leds
light: - platform: esp32_rmt_led_strip id: board_light rgb_order: GRB pin: GPIO48 num_leds: 1 rmt_channel: 0 chipset: ws2812 name: "Board Light" effects: - pulse: transition_length: 550ms update_interval: 550ms
- platform: monochromatic output: bulb_leds name: "Lev Nightlight"
- platform: esp32_rmt_led_strip id: led_strip_roof rgb_order: GRB is_rgbw: true pin: GPIO21 num_leds: 54 rmt_channel: 1 chipset: SK6812 name: "Lev Nightlight Fill" effects: - addressable_color_wipe: - addressable_color_wipe: name: "Color Wipe Mist" colors: - red: 0% green: 15% blue: 100% white: 0% num_leds: 3 gradient: true - red: 0% green: 0% blue: 0% white: 0% gradient: true num_leds: 3 add_led_interval: 30ms reverse: true - addressable_color_wipe: name: "Color Wipe Custom" colors: - red: 100% green: 100% blue: 100% white: 0% num_leds: 5 gradient: true - red: 0% green: 0% blue: 0% white: 0% num_leds: 1 add_led_interval: 100ms reverse: false - addressable_color_wipe: name: "Police" colors: - red: 100% green: 0% blue: 0% white: 0% num_leds: 6 - red: 0% green: 0% blue: 100% white: 0% num_leds: 6 add_led_interval: 30ms reverse: false - addressable_fireworks: - addressable_fireworks: name: "Fireworks Custom" update_interval: 32ms spark_probability: 10% use_random_color: false fade_out_rate: 120 - addressable_flicker: - addressable_flicker: name: "Addressable Flicker BRUH" update_interval: 25ms intensity: 15% - addressable_rainbow: - addressable_random_twinkle: - addressable_scan: - addressable_twinkle: - addressable_twinkle: name: "Twinkle BRUH" twinkle_probability: 80% progress_interval: 4ms - flicker: - flicker: name: "Flicker BRUH" alpha: 92% intensity: 4% - random: - strobe: - strobe: name: "Strobe Custom" colors: - state: true brightness: 100% red: 0% green: 0% blue: 100% white: 0% duration: 500ms - state: false duration: 250ms - state: true brightness: 100% red: 0% green: 100% blue: 0% white: 0% duration: 500ms - wled: