My DIY Linear LED Clock - Now Integrated with ESPHome & Home Assistant!

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:

  1. Core Components: Uses standard wifi, api, logger, ota, and time components. The time platform is set to homeassistant, making time sync effortless.
  2. LED Control: The light component with the neopixelbus platform defines the LED strip (id: led_strip).
  3. Display Logic Implementation (The ‘Many Ifs’):
  • The core logic resides in an ESPHome script (id: update_clock_display). This script runs every minute (via on_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 like light.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. Each if checks for a specific digit value (e.g., minute % 10 == 3). For these checks, I used small, simple lambda: functions only where necessary for calculations like modulo (%) or division (/). The then: block for each condition simply uses the standard ESPHome light.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).
  1. Clean HA Control (The Elegant Switch): Instead of exposing the raw light entity (with unwanted color/brightness controls) to Home Assistant, I implemented a template 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.
  1. Making it Work (internal: true Workaround): As mentioned in point 2, the light component (id: led_strip) controls the LEDs. To hide this from HA, I set internal: true. However, the previously mentioned issue where light.addressable_set didn’t work required a workaround:
  • Keep internal: true on the light component.
  • Explicitly add light.turn_on: with a desired brightness (e.g., brightness: 0.3 in the code below) at the beginning of the display script.
  • Explicitly add light.turn_off: in the turn_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, allowing light.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 or person entities to trigger switch.turn_off / switch.turn_on for switch.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:

  1. Prerequisites:
  • Your clock hardware built and wired (Wemos D1 Mini, WS2812 strip connected to D4, GND, 5V).
  • Home Assistant OS or Supervised running.
  1. Install ESPHome Add-on: If you haven’t already, go to SettingsAdd-onsAdd-on Store in Home Assistant, search for “ESPHome”, and install it. Start the add-on and enable “Show in sidebar”.
  2. Open ESPHome Web UI: Click on “ESPHome” in your Home Assistant sidebar.
  3. Create New Device: Click the green “+ NEW DEVICE” button. Click “Continue”.
  4. Name Your Device: Give it a name (e.g., linear-led-clock - lowercase, no spaces). Click “Next”.
  5. Select Board: Choose “ESP8266” and then find and select “Wemos D1 mini” (or compatible). Click “Next”.
  6. 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.
  7. Edit Configuration: Find your new device (linear-led-clock) in the list and click “EDIT”.
  8. Paste Code: Delete all the default content in the editor window. Copy the entire YAML code provided below and paste it into the editor.
  9. 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).
  10. 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)”.
  1. Add to Home Assistant: Once installed and connected to WiFi, Home Assistant should automatically discover the new ESPHome device under SettingsDevices & 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!

3 Likes