Skip to content

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 duration
  • timer_state: Current state string
  • progress_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_finished service
  • 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?

  1. Persistence: Survives HA restarts with restore: true
  2. UI Integration: Native HA timer card support
  3. Automation Support: Trigger automations on timer events
  4. Multi-device Access: Any device can query/control any timer

Why Template Sensors?

  1. Pre-computed Values: Remaining seconds calculated server-side
  2. Attribute Bundling: All timer data in one subscription
  3. Update Efficiency: Single sensor update triggers display refresh

Why Area-Based Routing?

  1. No Mapping Maintenance: Adding a device only requires area assignment
  2. Consistent Naming: Entity names derive from area IDs
  3. 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:

  1. Sensor Subscription: Updates on HA sensor change
  2. 30-Second Interval: Catches missed updates
  3. 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