ESPHomeHome Assistant

Weekday alarm on a Ulanzi TC001

ESPHome on a Ulanzi TC001 pixel clock rings a weekday time and a weekend time. The times are entities in Home Assistant. Any of the three buttons dismisses the alarm, and the face shows the date.

Difficulty · IntermediateSetup · 45–90 minutesHA · current stable (native API)ESPHome · current stable (ESP-IDF)
Get the pack · from $19Have us set it up

Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.

The stock TC001 is a pixel clock. This pack keeps the clock, the date card, the indoor temperature, and the three buttons, and adds two alarms: one time for Monday–Friday and one for Saturday–Sunday. Both times are Home Assistant entities, so a dashboard changes the same value the clock uses. It does not read a calendar event. A phone alarm that ramps the lights is phone-alarm wake light. This one is the clock making the noise.

Wi-Fi credentials and the API key stay in secrets.yaml. The weather entity id is a substitution. Do not publish which outdoor sensor you pointed it at.

What problem it solves

You want a bedside or desk pixel clock with a weekday wake time and a weekend wake time, changed from Home Assistant, without depending on a phone alarm.

How it works

The board is the TC001’s ESP32. ESPHome draws a 32×8 WS2812 matrix. Time comes from SNTP and is written to the onboard DS1307 so a short network loss does not blank the clock.

  1. Two alarms — template datetimes Mon-Fri Wake Time and Sat-Sun Wake Time, and two switches that arm them. Pack defaults are 07:10 and 09:00. Change them in Home Assistant or on the clock’s web page. The first boot uses the substitutions. Later edits are restored on the device.
  2. Ring — once a minute, if today’s switch is on and the clock matches that time, the buzzer script starts. It sweeps the pitch so it crosses the small passive buzzer’s loudest point. It stops on its own after 30 minutes.
  3. Dismiss — any of the three buttons stops it, and so does the Dismiss button in Home Assistant. A press only counts as dismiss while the alarm is actually ringing, so a normal page change does not have to be avoided the rest of the day.
  4. The face — the time page stays up the longest (about 45 seconds) and draws a date card, the time, a marker for PM, and weekday bars. Left and right change page: date, indoor temperature and humidity, outdoor temperature and condition, next alarm, then a short animation. Middle tap turns the panel off or on. Middle hold cycles brightness (auto, low, medium, high). Auto brightness uses the onboard light sensor.
  5. Outdoor line — a Home Assistant weather entity supplies the condition text, and a temperature sensor supplies the outdoor number. Both are substitutions. Leave them pointed at entities you already have. The clock still runs with no Home Assistant at all. That outdoor page is the part that goes blank.

The web page is ESPHome’s web server on the clock. Open it from the address on the device page in Home Assistant. It edits the wake times, the switches, the pitch, Test Alarm, Dismiss, and the animation, and it can take a firmware file if Wi-Fi update is how you recover.

Hardware notes

  • Clock. Ulanzi TC001, ESP32, 4 MB flash, 32×8 WS2812 on GPIO32, GRB, wired row-serpentine. The passive buzzer is GPIO15. Buttons are GPIO26 left, GPIO27 middle, and GPIO14 right, active low. The light sensor is GPIO35 and the battery divider is GPIO34. I²C is GPIO21 and GPIO22: SHT3x at address 0x44, DS1307 at 0x68. Leave those pins. In ESPHome use board: esp32dev and the ESP-IDF framework.
  • Temperature offset. The SHT3x sits next to the LEDs and reads high. temp_offset_c is the correction against a thermometer you trust. Start at 0 and trim.
  • Power and the first cable. The linked supply is 5 V USB-C. The first flash needs a data cable. A charge-only cable powers the panel and never opens a serial port. The USB-serial chip on these clocks drops the link easily. Flash at 115200 baud, not a faster rate. Back up the stock image before you overwrite it. After the first successful boot, update over Wi-Fi only.
  • Where it sits. A nightstand or a desk, close enough to hear a small buzzer. The buzzer is not a smoke alarm. A louder alarm means a different device.

Snippet

Pins and the two default times are substitutions. The matrix drawing lives in the pack.

substitutions:
  device_name: pixel_clock
  wk_alarm_time: "07:10:00"
  we_alarm_time: "09:00:00"
  temp_offset_c: "0.0"
  matrix_pin: GPIO32
  buzzer_pin: GPIO15

esp32:
  board: esp32dev
  framework:
    type: esp-idf

output:
  - platform: ledc
    id: buzzer_out
    pin: ${buzzer_pin}

datetime:
  - platform: template
    id: wk_alarm
    name: "Mon-Fri Wake Time"
    type: time
    initial_value: ${wk_alarm_time}
    restore_value: true

The weekend datetime, the two arming switches, and the minute check that calls the ring script are the rest of the package. The ring script gives up after 30 minutes (1800000 ms) if nobody presses a button.

Setup outline

  1. Copy the device YAML. Set the name and a fresh API key in secrets. Set the timezone substitution. Point the outdoor substitutions at a weather entity and a temperature sensor, or leave the outdoor page unused.
  2. Back up the stock flash at 115200 baud. Flash this firmware over USB at the same baud. If the transfer drops, replug the cable and resume. Do not switch to a faster baud to “finish it.”
  3. Adopt the node in Home Assistant. Confirm the two wake times and the two switches. Set a time two minutes ahead and arm that day.
  4. Wait for it to ring. Press any hardware button. It should stop. Run Test Alarm from the device page and then Dismiss.
  5. Later updates: OTA only. The web page on the device can also take a .bin if you are standing next to it.

Troubleshooting

  • The first flash never finishes. The cable is charge-only, the baud is too high, or the serial chip dropped. Use a data cable, 115200, and a port that reappears after you replug. Do not start a second full erase until the backup is somewhere safe.
  • It does not ring. The switch for today is off, the time entity does not match the clock, or the clock’s timezone substitution is wrong. Compare the device’s time sensor with a known clock before you blame the buzzer.
  • The indoor temperature is several degrees high. Set temp_offset_c. The sensor is next to the LEDs. That offset is normal on this board.
  • Outdoor page is empty. The weather substitution does not match an entity, or Home Assistant is down. The rest of the clock does not need it.
  • A button changes the page while the alarm is ringing. The dismiss path only runs when the alarm flag is on, and a page change is suppressed for a short moment after dismiss. If the alarm had already stopped itself at 30 minutes, the button is a normal page key again.
  • You wanted the room lights, not a buzzer. That is phone-alarm wake light, driven by the phone’s next alarm. This pack does not fade a bulb.

Credits

Based on ulanzi-tc001-alarm-clock. That ESPHome package keeps the TC001’s clock, date card, indoor climate, outdoor line, and three buttons, and adds weekday and weekend alarms with a web page. This write-up is an original overview of that path: the two Home Assistant times, the 30 minute ring, and the button dismiss. It does not flash stock social-counter firmware, and it does not read a calendar event.

Changelog

v1.0 · Oct 29, 2026: First publish.