Czech voice control in Home Assistant: models and custom sentences

guide · smart home

Home Assistant does voice control through Assist. In Czech it comes down to two questions – which model recognises speech and synthesises the answer, and how natural sentences map to actions. Both are below – the model choice from my own testing and two sets of custom sentences, for energy consumption and the TV. Each part carries the date it was made.

01

Local vs. cloud models

Speech recognition (STT) ran locally through Whisper on an Intel NUC, the default model faster-whisper small-int8, Czech. Synthesis (TTS) through Piper, locally the cs_CZ-jirka-medium voice.

model where
faster-whisper small-int8 local (NUC) ~5 s worse
Nabu Casa (subscription) cloud ~2 s better

The local model handled simple things, but its Czech comprehension was noticeably worse and the response slower. In the end cloud recognition through Nabu Casa stayed; the local Piper can still be used for synthesis.

02

Custom sentence architecture

Control splits into two layers:

  • Sentences / Hassil intents in custom_sentences/cs/ – recognition of sentences and slots only. One file per topic (energy.yaml, tv.yaml).
  • Intent handling in packages/voice_control.yaml – intent_script with the actual HA actions.

configuration.yaml loads the packages:

homeassistant:
  packages: !include_dir_named packages

The intent name must be identical in both layers. Unknown intent AssistSetTvVolume means the sentence was recognised but no handler was found for it in intent_script.

03

Energy: power draw and consumption per period (23 October 2025)

The file custom_sentences/cs/energy.yaml recognises queries about the current power draw (optionally filtered) and about consumption over a period:

intents:
  EnergyGetPowerByFilter:
    slots:
      device_filter:
        optional: true
    data:
      - sentences:
          - 'Jaký je aktuální příkon'
          - 'Jaká je teď spotřeba'
      - sentences:
          - 'Jaký je aktuální příkon {device_filter}'
          - 'Jaká je teď spotřeba {device_filter}'
  EnergyGetEnergyPeriod:
    slots:
      period:
        required: true
    data:
      - sentences:
          - 'Kolik elektřiny {period}'
          - 'Jaká je {period} spotřeba'
lists:
  device_filter:
    values:
      - { in: 'shelly', out: 'shelly' }
      - { in: 'zásuvka|zásuvky|zásuvek', out: 'socket' }
      - { in: 'bojler', out: 'ariston' }
  period:
    values:
      - { in: 'dnes|dnešní', out: 'day' }
      - { in: 'tento týden|týdenní', out: 'week' }
      - { in: 'tento měsíc|měsíční', out: 'month' }

Handling EnergyGetPowerByFilter: with no spoken filter it uses the main Shelly 3EM sensor directly (device_class: power, state_class: measurement, unit W) and answers e.g. “Aktuální příkon celkem je 850 wattů.” With a filter it dynamically finds the power/active_power sensors matching {device_filter} and sums them.

EnergyGetEnergyPeriod maps {period} to sensor.energy_daily, sensor.energy_weekly and sensor.energy_monthly and returns consumption in kWh.

04

TV: volume and mute (30 October 2025)

The file custom_sentences/cs/tv.yaml handles two intents – setting the volume and toggling mute:

intents:
  AssistSetTvVolume:
    data:
      - sentences:
          - 'nastav hlasitost (televize|televizi|tv) (v|ve) {area} na {volume}'
          - 'dej (televizi|tv) (v|ve) {area} hlasitost {volume}'
  AssistToggleTvMute:
    data:
      - sentences:
          - '{toggle} [zvuk] (televize|televizi|tv) (v|ve) {area}'
lists:
  volume:
    range:
      from: 0
      to: 100
  toggle:
    values:
      - in: '(ztiš|ztlum|ztlumit|vypni zvuk|mute|umlč)'
        out: 'mute'
      - in: '(zapni zvuk|zruš ztlumení|unmute|zesil|zesílit)'
        out: 'unmute'

The {area} slot is an HA area, {volume} a number 0-100. The meaning is fixed by design – ztiš = mute, zesil = unmute – a mute toggle, not a gradual level change. Example command: “Nastav hlasitost televize v obýváku na 2.”

Handling AssistSetTvVolume: it converts the percentage to 0.0-1.0, finds the media_player.* in the area through area_id() and area_entities(), prefers an entity containing tv and, by supported_features, uses media_player.volume_set or the fallback volume_up/volume_down.

Mute is handled by the Philips TV (philips_js) integration, which does a real mute:

action: media_player.volume_mute
data:
  is_volume_muted: true

That beats volume_set or volume_up/down – on mute the TV keeps its original volume and restores it on unmute. Volume fallbacks cannot guarantee that.

05

Things to watch out for

  • Hassil in: is not a YAML list. Writing in: ['ztiš', 'mute'] fails with TypeError: expected string or bytes-like object, got 'list'. Write each variant as its own value, or as an alternation a|b|c.
  • Intent indentation. data: must be nested under the intent; a wrong indent makes loading fail with AttributeError: 'NoneType' object has no attribute 'get'.
  • HA Jinja has no Python operators. Bitwise feats & 4 does not work; test a bit arithmetically, ((feats // 4) % 2) == 1. Replace the global abs(x) with the x | abs filter.
  • Tell the error layers apart. “Při zpracování došlo k neočekávané chybě” means the intent was recognised but its HA action failed – not a sentence error.
  • Check entity availability. Philips ConnectionFailure: All connection attempts failed is a problem of the integration talking to the TV, not of intent recognition. Before mute/unmute, call homeassistant.update_entity and do not send the action when the player is unavailable or unknown.

_Energy came first in October 2025, the TV followed a week later; the text was last updated on 30 October 2025._

← back to explore