Architecture¶
This document explains the technical design of the Voice Assistant Persistent Timers system.
System Overview¶
flowchart TB
subgraph HA["Home Assistant"]
subgraph Entities["Timer Entities"]
T1["timer.kitchen"]
T2["timer.bedroom"]
T3["timer.playroom"]
end
subgraph Sensors["Template Sensors"]
S1["sensor.kitchen_timer_remaining"]
S2["sensor.bedroom_timer_remaining"]
S3["sensor.playroom_timer_remaining"]
end
subgraph Intents["Intent Scripts"]
I1["HassStartTimer"]
I2["HassCancelTimer"]
I3["HassPauseTimer"]
I4["HassTimerStatus"]
end
Auto["Automations<br/>Timer Finished โ esphome.device_timer_finished<br/>Timer Started โ esphome.device_timer_started"]
Entities --> |State Changes| Auto
Sensors --> |Updates| Auto
Intents --> |Commands| Auto
end
subgraph Satellite["Voice Assistant Satellite"]
subgraph VA["Voice Assistant"]
VAStubs["on_timer_* stubs"]
end
subgraph HASensors["HA Sensors (subscribed)"]
TR["timer_remaining"]
TS["timer_state"]
end
subgraph API["API Services"]
AF["timer_finished"]
AS["timer_started"]
AC["timer_cancelled"]
end
Display["Display Scripts<br/>draw_display ยท draw_timer_timeline"]
VA --> Display
HASensors --> Display
API --> Display
end
Auto <-->|ESPHome API| Satellite
Component Responsibilities¶
Home Assistant Components¶
Timer Entities (timer.<area>)¶
- Purpose: Single source of truth for timer state
- States:
idle,active,paused - Attributes:
duration,remaining,finishes_at - Why HA timers?: Persist across restarts, visible in UI, usable in automations
Template Sensors (sensor.<area>_timer_remaining_seconds)¶
- Purpose: Provide timer data in a satellite-friendly format
- State: Remaining seconds (integer)
- Attributes:
duration_seconds: Original durationtimer_state: Current state stringprogress_percent: Completion percentage- Update Rate: Every second when timer is active
Intent Scripts¶
- Purpose: Route voice commands to correct timer entity
- Key Innovation: Direct area routing using
preferred_area_id
# Old approach (device mapping)
device_name: >-
{% set area_to_device = {'kitchen': 'device_a', 'bedroom': 'device_b'} %}
{{ area_to_device.get(area, '') }}
# New approach (direct routing)
area_id: "{{ preferred_area_id | lower | replace(' ', '_') }}"
timer_entity: "{{ 'timer.' ~ area_id }}"
Automations¶
- Timer Finished: Calls
esphome.<device>_timer_finishedservice - Timer Started (optional): Immediate display update
- Timer Cancelled (optional): Cleanup notification
Satellite Device Components¶
API Services¶
services:
- service: timer_finished # Triggers alarm sound + display
- service: timer_started # Optional: fast display update
- service: timer_cancelled # Stops alarm if ringing
- service: stop_alarm # Manual alarm dismissal
Home Assistant Sensors¶
Subscribed to HA template sensors for display updates:
sensor:
- platform: homeassistant
id: timer_remaining
entity_id: sensor.${timer_area}_timer_remaining_seconds
Voice Assistant Stubs¶
Minimal handlers that tell HA "this device supports timers":
voice_assistant:
on_timer_started:
- logger.log: "Timer started (handled by HA)"
on_timer_finished:
- logger.log: "Timer finished (handled by HA)"
# etc.
Without these stubs, HA responds: "Timers are not supported on this device."
Data Flow¶
Starting a Timer¶
sequenceDiagram
actor User
participant VA as Voice Assistant
participant HA as Home Assistant
participant Timer as timer.kitchen
participant Sensor as Template Sensor
User->>VA: "Set a timer for 5 minutes"
VA->>HA: Speech-to-Text โ Intent
HA->>HA: Match HassStartTimer
HA->>Timer: timer.start(duration)
Timer->>Sensor: State update
Sensor->>VA: Sensor subscription
VA->>VA: draw_display
VA->>User: Progress bar shown
Timer Completion¶
sequenceDiagram
participant Timer as timer.kitchen
participant Auto as Automation
participant VA as Voice Assistant
actor User
Timer->>Timer: Reaches zero
Timer->>Auto: timer.finished event
Auto->>VA: esphome.device_timer_finished
VA->>VA: timer_ringing ON
VA->>User: Alarm sound + display
User->>VA: Touch screen / "stop"
VA->>VA: timer_ringing OFF
VA->>User: Display returns to idle
Cross-Device Control¶
sequenceDiagram
actor User as User (bedroom)
participant BR as Bedroom VA
participant HA as Home Assistant
participant Timer as timer.kitchen
participant KT as Kitchen VA
User->>BR: "Cancel the kitchen timer"
BR->>HA: Intent: CancelOtherTimer
HA->>HA: Extract target: kitchen
HA->>Timer: timer.cancel
Timer->>KT: Sensor update
KT->>KT: Display returns to idle
Design Decisions¶
Why HA Timer Entities?¶
- Persistence: Survives HA restarts with
restore: true - UI Integration: Native HA timer card support
- Automation Support: Trigger automations on timer events
- Multi-device Access: Any device can query/control any timer
Why Template Sensors?¶
- Pre-computed Values: Remaining seconds calculated server-side
- Attribute Bundling: All timer data in one subscription
- Update Efficiency: Single sensor update triggers display refresh
Why Area-Based Routing?¶
- No Mapping Maintenance: Adding a device only requires area assignment
- Consistent Naming: Entity names derive from area IDs
- Home Assistant Native: Uses existing area infrastructure
Why Event Stubs?¶
The Home Assistant Voice Assistant integration expects timer event handlers on satellite devices. By default, these handlers would create and manage timers locally on the device. Without any handlers, the device reports "timers not supported." Our stubs satisfy this requirement while delegating actual timer state management to Home Assistant timer entities.
Synchronization¶
Display Updates¶
Three mechanisms ensure display accuracy:
- Sensor Subscription: Updates on HA sensor change
- 30-Second Interval: Catches missed updates
- 1-Second Interval: Fast updates in final minute
State Consistency¶
The HA timer entity is always authoritative. The satellite device never stores timer state locally - it only:
- Subscribes to HA sensors for display
- Receives API calls for alarm triggers
- Provides UI for dismissing alarms
Security Considerations¶
- Timer state is stored in HA (encrypted API communication)
- No timer data persists on satellite device
- Cross-device commands require HA authentication
- API services don't expose sensitive operations