Hey everyone!
Like many of you, I have a fascination with clocks and interesting ways to display time. I love DIY projects, and one of my creations is a desktop Linear LED Clock, based on an idea I documented previously on Instructables:
My Linear LED Clock Instructable
The Original Project
My inspiration started with projects like Mirko Pavleski’s “DIY Unusual Linear Clock,” but I wanted to create a more compact version suitable for a desk and implement my own take on the display logic using groups of triangular segments.
The hardware involves a Wemos D1 Mini and a strip of 35 WS2812 LEDs. A key part of the build was modifying a standard 60 LEDs/m strip to achieve a tighter 5mm pitch between LEDs – I did this by carefully pushing the excess strip length through rectangular holes in a custom 3D-printed holder (you can see the details in the Instructable). The display originally used a simple but effective diffuser made from a piece of smoked acrylic behind a sheet of white paper, all housed in a 3D-printed case. (More on diffusers later!)
Originally, the clock ran custom Arduino-based code. This handled everything: connecting to WiFi via a captive portal I coded, syncing time directly with NTP servers (including timezone/DST logic), and even providing a small web interface hosted on the ESP itself for configuration and settings adjustments. It worked well as a standalone device!
Bringing it into the Home Assistant Ecosystem
While the original clock was functional, I really wanted to integrate it more deeply into my Home Assistant setup. My goals were:
- Simplified Time Management: Leverage HA for accurate time, timezone, and DST handling without needing direct NTP configuration on the ESP.
- Unified Control: Control the clock alongside my other smart home devices.
- Automation Potential: Enable automations like turning the clock display off when I’m away or overnight.
ESPHome seemed like the perfect tool for the job, allowing configuration via YAML and seamless integration with Home Assistant.
The ESPHome Implementation Journey
The hardware remained the same (Wemos D1 Mini, 35x WS2812 LEDs on pin D4). The main task was translating the display logic and control into ESPHome’s YAML configuration.
Here’s a breakdown of the final ESPHome setup:
- Core Components: Uses standard
wifi
,api
,logger
,ota
, andtime
components. Thetime
platform is set tohomeassistant
, making time sync effortless. - LED Control: The
light
component with theneopixelbus
platform defines the LED strip (id: led_strip
). - Display Logic Implementation (The ‘Many Ifs’):
- The core logic resides in an ESPHome
script
(id: update_clock_display
). This script runs every minute (viaon_time
) or immediately when the clock is turned on. - Inside the script, after checking if the time from HA is valid, it needs to turn on the correct LEDs for the current hour and minute based on my specific visual design (Blue for Minute Units, Green for Minute Tens, Red for Hour Units, Yellow for Hour Tens).
- The Journey to the Working Logic: Honestly, my first instinct was to try and create a single, elegant
lambda
function to handle all the time calculations and LED assignments. I spent quite some time experimenting with this approach. However, I found it challenging to reliably perform actions likelight.addressable_set
directly from within the type of complex lambda I envisioned, and I struggled to find clear examples or documentation for such a specific, combined calculation-and-action lambda within ESPHome YAML. Perhaps it’s possible with deeper C++ knowledge or maybe I just couldn’t find the right way. - The Solution That Worked: So, after several unsuccessful attempts with the ‘big lambda’ idea, I shifted to a more standard and robust ESPHome pattern: using a series of
if
actions. Eachif
checks for a specific digit value (e.g.,minute % 10 == 3
). For these checks, I used small, simplelambda:
functions only where necessary for calculations like modulo (%
) or division (/
). Thethen:
block for each condition simply uses the standard ESPHomelight.addressable_set
action to light up the pre-calculated LED indices for that digit. - It might not be the most ‘beautiful’ or compact code compared to a theoretical single function, but this approach is explicit, much easier (for me) to read and debug, leverages standard ESPHome actions effectively, and most importantly – it works reliably! It’s a good reminder that sometimes the most practical solution is the best one, especially when learning and iterating. It definitely shows I’m not a programming guru, just persistent! (And I had some help analyzing code and logs via AI assistance from Google’s Gemini during the debugging phase).
- Clean HA Control (The Elegant Switch): Instead of exposing the raw
light
entity (with unwanted color/brightness controls) to Home Assistant, I implemented atemplate switch
in ESPHome:
id: clock_power_switch
name: "Power"
This switch appears in Home Assistant under the device named “Linear LED Clock” as a simple ON/OFF toggle.
- Making it Work (
internal: true
Workaround): As mentioned in point 2, thelight
component (id: led_strip
) controls the LEDs. To hide this from HA, I setinternal: true
. However, the previously mentioned issue wherelight.addressable_set
didn’t work required a workaround:
- Keep
internal: true
on thelight
component. - Explicitly add
light.turn_on:
with a desired brightness (e.g.,brightness: 0.3
in the code below) at the beginning of the displayscript
. - Explicitly add
light.turn_off:
in theturn_off_action
of the template switch (and also if the script detects invalid time). This ensures the underlying light entity is programmatically turned ON when needed, allowinglight.addressable_set
to work even when the entity is hidden from HA.
The Result
Now, the clock integrates perfectly!
Showing the “Linear LED Clock” device with only the “Power” switch visible.
I have a clean “Power” switch (switch.clock_power_switch
) in Home Assistant to turn the clock display on and off. Turning it ON shows the time immediately, and it updates every minute. Turning it OFF reliably blanks the display.
A Note on Brightness: I’ve set the brightness to 0.3
(30%) in the example code below (light.turn_on:
action in the script) as a conservative starting point. Personally, for my setup using the original acrylic+paper, I’ve found that around 0.6
(60%) works quite well, making the individual segments clearly visible without being too harsh. You should definitely experiment to find the level that best suits your eyes and your room’s lighting by adjusting this brightness:
value in the YAML. It might still be too bright for some in a very dark room, which highlights the usefulness of the automations mentioned earlier for turning it off at night!
Example Automation Ideas
The real power comes from HA integration, allowing automations like these:
- Presence Detection: Turn the clock display OFF when everyone leaves home and back ON when the first person arrives using
zone.home
state orperson
entities to triggerswitch.turn_off
/switch.turn_on
forswitch.clock_power_switch
. - Night / Sleep Mode: Turn the display OFF overnight. Create an
input_boolean.sleep_mode
helper in HA and use its state to turn the clock switch ON/OFF, giving you flexible control over nighttime behavior. Alternatively, use simple time triggers (e.g., OFF at 11 PM, ON at 7 AM). - Scene Integration: Include the
switch.clock_power_switch
state in your HA scenes (e.g., turn OFF in “Movie Time” or “Good Night” scenes, turn ON in “Home” or “Good Morning” scenes).
An Alternative Build Note: 3D Printed Diffuser
While my original Instructable used a combination of smoked acrylic and white paper for the diffuser, I’ve recently experimented with achieving the diffusion effect directly with the 3D print itself.
I’ve had interesting results using Fillamentum PLA Extrafill “Volcanic Dust” (the Filament). By carefully adjusting slicer settings (like using thin walls or specific infill for the front display part, while keeping the main case walls opaque), it’s possible to create a semi-transparent front panel that diffuses the LEDs nicely.
You can see an example of this technique in another project of mine: Just Hues - an Ultraminimalist Perpetual Monthly LED Calendar
It might be an interesting alternative for others looking to build this clock, potentially simplifying the assembly slightly.
The Code
Here is the final ESPHome YAML configuration that achieves this.
# ESPHome Configuration for Linear LED Clock with HA Integration
# Generation date: 2025-04-06 11:08:00 EEST
# Final Version (English Comments, Final Names, Correct Colors, Brightness Control)
esphome:
name: linear-clock-yaml-simple # Internal ESPHome name/hostname
friendly_name: "Linear LED Clock" # Name displayed in Home Assistant Integrations
on_boot:
priority: -10.0
then:
- logger.log: "Device booted. Ensuring initial OFF state."
# Ensure light entity is OFF on boot
- light.turn_off: led_strip
# Explicitly turn LEDs off
- light.addressable_set:
id: led_strip
range_from: 0
range_to: 34
red: 0.0
green: 0.0
blue: 0.0
- delay: 50ms
- logger.log: "Initial OFF state set."
time:
- platform: homeassistant
id: ha_time # ID for referencing time component
on_time:
# Runs every minute, at second 00
- seconds: 0
then:
# Execute periodic update ONLY if the main clock switch is ON
- if:
condition:
switch.is_on: clock_power_switch
then:
- logger.log: "Periodic clock update (on_time trigger)."
- script.execute: update_clock_display
# Script containing the clock display logic
script:
- id: update_clock_display # Script ID
mode: single # Prevents multiple simultaneous runs
then:
- logger.log: "SCRIPT: 'update_clock_display' execution START."
# First, check if time is valid
- if:
condition:
lambda: return id(ha_time).now().is_valid();
then:
- logger.log: "SCRIPT: Time is VALID. Displaying clock."
# ---> Turn ON the light entity first and set brightness <---
- light.turn_on:
id: led_strip
brightness: 0.3 # Adjust brightness (0.0 to 1.0)
# ----------------------------------------------------------
# Now continue with the clock logic
# Step 1: Turn everything off before setting digits
- light.addressable_set:
id: led_strip
range_from: 0
range_to: 34
red: 0.0
green: 0.0
blue: 0.0
# --- Step 2: Display Minute Units (MU) - Blue ---
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 1);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 2);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 3);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 4);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 5);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 14, range_to: 14, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 6);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 14, range_to: 14, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 20, range_to: 20, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 7);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 13, range_to: 13, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 14, range_to: 14, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 20, range_to: 20, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 8);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 12, range_to: 12, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 13, range_to: 13, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 14, range_to: 14, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 20, range_to: 20, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute % 10 == 9);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 12, range_to: 12, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 22, range_to: 22, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 13, range_to: 13, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 14, range_to: 14, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 20, range_to: 20, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 15, range_to: 15, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 16, range_to: 16, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 18, range_to: 18, blue: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 17, range_to: 17, blue: 1.0 } } ] }
# --- Step 3: Display Minute Tens (MT) - Green ---
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute / 10 == 1);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 11, range_to: 11, green: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute / 10 == 2);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 10, range_to: 10, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 11, range_to: 11, green: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute / 10 == 3);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 10, range_to: 10, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 24, range_to: 24, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 11, range_to: 11, green: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute / 10 == 4);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 9, range_to: 9, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 10, range_to: 10, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 24, range_to: 24, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 11, range_to: 11, green: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().minute / 10 >= 5);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 8, range_to: 8, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 9, range_to: 9, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 10, range_to: 10, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 24, range_to: 24, green: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 11, range_to: 11, green: 1.0 } } ] }
# --- Step 4: Display Hour Units (HU) - RED ---
# Color: red: 1.0
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 1);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 2);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 3);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 4);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 5);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 4, range_to: 4, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 6);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 4, range_to: 4, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 30, range_to: 30, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 7);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 3, range_to: 3, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 4, range_to: 4, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 30, range_to: 30, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 8);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 2, range_to: 2, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 3, range_to: 3, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 4, range_to: 4, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 30, range_to: 30, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour % 10 == 9);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 2, range_to: 2, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 32, range_to: 32, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 3, range_to: 3, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 4, range_to: 4, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 30, range_to: 30, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 5, range_to: 5, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 6, range_to: 6, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 28, range_to: 28, red: 1.0 } }, { light.addressable_set: { id: led_strip, range_from: 7, range_to: 7, red: 1.0 } } ] }
# --- Step 5: Display Hour Tens (HT) - YELLOW ---
# Color: red: 1.0, green: 1.0
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour / 10 == 1);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 1, range_to: 1, red: 1.0, green: 1.0, blue: 0.0 } } ] }
- if: { condition: { lambda: !lambda "return (id(ha_time).now().hour / 10 >= 2);" }, then: [ { light.addressable_set: { id: led_strip, range_from: 0, range_to: 0, red: 1.0, green: 1.0, blue: 0.0 } }, { light.addressable_set: { id: led_strip, range_from: 1, range_to: 1, red: 1.0, green: 1.0, blue: 0.0 } } ] }
# --- End Clock Logic ---
else:
# If time is invalid
- logger.log: "SCRIPT: Time INVALID. Turning off LEDs and light entity."
# Turn off the light entity
- light.turn_off: led_strip
# Explicitly turn off LEDs
- light.addressable_set:
id: led_strip
range_from: 0
range_to: 34
red: 0.0
green: 0.0
blue: 0.0
# Template Switch that will appear in Home Assistant
switch:
- platform: template
id: clock_power_switch # Internal ID for the switch
name: "Power" # Name displayed in Home Assistant UI (under "Linear LED Clock" device)
optimistic: true
turn_on_action:
- logger.log: "Clock Switch turned ON. Displaying current time."
- script.execute: update_clock_display
turn_off_action:
- logger.log: "Clock Switch turned OFF. Turning off LEDs and light entity."
# Turn off the light entity
- light.turn_off: led_strip
# Explicitly turn off LEDs
- light.addressable_set:
id: led_strip
range_from: 0
range_to: 34
red: 0.0
green: 0.0
blue: 0.0
# Standard ESPHome components
esp8266:
board: d1_mini
wifi:
# --- IMPORTANT: Replace with your WiFi credentials ---
ssid: "YOUR_WIFI_SSID"
password: "YOUR_WIFI_PASSWORD"
# Optional but recommended: Use ESPHome Secrets for credentials
# https://esphome.io/components/wifi#configuration-variables
# ssid: !secret wifi_ssid
# password: !secret wifi_password
logger:
api:
ota:
platform: esphome
# Light component - REMAINS internal
light:
- platform: neopixelbus
type: GRB
variant: WS2812
pin: D4 # ESP8266 Pin D4 (GPIO2)
num_leds: 35 # Total number of LEDs
name: "Internal Linear Clock LEDs" # Internal name, not visible in HA UI
id: led_strip # ID used to control the strip
internal: true # Hides this entity from Home Assistant UI
default_transition_length: 0s # No transition effect
How to Use This Code (Quick Start)
If you’re new to ESPHome and want to install this configuration on your Wemos D1 Mini based clock, here’s a quick guide assuming you’re using the ESPHome Add-on within Home Assistant:
- Prerequisites:
- Your clock hardware built and wired (Wemos D1 Mini, WS2812 strip connected to D4, GND, 5V).
- Home Assistant OS or Supervised running.
- Install ESPHome Add-on: If you haven’t already, go to
Settings
→Add-ons
→Add-on Store
in Home Assistant, search for “ESPHome”, and install it. Start the add-on and enable “Show in sidebar”. - Open ESPHome Web UI: Click on “ESPHome” in your Home Assistant sidebar.
- Create New Device: Click the green “+ NEW DEVICE” button. Click “Continue”.
- Name Your Device: Give it a name (e.g.,
linear-led-clock
- lowercase, no spaces). Click “Next”. - Select Board: Choose “ESP8266” and then find and select “Wemos D1 mini” (or compatible). Click “Next”.
- Skip Basic Config: Click “SKIP THIS STEP” when asked to create basic configuration - we will replace it entirely. The device will appear in your ESPHome dashboard.
- Edit Configuration: Find your new device (
linear-led-clock
) in the list and click “EDIT”. - Paste Code: Delete all the default content in the editor window. Copy the entire YAML code provided below and paste it into the editor.
- Enter WiFi Credentials: Scroll down to the
wifi:
section in the pasted code and replace"YOUR_WIFI_SSID"
and"YOUR_WIFI_PASSWORD"
with your actual WiFi network name and password. Using ESPHome Secrets is recommended for better security (see comments in the code). - Save & Install:
- Click “SAVE”.
- Click “INSTALL”.
- For the very first installation, choose “Plug into this computer”. You’ll need to connect your Wemos D1 Mini via a USB data cable to the computer running your web browser (or sometimes directly to the HA server, depending on your setup). Follow the on-screen prompts to select the correct serial port and begin the installation.
- After the first successful USB install, future updates can usually be done wirelessly by clicking “INSTALL” and choosing “Wirelessly (Over The Air)”.
- Add to Home Assistant: Once installed and connected to WiFi, Home Assistant should automatically discover the new ESPHome device under
Settings
→Devices & Services
. Click “Configure” to add it.
Other ESPHome Setups: If you run ESPHome standalone (Docker, pip install) or via the command line, the process is similar: create a new YAML file, paste the code, edit WiFi, then compile and upload using the appropriate methods (esphome run your_config.yaml
or esphome compile ...
and esphome upload ...
).
For detailed instructions, always refer to the official ESPHome documentation: ESPHome - Getting Started
Conclusion
I’m thrilled with how this ESPHome integration turned out, making my DIY clock truly ‘smart’ and part of my home ecosystem. It was a fun learning process involving hardware, software, and some good old trial-and-error (plus some helpful AI debugging!). I hope this inspires others or provides a useful reference.
Happy making, and I’d love to hear your thoughts or answer any questions in the comments!