ThirdReality Voice/Music Assistant Dev Edition

Vielleicht - vermutlich sogar - bin ich zu blöd zu suchen, aber ich habe hier keinen Thread zum Voice/Music Assistant von ThirdReality gefunden, mit dem ich mich in den letzten zwei-drei Wochen ausführlich auseinandergesetzt habe, und den ich inzwischen für viele Anwendungen schön zum Laufen gebracht habe. Wenn jemand damit ebenfalls Erfahrungen hat, würde ich mich gern über das Gerät und die beste Nutzung austauschen. Ich spreche von dem Teil hier:

Disclaimer: Ich stehe in keinerlei Verbindung zu der Firma oder deren Vertreibern, bin nicht gesponsort und habe das Gerät mit meinem eigenen Geld gekauft. Ich bin ausschließlich am Erfahrungsaustausch interessiert, ohne jegliche kommerzielle oder finanzielle Absicht. Sowas muß man ja heute leider klarstellen!

niemand? Ist ein nettes Gerät für Leute, die gern mit ihrem Smarthome reden, aber nicht unbedingt bei allem den Herrn Bezos mithören lassen wollen.

Ich habe meinen gestern bekommen und war positiv überrascht von der problemlosen Inbetriebnahme (ich hatte davor noch gar nichts zum Thema Sprachassistant in Homeassistant gemacht).

Bisher habe ich leider noch nicht viel Zeit damit verbringen können, aber Uhrzeit & Raumtemperatur kann er mir schon mal ansagen und das Licht ein- & ausschalten :slight_smile: (fokussierter lokaler Assistent)

Das zweite Wake-Word (für den vollständigen lokalen Assistenten) habe ich auf Anhieb nicht zum Laufen bekommen. Das muss ich mir nochmal in Ruhe anschauen.

Bislang gefällt mir das Gerät und der Workflow in HA aber sehr gut!

Hast du ihn mit einer (lokalen) AI verknüpft, oder lässt du ihn über den Cloud-Assistenten von Nabucasa laufen und nutzt dessen “Intelligenz”? Ich habe versucht, alles lokal zu machen, und verwende die Cloud praktisch nicht (ich will ja gerade die Abhängigkeit von einer Cloud loswerden, als Ersatz für Alexa). Die Einrichtung des zweiten Wake-Words war bei mir “problemlos”, wenn ich “Hey Home Assistant” verwende. Ich hatte zuerst “Pyper” versucht, was das Gerät als Alternative anbietet, aber das Teil ist bei jedem “Also” in einem Satz angesprungen. Wie er “Also” als “Pyper” fehl-hören kann, erschließt sich mir nicht, war aber extrem nervig. Aber dann habe ich halt erstmal ein anderes Wake-Word verwendet.

Als Spracherkennung verwende ich lokal Whisper mit dem base-int8 Modell und faster-whisper als Library. Das läuft auf meiner Maschine (eine virtuelle Maschine auf meinem QNAP NAS) performant genug, dürfte aber gern etwas schneller sein. Als Text-to-Speech verwende ich Piper mit der de_DE-thorsten-medium Stimme mit length scale 1.3 (der Hesse spricht mir zu langsam :slight_smile: ).

Ohne die Intelligenz des Cloud-Assistenten habe ich viele typische Anfragen per Automation mit “trigger: conversation” gelöst, was erstaunlich gut funktioniert. Sachen die ich bisher implementiert habe sind unter anderem: Temperatur für einen bestimmten Raum und draussen abfragen/ansagen, ebenso Luftqualität (CO2-Wert von IKEA-Alpstuga Sensoren), Fragen nach dem Wetter beantworten (zB “regnet es heute?”, Wettervorhersage für heute oder morgen ansagen), Lautstärke des Assistenten per Sprache lauter/leiser oder auf einen bestimmten Wert stellen, Einkaufslisten verwalten (Artikel zufügen bzw löschen, alles abhaken, eine andere Liste verwenden, verwendete Liste ansagen etc), Tagesschau-Podcast abspielen für aktuelle Nachrichten, und ein Bestätigungston für Änderungen der Lautstärke am Gerät (was gut über das Edititeren einer Systemdatei /etc/adckey/adckey_function.sh auf dem Gerät funktioniert). Das alles funktioniert soweit ganz Alltags-tauglich.

NICHT vernünftig habe ich bisher Timer zum Laufen bekommen. Das heißt - schon, aber man kann einen abspielenden Timer nicht per Stimme anhalten bis man einen Button am Gerät drückt. Da bin ich noch am Experimentieren, aber das Hauptproblem scheint zu sein daß der Assist “schlau” ist und den Timer selbst verwaltet statt über HA, was wiederum bedeutet daß HA ihn nicht sieht und auch nicht beeinflussen kann.

Was mich ebenfalls nervt ist der “Thinking”-Ton des Voice Assist, der nützlich aber zu schrill und laut ist.

Ein proof-of-principle Beispiel für die heutige Wettervorhersage (was die Pirateweather-Integration verwendet, aber prinzipiell mit einer anderen weather-entity genauso funktionieren sollte):

alias: "Assist: Wettervorhersage heute"
description: ""
triggers:
  - trigger: conversation
    command:
      - wie wird das wetter heute
      - wie wird das wetter
      - wie ist die vorhersage
      - was ist die vorhersage
      - wie ist die wettervorhersage
      - was ist die wettervorhersage
      - wettervorhersage
      - wettervorhersage heute
      - wettervorhersage für heute
      - was ist die wettervorhersage
      - wie ist die wettervorhersage
      - wie ist die vorhersage heute
      - wie ist die vorhersage für heute
      - wie ist die wettervorhersage für heute
      - was ist die vorhersage für heute
      - was ist die wettervorhersage für heute
      - gib mir die wettervorhersage für heute
conditions:
  - condition: template
    value_template: "{{ 'morgen' not in (trigger.sentence | default('') | lower) }}"
actions:
  - variables:
      weather_entity: weather.pirateweather
  - action: weather.get_forecasts
    target:
      entity_id: "{{ weather_entity }}"
    data:
      type: daily
    response_variable: forecast_data
  - variables:
      forecast: "{{ forecast_data[weather_entity]['forecast'][0] }}"
      condition: "{{ forecast.condition | default('unknown') }}"
      temp_high: "{{ forecast.temperature | default(none) }}"
      temp_low: "{{ forecast.templow | default(none) }}"
      precip: "{{ forecast.precipitation | default(0) }}"
      precip_prob: "{{ forecast.precipitation_probability | default(none) }}"
      wind: "{{ forecast.wind_speed | default(none) }}"
      condition_de: |-
        {% set c = forecast.condition | default('unknown') %} {% set map = {
          'clear-night': 'klar',
          'cloudy': 'bewölkt',
          'exceptional': 'ungewöhnlich',
          'fog': 'neblig',
          'hail': 'Hagel',
          'lightning': 'Gewitter',
          'lightning-rainy': 'Gewitter mit Regen',
          'partlycloudy': 'teilweise bewölkt',
          'pouring': 'starker Regen',
          'rainy': 'regnerisch',
          'snowy': 'schneit',
          'snowy-rainy': 'Schneeregen',
          'sunny': 'sonnig',
          'windy': 'windig',
          'windy-variant': 'windig und bewölkt'
        } %} {{ map.get(c, c) }}
  - set_conversation_response: >
      Heute wird es voraussichtlich {{ condition_de }}. Die Temperatur liegt
      zwischen {{ temp_low | round(0) }} und {{ temp_high | round(0) }} Grad. {%
      if precip_prob is not none %} Die Regenwahrscheinlichkeit liegt bei {{
      precip_prob | round(0) }} Prozent. {% endif %} {% if precip | float(0) > 0
      %} Erwarteter Niederschlag: {{ precip | round(1) }} Millimeter. {% endif
      %}
mode: single

Ich will den Assistenten ausschliesslich lokal verwenden und spiele deshalb gerade mit den beiden lokalen Modellen (“speech-to-phrase” und “fast-whisper”) herum. Das Problem mit dem zweiten Wake-word hat sich dahingehend gelöst, dass manche bei mir einfach nicht funktionieren wollen. “OK Nabu”, “Hey Mycroft” und “Alexa” klappen problemlos auch quer durch den ganzen Raum. “Hey Jarvis”, “hey homeassistant” oder “OK Computer” nur in 10% aller Fälle und auch nur wenn ich direkt davor stehe und ihn anbrülle.

Ultimativ hätte ich gerne ein Wake-word für schnelle, einfache Aufgaben, die auf meinem stromsparenden NUC erkannt werden und ein Wake-word für komplexere Aufgaben, das ich mit einem lokalen LLM verknüpfe. Den dazugehörigen “Boliden” will ich aber nicht unbedingt 24/7 laufen lassen müssen. Das ist aber noch Zukunftsmusik.

Vielen Dank für dein Beispiel. Damit fange ich mal an :slight_smile:

ja, “OK Nabu” funktioniert praktisch immer, und “Hey Home Assistant” leidlich gut - braucht aber entweder mehr Lautstärke oder klarere Aussprache um erkannt zu werden.

Meine Idee war ursprünglich, “OK Nabu” zu verwenden wenn ich den Cloud-Assistenten verwenden will, und “Hey Home Assistant” für alle lokalen Sachen. Aber deine Idee, eines der Wake Words mit einer lokalen AI zu verknüpfen ist noch viel besser. Das probier ich auch mal. Bei mir ist nur das Problem daß ich mein HA auf einem NAS laufen habe, das keine vernünftige GPU hat, und auch nur eine bescheidene CPU. Da muß ich schauen, ob ich ein “tiny” LLM zum Laufen kriege ohne 10 Sekunden warten zu müssen bis HA auf einen Befehl reagiert.

Mit meiner bisherigen, rein lokalen Whisper/Piper-Kombination ist alles recht schnell. Länger als 2 Sekunden muß ich auf nix warten. Damit kann ich gut leben.

Sieht ansprechend aus. Habe ich noch garnicht mitbekommen.

Ich selber bin Freund von Sprachassistenten und mache viele eigene Automatisierungen. Ich habe drei Voice Geräte von HA selbst. Die sind schon solide. Für Wohnzimmer mit den Fernsehergeräuschen wollte ich einen besseren finden. Dafür habe ich den Satellite1 mit 4 Mikrofonen statt 2 gekauft aber finde die Performance ähnlich zum HA Voice. Hast du auch einen HA Voice und kannst das Gerät von dir vergleichen?

Beim Timer fand ich die inside Lösung gut. Man kann den Timer mitten im Klingeln mit dem Wort “Abbrechen” beenden. Also ohne Aktivierungswort. Bei dir nicht? Ich fand es aber auch doof, dass der Timer nur im Gerät war und es keine Timer Entität in Home assistant gab. Ich habe dann einfach die built in sentence aus github kopiert und bei mir lokal abgespeichert. Dann lasse ich maximal zwei Timer Entitäten starten. Dadurch kann man zb folgendes machen

  • Gleichzeitig in allen Räumen Bescheid geben, dass der Timer fertig ist, während im Raum wo der Timer gestellt wurde solange klingelt, bis der Bewegungsmelder im jeweiligen Raum aktiviert wird
  • Timer in Dashboards visualisieren
  • Timer von überall beenden

leider nein, ich hab bisher nur Alexa-Geräte verwendet. Die funktionieren zwar gut, aber die Cloud von Bezos zu füttern ist nicht unbedingt das, was ich möchte. Von daher kann ich den ThirdReality Assistenten nur mit Alexa vergleichen. Da muß ich sagen, daß “out of the box” Alexa besser funktioniert und mehr versteht. Ich bin sicher daß der ThirdReality Assistent Alexa übertrumpfen kann, wenn man ihn mit einer lokalen AI verknüpft. Ich fürchte nur daß meine derzeitige Hardware nicht leistungsfähig genug ist.

Hmmm, das hatte ich nie probiert. Mein Test gerade eben hat aber ergeben, daß ich den klingelnden Timer mit “Stop” abbrechen kann. Oft funktioniert auch “OK”, selten auch “Ende”. “Abbrechen” versteht er bei mir dagegen nicht. Aber mit “Stop” kann ich sehr gut leben. DANKE für den Super-Tip!

kannst du das etwas erläutern? Klingt genau nach dem was ich auch brauche! Die Github-Seite mit den (unter anderem deutschen Sätzen) kenn ich natürlich, aber wie krieg ich den Voice Assistenten dazu, den Timer in HA zu setzen als auf dem Gerät selbst?

Ach stimmt, es war nicht abbrechen sondern stop.

Wegen dem Timer. Scheinbar gibt es eine Reihenfolge bei den Sätzen. Erst wird geschaut, ob der User selbst eine erstellt hat und wenn nicht, wird in den built-in-sentence geschaut. Um mir nicht alle möglichen Sätze selber auszudenken und um die integrierten Sätze vollumfänglich auszuhebeln, habe ich auf github die built-in-sentence kopiert und wie hier ( Setting up custom sentences in configuration.yaml - Home Assistant ) beschrieben in meine yaml Datei eingefügt. Ich sehe jetzt, dass die Sätze in Github mittlerweile in mehrere Dateien verteilt worden. Hier die von damals (mit leichter Verbesserung).

conversation:
intents:

Zusammenfassung
SetTimer:
    data:
      - sentences:
          - "[einen ]<timer_duration> Timer[ <timer_set_end_of_sentence>]"
          - "[einen ]Timer[ (auf|für)] <timer_duration>[ <timer_set_end_of_sentence>]"
          - "[einen ]<timer_duration> Timer für {timer_name:name}[ <timer_set_end_of_sentence>]"
          - "[einen ]<timer_duration> Timer <timer_set_end_of_sentence> für {timer_name:name}"
          - "[einen ]Timer[ (auf|für)] <timer_duration> für[ (<possessivpronom_mein>|<possessivpronom_unser>|<artikel_bestimmt>)] {timer_name:name} <timer_set_end_of_sentence>"
          - "[einen ]Timer[ (auf|für)] <timer_duration> [<timer_set_end_of_sentence> ]für[ (<possessivpronom_mein>|<possessivpronom_unser>|<artikel_bestimmt>)] {timer_name:name}"
          - "[einen ]Timer (auf|für) <timer_duration> <timer_set_end_of_sentence> und ihn {timer_name:name} nennen"
          - "[einen ]Timer[ (auf|für)] <timer_duration> namens {timer_name:name}[ <timer_set_end_of_sentence>]"
          - "[einen ]{timer_name:name} Timer[ (auf|für)] <timer_duration>[ <timer_set_end_of_sentence>]"
          - "[einen ]{timer_name:name} Timer <timer_set_end_of_sentence> (auf|für) <timer_duration>"
          - "<timer_set>[ einen] <timer_duration> Timer"
          - "<stelle> einen <timer_duration> Timer ein"
          - "<timer_set>[ einen] Timer (auf|für) <timer_duration> [{irgendwas}]"
          - "<timer_set>[ einen] Timer (auf|für) <timer_duration> (namens|für[ (<possessivpronom_mein>|<possessivpronom_unser>|<artikel_bestimmt>)]) {timer_name:name}"
          - "<timer_set> einen Timer (auf|für) <timer_duration> und ([be]nenne|nenn) ihn {timer_name:name}"
          - "<timer_set>[ einen] <timer_duration> Timer (namens|für[ (<possessivpronom_mein>|<possessivpronom_unser>|<artikel_bestimmt>)]) {timer_name:name}"
          - "<timer_set>[ einen] {timer_name:name} Timer (auf|für) <timer_duration>"
          - "<timer_set>[ einen] Timer (namens|für[ (<possessivpronom_mein>|<possessivpronom_unser>|<artikel_bestimmt>)]) {timer_name:name} (auf|für) <timer_duration>"
      - sentences:
          - "{timer_command:conversation_command} (in|nach) <timer_duration>"
          - "[(mach[e]|fahr[e]|schalt[e]) ](in|nach) <timer_duration> {timer_command:conversation_command}"
        response: command
  TimerAbfragen:
    data:
      - sentences:
          - "Timer (Restzeit|Status)"
          - "(Restzeit|Status) (des|meines|unseres) Timer[s]"
          - "(Restzeit|Status)[ (von (dem|meinem|unserem)|vom)] Timer"
          - "wie lang[e]"
          - "wie lang[e] (läuft|braucht|hat|dauert|geht) (der|mein|unser) {irgendwas} [noch]"
          - "was (macht|sagt) (der|mein|unser) (Timer|Termin) [{irgendwas}]"
          - "<timer_start> Timer (Restzeit|Status)"
          - "(Restzeit|Status) (des|meines|unseres) <timer_start> Timer[s]"
          - "(Restzeit|Status)[ (von (dem|meinem|unserem)|vom)] <timer_start> Timer"
          - "wie lang[e] (läuft|braucht|hat|dauert|geht) (der|mein|unser) <timer_start> Timer"
          - "was macht (der|mein|unser) <timer_start> Timer"
          - "{area} Timer (Restzeit|Status)"
          - "(Restzeit|Status) (des|meines|unseres) {area} Timer[s]"
          - "(Restzeit|Status) (des|meines|unseres) Timer[s] <area>"
          - "(Restzeit|Status)[ (von (dem|meinem|unserem)|vom)] {area} Timer"
          - "(Restzeit|Status)[ (von (dem|meinem|unserem)|vom)] Timer <area>"
          - "wie lang[e] (läuft|braucht|hat|dauert|geht) (der|mein|unser) {area} Timer"
          - "wie lang[e] (läuft|braucht|hat|dauert|geht) (der|mein|unser) Timer <area>"
          - "was macht (der|mein|unser) {area} Timer"
          - "was macht (der|mein|unser) Timer <area>"
          - "{timer_name:name} Timer (Restzeit|Status)"
          - "(Restzeit|Status) (des|meines|unseres) {timer_name:name} Timer[s]"
          - "(Restzeit|Status)[ (von (dem|meinem|unserem)|vom)] {timer_name:name} Timer"
          - "wie lang[e] (läuft|braucht|hat|dauert|geht) (der|mein|unser) {timer_name:name} Timer"
          - "was macht (der|mein|unser) {timer_name:name} Timer"
          - "wann endet (der|mein|unser) timer[ für [(<artikel_bestimmt>|<possessivpronom_mein>|<possessivpronom_unser>) ]{timer_name:name}]"
          - "wann endet (der|mein|unser) (<timer_start>|{area}) timer"
          - "wann endet (der|mein|unser) Timer <area>"
          - "wann ist (der|mein|unser) timer[ für [(<artikel_bestimmt>|<possessivpronom_mein>|<possessivpronom_unser>) ]{timer_name:name}] (zu ende|fertig)"
          - "wann ist (der|mein|unser) (<timer_start>|{area}) timer (zu ende|fertig)"
          - "wann ist (der|mein|unser) Timer <area> (zu ende|fertig)"
          - "wann läuft (der|mein|unser) timer[ für [(<artikel_bestimmt>|<possessivpronom_mein>|<possessivpronom_unser>) ]{timer_name:name}] ab"
          - "wann läuft (der|mein|unser) (<timer_start>|{area}) timer ab"
          - "wann läuft (der|mein|unser) Timer <area> ab"
          - "wann ist [(der|mein|unser) ]{timer_name:name} Timer (zu ende|fertig)"
          - "wann (ist|sind) [(der|die|das|mein|meine|unser|unsere) ]{timer_name:name} fertig"
  TimerBeenden:
    data:
      - sentences:
          - "<timer_cancel>[ (den|meinen|unseren)] {irgendwas}"
          - "brech[e] (den|meinen|unseren) Timer ab"
          - "<timer_cancel>[ (den|meinen|unseren)] <timer_start> Timer"
          - "brech[e] (den|meinen|unseren) <timer_start> Timer ab"
          - "<timer_cancel>[ (den|meinen|unseren)] Timer für <timer_start>"
          - "brech[e] (den|meinen|unseren) Timer für <timer_start> ab"
          - "<timer_cancel>[ (den|meinen|unseren)] {area} Timer"
          - "brech[e] (den|meinen|unseren) {area} Timer ab"
          - "<timer_cancel>[ (den|meinen|unseren)] Timer <area>"
          - "brech[e] (den|meinen|unseren) Timer <area> ab"
          - "<timer_cancel>[ (den|meinen|unseren)] {timer_name:name} Timer"
          - "brech[e] (den|meinen|unseren) {timer_name:name} Timer ab"
          - "<timer_cancel>[ (den|meinen|unseren)] Timer für [<artikel_bestimmt> ]{timer_name:name}"
          - "brech[e] (den|meinen|unseren) Timer für [<artikel_bestimmt> ]{timer_name:name} ab"
          - "[(den|meinen|unseren) ][(<timer_start>|{timer_name:name}|{area}) ]Timer <timer_cancel_end_of_sentence>"
          - "[(den|meinen|unseren) ]Timer (für [<artikel_bestimmt> ]{timer_name:name}|<area>) <timer_cancel_end_of_sentence>"
lists:
  irgendwas:
    wildcard: true
  min:
    wildcard: true
  minuten:
    wildcard: true
  stell:
    wildcard: true
  sekunden:
    wildcard: true
  stunden:
    wildcard: true
  timer_seconds:
    range:
      from: 1
      to: 100
  timer_minutes:
    range:
      from: 1
      to: 100
  timer_hours:
    range:
      from: 1
      to: 100
  timer_name:
    wildcard: true
  timer_command:
    wildcard: true
expansion_rules:
  timer_set: "(starte|setze|<stelle>|<erstelle>)"
  timer_set_end_of_sentence: "{irgendwas}"
  timer_cancel: "(beende|stopp[e]|lösch[e])"
  timer_cancel_end_of_sentence: "(ausschalten|deaktivieren|abbrechen|stoppen|aus)"
  timer_duration_seconds: "{timer_seconds:sekunden} Sekunde[n]"
  timer_duration_minutes: "{timer_minutes:minuten} Minute[n][ [und ]{timer_seconds:sekunden} Sekunde[n]]"
  timer_duration_hours: "{timer_hours:stunden} Stunde[n][ [und ]{timer_minutes:minuten} Minute[n]][ [und ]{timer_seconds:sekunden} Sekunde[n]]"
  timer_duration: "<timer_duration_seconds>|<timer_duration_minutes>|<timer_duration_hours>"
  timer_decrease: "(verringere|verkürze|reduziere)"
  timer_decrease_end_of_sentence: "(verringern|verkürzen|reduzieren)"

  timer_start_seconds: "{timer_seconds:start_seconds} Sekunde[n]"
  timer_start_minutes: "{timer_minutes:start_minutes} Minute[n][ [und ]{timer_seconds:start_seconds} Sekunde[n]]"
  timer_start_hours: "{timer_hours:start_hours} Stunde[n][ [und ]{timer_minutes:start_minutes} Minute[n]][ [und ]{timer_seconds:start_seconds} Sekunde[n]]"
  timer_start: "<timer_start_seconds>|<timer_start_minutes>|<timer_start_hours>"

intent_script:

Zusammenfassung
SetTimer:
   action:
      - service: timer.start
        data: 
          entity_id: >
           {% if is_state('timer.timer1', 'active') %}
            timer.timer2
           {% else %}
            timer.timer1
           {% endif %}
          duration: >
            {% if stunden is not defined %}
              {% set stunden = '00' %}
            {% else %}
              {% set stunden = stunden | float | int %}
            {% endif %}

            {% if minuten is not defined %}
              {% set minuten = '00' %}
            {% else %}
              {% set minuten = minuten | float | int %} 
            {% endif %}

            {% if sekunden is not defined %}
              {% set sekunden = '00' %}
            {% else %}
              {% set sekunden = sekunden | float | int %}
            {% endif %}

            {{ stunden }}:{{ minuten }}:{{ sekunden }}
   speech:
#      text: "{% if stunden is not none %}{{ stunden | float | int }} {{ 'Stunde' if stunden == 1 else 'Stunden' }}{% endif %}{% if minuten is not none %} {{ minuten | float | int }} {{ 'Minute' if minuten == 1 else 'Minuten' }}{% endif %}{% if sekunden is not none %} {{ sekunden | float | int }} {{ 'Sekunde' if sekunden == 1 else 'Sekunden' }}{% endif %} ab jetzt"
     text: "{{ stunden | float | int if stunden != NULL }} {{ 'stunden' if stunden != NULL }} {{ minuten | float | int if minuten != NULL }} {{ 'minuten' if minuten != NULL }} {{ sekunden | float | int if sekunden != NULL }} {{ 'sekunden' if sekunden != NULL }} ab jetzt"
Zusammenfassung
  TimerBeenden:
   action:
      - service: script.turn_on
        data:
         entity_id: script.timerbeenden
Zusammenfassung
  TimerAbfragen:
    speech:
         text: >
          {% if is_state('timer.timer2', 'active') %}
           {% set stunden = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%H', false) %}
           {% set minuten = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%M', false) %}
           {% set sekunden = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%S', false) %}
           {% if stunden == '01' %}
             {% set stunden = 'eine' %}
             {% set stundentext = 'stunde,' %}
           {% elif stunden != '00' %}
             {% set stunden = stunden | int %}
             {% set stundentext = 'stunden,' %}
           {% else %}
             {% set stunden = '' %}
             {% set stundentext = '' %}
           {% endif %}

           {% if minuten == '01' %}
             {% set minuten = 'eine' %}
             {% set minutentext = 'minute und' %}
           {% elif minuten != '00' %}
             {% set minuten = minuten | int %}
             {% set minutentext = 'minuten und' %}
           {% else %}
             {% set minuten = '' %}
             {% set minutentext = '' %}
           {% endif %}

           {% if sekunden == '01' %}
             {% set sekunden = 'eine' %}
             {% set sekundentext = 'sekunde' %}
           {% elif sekunden != '00' %}
             {% set sekunden = sekunden | int %}
             {% set sekundentext = 'sekunden' %}
           {% else %}
             {% set sekunden = '' %}
             {% set sekundentext = '' %}
           {% endif %}

           {% set stunden2 = (as_timestamp(state_attr('timer.timer2', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%H', false) %}
           {% set minuten2 = (as_timestamp(state_attr('timer.timer2', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%M', false) %}
           {% set sekunden2 = (as_timestamp(state_attr('timer.timer2', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%S', false) %}
           {% if stunden2 == '01' %}
             {% set stunden2 = 'eine' %}
             {% set stundentext2 = 'stunde,' %}
           {% elif stunden2 != '00' %}
             {% set stunden2 = stunden2 | int %}
             {% set stundentext2 = 'stunden,' %}
           {% else %}
             {% set stunden2 = '' %}
             {% set stundentext2 = '' %}
           {% endif %}

           {% if minuten2 == '01' %}
             {% set minuten2 = 'eine' %}
             {% set minutentext2 = 'minute und' %}
           {% elif minuten2 != '00' %}
             {% set minuten2 = minuten2 | int %}
             {% set minutentext2 = 'minuten und' %}
           {% else %}
             {% set minuten2 = '' %}
             {% set minutentext2 = '' %}
           {% endif %}

           {% if sekunden2 == '01' %}
             {% set sekunden2 = 'eine' %}
             {% set sekundentext2 = 'sekunde' %}
           {% elif sekunden2 != '00' %}
             {% set sekunden2 = sekunden2 | int %}
             {% set sekundentext2 = 'sekunden' %}
           {% else %}
             {% set sekunden2 = '' %}
             {% set sekundentext2 = '' %}
           {% endif %}

           du hast beim ersten Timer noch {{ stunden if stunden != '' }} {{ stundentext if stunden != '' }} {{ minuten if minuten != '' }} {{ minutentext if minuten != '' }} {{ sekunden if sekunden != '' }} {{ sekundentext if sekunden != '' }} "und beim zweiten Timer noch " {{ stunden2 if stunden2 != '' }} {{ stundentext2 if stunden2 != '' }} {{ minuten2 if minuten2 != '' }} {{ minutentext2 if minuten2 != '' }} {{ sekunden2 if sekunden2 != '' }} {{ sekundentext2 if sekunden2 != '' }}
          {% else %}
           {% set stunden = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%H', false) %}
           {% set minuten = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%M', false) %}
           {% set sekunden = (as_timestamp(state_attr('timer.timer1', 'finishes_at')) - as_timestamp(now())) | timestamp_custom('%S', false) %}
           {% if stunden == '01' %}
            {% set stunden = 'eine' %}
            {% set stundentext = 'stunde,' %}
           {% elif stunden != '00' %}
            {% set stunden = stunden | int %}
            {% set stundentext = 'stunden,' %}
           {% else %}
            {% set stunden = '' %}
            {% set stundentext = '' %}
           {% endif %}

           {% if minuten == '01' %}
            {% set minuten = 'eine' %}
            {% set minutentext = 'minute und' %}
           {% elif minuten != '00' %}
            {% set minuten = minuten | int %}
            {% set minutentext = 'minuten und' %}
           {% else %}
            {% set minuten = '' %}
            {% set minutentext = '' %}
           {% endif %}

           {% if sekunden == '01' %}
            {% set sekunden = 'eine' %}
            {% set sekundentext = 'sekunde' %}
           {% elif sekunden != '00' %}
            {% set sekunden = sekunden | int %}
            {% set sekundentext = 'sekunden' %}
           {% else %}
            {% set sekunden = '' %}
            {% set sekundentext = '' %}
           {% endif %}

           du hast noch {{ stunden if stunden != '' }} {{ stundentext if stunden != '' }} {{ minuten if minuten != '' }} {{ minutentext if minuten != '' }} {{ sekunden if sekunden != '' }} {{ sekundentext if sekunden != '' }}
          {% endif %}

Und dann brauchst du noch zwei Helfer Timer. Mir reichen zwei aber so wie ich es habe, würde er beim dritten mal einen der beiden ersetzen.

Und hier noch das Skript zum beenden des Timers:

Zusammenfassung
sequence:
  - choose:
      - conditions:
          - condition: state
            entity_id: input_boolean.timerfertig
            state:
              - "on"
        sequence:
          - action: input_boolean.turn_off
            metadata: {}
            target:
              entity_id: input_boolean.timerfertig
            data: {}
          - action: assist_satellite.announce
            metadata: {}
            data:
              preannounce: false
              message: Timer beendet
            target:
              entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
      - conditions:
          - condition: state
            entity_id: timer.timer1
            state:
              - active
          - condition: state
            entity_id: timer.timer2
            state:
              - idle
        sequence:
          - action: timer.cancel
            metadata: {}
            target:
              entity_id: timer.timer1
            data: {}
          - action: assist_satellite.announce
            metadata: {}
            data:
              preannounce: false
              message: Timer gelöscht
            target:
              entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
      - conditions:
          - condition: state
            entity_id: timer.timer2
            state:
              - active
          - condition: state
            entity_id: timer.timer1
            state:
              - idle
        sequence:
          - action: timer.cancel
            metadata: {}
            target:
              entity_id:
                - timer.timer2
            data: {}
          - action: assist_satellite.announce
            metadata: {}
            data:
              preannounce: false
              message: Timer gelöscht
            target:
              entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
      - conditions:
          - condition: state
            entity_id: timer.timer2
            state:
              - active
          - condition: state
            entity_id: timer.timer1
            state:
              - active
        sequence:
          - action: timer.cancel
            metadata: {}
            target:
              entity_id:
                - timer.timer2
            data: {}
          - action: assist_satellite.announce
            metadata: {}
            data:
              preannounce: false
              message: Timer gelöscht
            target:
              entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
          - action: assist_satellite.ask_question
            metadata: {}
            data:
              question: >-
                Es sind zwei Timer aktiv. Möchtest du den ersten oder den
                zweiten beenden?
              preannounce: false
              entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
              answers:
                - id: "1"
                  sentences:
                    - "[Den|dem] ersten"
                - id: "2"
                  sentences:
                    - "[Den|dem] zweiten"
            response_variable: antwort
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ antwort.id == 1 }}"
                sequence:
                  - action: timer.cancel
                    metadata: {}
                    target:
                      entity_id: timer.timer1
                    data: {}
                  - action: assist_satellite.announce
                    metadata: {}
                    data:
                      preannounce: false
                      message: Ersten Timer gelöscht
                    target:
                      entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
              - conditions:
                  - condition: template
                    value_template: "{{ antwort.id == 2 }}"
                sequence:
                  - action: timer.cancel
                    metadata: {}
                    target:
                      entity_id:
                        - timer.timer2
                    data: {}
                  - action: assist_satellite.announce
                    metadata: {}
                    data:
                      preannounce: false
                      message: Zweiten Timer gelöscht
                    target:
                      entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
    default:
      - action: assist_satellite.announce
        metadata: {}
        data:
          preannounce: false
          message: Es ist kein Timer gestellt
        target:
          entity_id: "{{ states.input_text.aktueller_assist_entity_id.state }}"
alias: TimerBeenden
description: ""

Und dann brauchst natürlich noch eine Automatisierung für Timer fertig. Hier eine vereinfachte Version:

Zusammenfassung
description: ""
mode: single
triggers:
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.timer1
  - trigger: event
    event_type: timer.finished
    event_data:
      entity_id: timer.timer2
conditions: []
actions:
  - action: input_boolean.turn_on
    metadata: {}
    target:
      entity_id: input_boolean.timerfertig
    data: {}
  - if:
      - condition: state
        entity_id: binary_sensor.knx_bewegung_kueche
        state:
          - "on"
    then:
      - action: assist_satellite.announce
        target:
          entity_id: assist_satellite.voice_kuche_assist_satellit
        data:
          preannounce: false
          media_id:
            media_content_id: media-source://media_source/media/timer_finished.flac
            media_content_type: audio/flac
            metadata:
              title: timer_finished.flac
              thumbnail: null
              media_class: music
              children_media_class: null
              navigateIds:
                - {}
                - media_content_type: app
                  media_content_id: media-source://media_source
      - delay:
          hours: 0
          minutes: 0
          seconds: 1
          milliseconds: 0
      - action: assist_satellite.announce
        target:
          entity_id: assist_satellite.voice_kuche_assist_satellit
        data:
          preannounce: false
          media_id:
            media_content_id: media-source://media_source/media/timer_finished.flac
            media_content_type: audio/flac
            metadata:
              title: timer_finished.flac
              thumbnail: null
              media_class: music
              children_media_class: null
              navigateIds:
                - {}
                - media_content_type: app
                  media_content_id: media-source://media_source
    else:
      - repeat:
          until:
            - condition: or
              conditions:
                - condition: state
                  entity_id: assist_satellite.voice_kuche_assist_satellit
                  state:
                    - listening
                - condition: state
                  entity_id: input_boolean.timerfertig
                  state:
                    - "off"
                - condition: state
                  entity_id: binary_sensor.knx_bewegung_kueche
                  state:
                    - "on"
          sequence:
            - action: assist_satellite.announce
              target:
                entity_id: assist_satellite.voice_kuche_assist_satellit
              data:
                preannounce: false
                media_id:
                  media_content_id: media-source://media_source/media/timer_finished.flac
                  media_content_type: audio/flac
                  metadata:
                    title: timer_finished.flac
                    thumbnail: null
                    media_class: music
                    children_media_class: null
                    navigateIds:
                      - {}
                      - media_content_type: app
                        media_content_id: media-source://media_source
            - delay:
                hours: 0
                minutes: 0
                seconds: 1
                milliseconds: 0
  - action: input_boolean.turn_off
    metadata: {}
    target:
      entity_id: input_boolean.timerfertig
    data: {}

Abschließend brauchst du den Ton apps/assist_satellite/sounds/timer_finished.flac at main · OHF-Voice/apps · GitHub

Danke für die ausführliche Antwort, das hilft enorm! Ich mach mich gleich mal dran, das selbst zu implementieren, mit ein paar Veränderungen die mir im Prozess einfallen werden :slight_smile:

Viel Spaß dabei :slightly_smiling_face: und sollte dir was tolles einfallen, sag Bescheid :grin: