Article / 2026

Surfacing Home Assistant Voice Timers on a Custom Display

I've been building a little Stream-Deck-style control panel for my desk that talks to Home Assistant (future more in-depth blog post coming soon).

Published

homeassistantvoiceesphome

I've been building a little Stream-Deck-style control panel for my desk that talks to Home Assistant (future more in-depth blog post coming soon). However, I wanted to put out this potentially helpful tidbit first.

I often set a timer for something in the kitchen, and then hop back on the computer while it runs. So I wanted to mirror the Home Assistant timer that I'd set via the Voice PE to the deck's display and trigger an alert animation on its screen as well.

That turned out to be way more involved than I expected, for one annoying reason: voice timers are invisible to everything except the device that created them. What follows is the setup I landed on that gives me the best of both worlds — a timer I can see on my panel and that still rings with the Voice PE's native alarm.

#Why voice timers are invisible

When you say "set a timer for 10 minutes" to a Voice PE (or any ESPHome/Wyoming satellite), Home Assistant creates a timer that lives on that satellite device. It's delivered to the device via ESPHome triggers like on_timer_started / on_timer_finished, and that's also what plays the alarm sound when it's done.

The catch: that timer is not a timer.* entity visible to the rest of Home Assistant, and there's no public websocket command for some other client (like my panel) to list or subscribe to active voice timers. So my display had no way to know a timer even existed.

#The plan

The trick is to stop relying on the device-bound timer and instead route everything through a regular timer. helper entity, which is a normal entity any client can watch:

  1. Make the voice assistant start a timer.kitchen helper instead of its own internal timer.
  2. Surface that helper's countdown on my custom display.
  3. Bridge the helper's "finished" event back to the Voice PE so it still rings with the real native alarm.

#Step 1 — Create the timer helper

In Home Assistant: Settings → Devices & Services → Helpers → Create Helper → Timer. I named mine "Kitchen", which results in the entity id timer.kitchen. Tick Restore so it survives a restart.

A timer.* entity carries everything we need: a state of idle / active / paused, and — while active — a finishes_at attribute with the absolute UTC end time.

#Step 2 — Point the voice agent at the helper

Two parts here.

First, in your voice pipeline settings, turn off "Prefer handling commands locally." With it on, the Voice PE kept intercepting "set a timer" and using its internal mechanism instead of my helper.

Second, I'm using an LLM agent for Assist, so I had to teach it about the helper explicitly. The LLM kept misreading the timer's leftover duration attribute and insisting "the kitchen timer is already set" instead of just starting a new one. This ## Timers section in the voice assistant's system prompt fixed it:

markdown
## Timers
- There is one timer: timer.kitchen. To start or restart it, call the timer.start
  service with the requested duration (e.g. "00:02:00" for two minutes). Always
  start it when the user asks for a timer, even if it was used before.
- A timer is running only when its state is "active". "idle" means nothing is
  running; "paused" means paused. Never say a timer is already set unless its
  state is "active".
- An idle timer's duration and remaining attributes are leftovers from the last
  run, not proof a timer is set. Ignore them when deciding whether to start one.
- To cancel a running timer, call timer.cancel.

The key insights: tell it the actual action (timer.start, which safely restarts even if already running), and correct the state semantics so it stops treating a finished timer as still set.

#Step 3 — Restore the native alarm

Routing through the helper means the Voice PE's on_timer_finished trigger never fires, so the alarm went silent. Looking at the Voice PE ESPHome config, the alarm chain is:

yaml
on_timer_finished:
  - switch.turn_on: timer_ringing

That timer_ringing switch runs the real ring_timer script including the ringing audio, audio ducking, the "say stop to silence it" wake word. The problem: timer_ringing is marked internal: true and the scripts have no name:, so none of it is callable from Home Assistant out of the box. It's all internal to the Voice PE only.

Fortunately the fix is small. The switch's id still exists in the compiled firmware — internal: only hides it from HA, it doesn't stop you referencing it. So in your Voice PE's ESPHome config, add a template button that flips that existing switch:

yaml
button:
  - platform: template
    name: "Ring timer"
    on_press:
      - switch.turn_on: timer_ringing

Flash it, and you get a new button.<your_device>_ring_timer entity that triggers the full native alarm — loop, ducking, stop-word, LED and all.

Then wire the helper's finish to that button with a new automation:

yaml
alias: Ring Voice PE when kitchen timer finishes
triggers:
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.kitchen
actions:
  - action: button.press
    target:
      entity_id: button.kitchen_ha_voice_pe_001_ring_timer

This ensures that when your Home Assistant native timer.kitchen entity finishes, the Voice PE internal timer_ringing script runs and acts like a normal Voice PE-native timer.

#Step 4 — Show the countdown on the display

Now that the timer is a real entity and everything is wired up and working on the Voice PE again, through our new Home Assistant native timer.kitchen entity, my panel can watch that entity via the normal websocket subscription!

Over the Home Assistant websocket API, the cleanest subscription is subscribe_entities with an explicit entity list, for example:

json
{
  "id":1,
  "type":"subscribe_entities",
  "entity_ids":[
    "timer.kitchen"
  ]
}

#The result

I say "set a timer for two minutes" like always, and now the Voice PE starts a timer.kitchen native Home Assistant timer and my desk panel shows the live countdown. When it finishes the small automation we added triggers the ring button and the Voice PE rings its real alarm. One helper entity ties it all together, and now the timer is visible to anything in my house that can speak websocket, not just the satellite that set it.

I hope documenting this windy road can help someone else stuck in this situation too. Stay tuned for a full walk through of the Steam Deck-like desk panel!

Discussion

On the Atmosphere

Open thread
Loading Bluesky comments...