From eb5f04096e1f7466e8daba94a25d218435b6d675 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Mon, 7 Oct 2024 15:15:29 -0400 Subject: [PATCH] feat(hass): use ha-fallback with custom sentences --- .../homie/modules/home-assistant/assist.nix | 48 + .../modules/home-assistant/docs/functions.nix | 284 +----- .../homie/modules/home-assistant/docs/prompt | 1 - .../homie/modules/home-assistant/frontend.nix | 8 + .../home-assistant/spotify-sentences.nix | 23 + .../homie/modules/home-assistant/spotify.nix | 25 +- .../home-assistant/timer-sentences.nix | 942 ++++++++++++++++++ .../homie/modules/home-assistant/timer.nix | 78 +- flake.lock | Bin 55143 -> 56179 bytes flake.nix | Bin 11058 -> 11334 bytes inputs.nix | 9 + legacyPackages/hass-components/default.nix | 1 + .../ha-fallback-conversation/default.nix | 16 + 13 files changed, 1122 insertions(+), 313 deletions(-) create mode 100644 devices/homie/modules/home-assistant/spotify-sentences.nix create mode 100644 devices/homie/modules/home-assistant/timer-sentences.nix create mode 100644 legacyPackages/hass-components/ha-fallback-conversation/default.nix diff --git a/devices/homie/modules/home-assistant/assist.nix b/devices/homie/modules/home-assistant/assist.nix index c4b169d7..37287140 100644 --- a/devices/homie/modules/home-assistant/assist.nix +++ b/devices/homie/modules/home-assistant/assist.nix @@ -11,10 +11,58 @@ services = { home-assistant = { + package = + (pkgs.home-assistant.override { + packageOverrides = final: prev: { + # Needed for spotifyplus + inherit (self.packages.${pkgs.system}) urllib3; + + # Version before HassStartTimer + home-assistant-intents = final.buildPythonPackage rec { + pname = "home-assistant-intents"; + version = "2024.4.3"; + format = "wheel"; + disabled = final.pythonOlder "3.9"; + src = final.fetchPypi { + inherit version format; + pname = "home_assistant_intents"; + dist = "py3"; + python = "py3"; + hash = "sha256-GraYVtioKIoKlPRBhhhzlbBfI6heXAaA1MQpUqAgEDQ="; + }; + build-system = [ + final.setuptools + ]; + doCheck = false; + pytestFlagsArray = [ + "intents/tests" + ]; + }; + }; + }) + .overrideAttrs { + disabledTestPaths = [ + # we neither run nor distribute hassfest + "tests/hassfest" + # we don't care about code quality + "tests/pylint" + # redundant component import test, which would make debugpy & sentry expensive to review + "tests/test_circular_imports.py" + # don't bulk test all components + "tests/components" + + # Make old intent version build + "tests/scripts/test_check_config.py" + "tests/test_bootstrap.py" + "tests/helpers" + ]; + }; + customComponents = builtins.attrValues { inherit (self.legacyPackages.${pkgs.system}.hass-components) extended-ollama-conversation + ha-fallback-conversation tuya-local ; }; diff --git a/devices/homie/modules/home-assistant/docs/functions.nix b/devices/homie/modules/home-assistant/docs/functions.nix index 1bec256e..93b90aa3 100644 --- a/devices/homie/modules/home-assistant/docs/functions.nix +++ b/devices/homie/modules/home-assistant/docs/functions.nix @@ -1,8 +1,5 @@ # I use nix2yaml from ../default.nix to convert this to YAML and place it in the functions of extended_ollama_conversation -let - inherit (import ../../../../../lib {}) lib; - inherit (lib) concatStrings concatStringsSep splitString; -in [ +[ { spec = { name = "get_attributes"; @@ -26,283 +23,4 @@ in [ value_template = "{{ states[entity_id] }}"; }; } - - { - spec = { - name = "timer_start"; - description = "Use this function to start a timer in Home Assistant whose ID defaults to 1."; - - parameters = { - type = "object"; - - properties = { - hours = { - type = "string"; - description = "The amount of hours the timer should run for."; - }; - - minutes = { - type = "string"; - description = "The amount of minutes the timer should run for."; - }; - - seconds = { - type = "string"; - description = "The amount of seconds the timer should run for."; - }; - }; - - required = []; - }; - }; - - function = { - type = "script"; - - sequence = [ - { - service = "script.assist_timerstart"; - - # dummy ID that won't be used by the script - target.entity_id = "timer.assist_timer1"; - - data = { - duration = concatStrings [ - ''{% if not hours %} {% set hours = "0" %} {% endif %}'' - ''{% if not minutes %} {% set minutes = "0" %} {% endif %}'' - ''{% if not seconds %} {% set seconds = "0" %} {% endif %}'' - - ''{{ hours | int(default=0) }}:{{ minutes | int(default=0) }}:{{ seconds | int(default=0) }}'' - ]; - }; - } - ]; - }; - } - - { - spec = { - name = "timer_stop"; - description = "Use this function to stop a timer in Home Assistant."; - - parameters = { - type = "object"; - - properties = { - timer_number = { - type = "string"; - description = "The number of the timer"; - enum = ["1" "2" "3"]; - }; - }; - - required = ["timer_number"]; - }; - }; - - function = { - type = "script"; - - sequence = [ - { - service = "script.assist_timerstop"; - target.entity_id = ''{{ "timer.assist_timer" ~ timer_number }}''; - } - ]; - }; - } - - { - spec = { - name = "timer_pause"; - description = "Use this function to pause a timer in Home Assistant."; - - parameters = { - type = "object"; - - properties = { - timer_number = { - type = "string"; - description = "The number of the timer"; - enum = ["1" "2" "3"]; - }; - }; - - required = ["timer_number"]; - }; - }; - - function = { - type = "script"; - - sequence = [ - { - service = "script.assist_timerpause"; - - target.entity_id = ''{{ "timer.assist_timer" ~ timer_number }}''; - - data = { - timer_action = "pause"; - }; - } - ]; - }; - } - - { - spec = { - name = "timer_unpause"; - description = "Use this function to unpause or resume a timer in Home Assistant."; - - parameters = { - type = "object"; - - properties = { - timer_number = { - type = "string"; - description = "The number of the timer"; - enum = ["1" "2" "3"]; - }; - }; - - required = ["timer_number"]; - }; - }; - - function = { - type = "script"; - - sequence = [ - { - service = "script.assist_timerpause"; - - target.entity_id = ''{{ "timer.assist_timer" ~ timer_number }}''; - - data = { - timer_action = "resume"; - }; - } - ]; - }; - } - - { - spec = { - name = "timer_duration"; - description = "Use this function to get the remaining duration of a timer in Home Assistant."; - - parameters = { - type = "object"; - - properties = { - timer_number = { - type = "string"; - description = "The number of the timer"; - enum = ["1" "2" "3"]; - }; - }; - - required = ["timer_number"]; - }; - }; - - function = { - type = "template"; - - value_template = concatStringsSep " " (splitString "\n" '' - {%- set entity_id = "timer.assist_timer" ~ timer_number %} - - {%- set timer_amount = states.timer - | selectattr("state","eq","active") - | selectattr("entity_id","match","timer.assist_timer*") - | map(attribute="entity_id") - | list - | length -%} - - {% if timer_amount == 0 %} - There are no timers active. - - {% else %} - {%- if entity_id != "all" and entity_id != "null" %} - {%- set active_timers = states.timer - | selectattr("state","eq","active") - | selectattr("entity_id","match",entity_id) - | list -%} - - {%- else%} - {%- set active_timers = states.timer - | selectattr("state","eq","active") - | selectattr("entity_id","match","timer.assist_timer*") - | list -%} - {%- endif %} - - {% if active_timers|length == 0 %} - {%- if entity_id != "all" and entity_id != "null" %} - This timer is not active. - - {%- else %} - There are no timers active. - {%- endif %} - - {% elif active_timers | length > 1 %} - There are {{active_timers|length }} timers active. - {% endif %} - - {% for timer in active_timers %} - {% set timer_id = timer.entity_id %} - {% set timer_finishes_at = state_attr(timer_id, "finishes_at") %} - {% set time_remaining = as_datetime(timer_finishes_at) - now() %} - {% set hours_remaining = time_remaining.total_seconds() // 3600 %} - {% set minutes_remaining = (time_remaining.total_seconds() % 3600) // 60 %} - {% set seconds_remaining = time_remaining.total_seconds() % 60 %} - - {% if timer.state == "active" or timer.state == "paused" %} - {% if entity_id != timer_id %} - {{ state_attr(timer_id, "friendly_name")[9:] }} {% if timer.state == "paused" %} is paused and {% endif %} has - - {% else %} - There are - {% endif %} - - {% if hours_remaining > 0 %}{{ hours_remaining | round }} hours {% endif %} - {% if minutes_remaining == 1 %}1 minute {% endif %} - {% if minutes_remaining > 1 %}{{ minutes_remaining | round }} minutes {% endif %} - {% if seconds_remaining == 1 and hours_remaining == 0%}1 seconde {% endif %} - {% if seconds_remaining > 1 and hours_remaining == 0 %}{{ seconds_remaining | round }} seconds {% endif %}remaining. - {% endif %} - {% endfor %} - {% endif %} - ''); - }; - } - - { - spec = { - name = "play_artist"; - description = "Use this function to play music from an artist"; - - parameters = { - type = "object"; - - properties.query = { - type = "string"; - description = "The query"; - }; - - required = ["query"]; - }; - }; - - function = { - type = "script"; - sequence = [ - { - service = "netdaemon.spotify_play_artist"; - data = { - criteria = "{{ query }}"; - }; - } - ]; - }; - } ] diff --git a/devices/homie/modules/home-assistant/docs/prompt b/devices/homie/modules/home-assistant/docs/prompt index 3444af23..95f43f9c 100644 --- a/devices/homie/modules/home-assistant/docs/prompt +++ b/devices/homie/modules/home-assistant/docs/prompt @@ -48,5 +48,4 @@ entity_id,name,state,aliases,attributes ``` The current state of devices is provided in available devices. -Do not ask for confirmation to execute a service. Do not restate or appreciate what user says, rather make a quick inquiry. diff --git a/devices/homie/modules/home-assistant/frontend.nix b/devices/homie/modules/home-assistant/frontend.nix index 3a74529e..4ec95279 100644 --- a/devices/homie/modules/home-assistant/frontend.nix +++ b/devices/homie/modules/home-assistant/frontend.nix @@ -77,6 +77,14 @@ in { "switch.smartplug3" ]; } + { + type = "entities"; + entities = [ + "timer.assist_timer1" + "timer.assist_timer2" + "timer.assist_timer3" + ]; + } ]; } ]; diff --git a/devices/homie/modules/home-assistant/spotify-sentences.nix b/devices/homie/modules/home-assistant/spotify-sentences.nix new file mode 100644 index 00000000..ba773424 --- /dev/null +++ b/devices/homie/modules/home-assistant/spotify-sentences.nix @@ -0,0 +1,23 @@ +{ + language = "en"; + + intents = { + PlayArtist.data = [ + { + sentences = [ + "play [some] music from [artist] {artist}" + ]; + } + ]; + }; + + lists = { + artist.wildcard = true; + }; + + responses.intents = { + PlayArtist.default = '' + Searching for {{ slots.artist }} on Spotify and playing their top songs. + ''; + }; +} diff --git a/devices/homie/modules/home-assistant/spotify.nix b/devices/homie/modules/home-assistant/spotify.nix index 60d9d091..1ab4e699 100644 --- a/devices/homie/modules/home-assistant/spotify.nix +++ b/devices/homie/modules/home-assistant/spotify.nix @@ -6,26 +6,23 @@ ... }: let inherit (lib) getExe; + inherit (pkgs.writers) writeYAML; in { systemd.services.home-assistant.preStart = let WorkingDirectory = "/var/lib/hass"; creds = config.sops.secrets.spotifyd.path; + spotify = writeYAML "assist_spotify.yaml" (import ./spotify-sentences.nix); in getExe (pkgs.writeShellApplication { - name = "spotify-plus-creds"; + name = "spotify-files"; text = '' + mkdir -p ${WorkingDirectory}/custom_sentences/en + cp -f ${spotify} ${WorkingDirectory}/custom_sentences/en cp -f ${creds} ${WorkingDirectory}/.storage/SpotifyWebApiPython_librespot_credentials.json ''; }); services.home-assistant = { - # Needed for spotifyplus - package = pkgs.home-assistant.override { - packageOverrides = _: super: { - inherit (self.packages.${pkgs.system}) urllib3; - }; - }; - customComponents = builtins.attrValues { inherit (self.legacyPackages.${pkgs.system}.hass-components) @@ -36,5 +33,17 @@ in { extraComponents = [ "spotify" ]; + + config.intent_script = { + PlayArtist = { + async_action = "false"; + action = [ + { + service = "netdaemon.spotify_play_artist"; + data.criteria = "{{ artist }}"; + } + ]; + }; + }; }; } diff --git a/devices/homie/modules/home-assistant/timer-sentences.nix b/devices/homie/modules/home-assistant/timer-sentences.nix new file mode 100644 index 00000000..44edae79 --- /dev/null +++ b/devices/homie/modules/home-assistant/timer-sentences.nix @@ -0,0 +1,942 @@ +{ + language = "en"; + + intents = { + TimerDuration.data = [ + { + sentences = [ + "how [much] long[er] on [the] {entity_id} timer" + "how much time is left on [the] {entity_id} timer" + "how long until [the] {entity_id} timer (is finished|finishes)" + ]; + } + { + sentences = [ + "how [much] long[er] on [the] timer[s]" + "how much time is left on [the] {entity_id} timer[s]" + "how long until [the] timer[s] (is finished|finishes)" + ]; + slots.entity_id = "null"; + } + ]; + + TimerPause.data = [ + { + sentences = ["(pause|interrupt) [the] {entity_id} timer[s]"]; + slots.timer_action = "pause"; + } + { + sentences = ["(pause|interrupt) [the] timer[s]"]; + slots = { + entity_id = "null"; + timer_action = "pause"; + }; + } + { + sentences = ["(pause|interrupt) all timer[s]"]; + slots = { + entity_id = "all"; + timer_action = "pause"; + }; + } + { + sentences = ["(resume|continue) [the] {entity_id} timer[s]"]; + slots.timer_action = "resume"; + } + { + sentences = ["(resume|continue) [the] timer[s]"]; + slots = { + entity_id = "null"; + timer_action = "resume"; + }; + } + { + sentences = ["(resume|continue) all timer[s]"]; + slots = { + entity_id = "all"; + timer_action = "resume"; + }; + } + ]; + + TimerStart.data = [ + { + sentences = [ + "(start|set) [a] timer (for|with) {hours} hour[s] [and] {minutes} minute[s] [and] {seconds} seconde[s]" + ]; + } + + { + sentences = [ + "(start|set) [(a|an)] timer (for|with) {hours} hour[s]" + "(start|set) [(a|an)] {hours} hour[s] timer" + ]; + slots = { + minutes = 0; + seconds = 0; + }; + } + + { + sentences = [ + "(start|set) [a] timer (for|with) {minutes} minute[s]" + "(start|set) [(a|an)] {minutes} minute[s] timer" + ]; + slots = { + hours = 0; + seconds = 0; + }; + } + + { + sentences = [ + "(start|set) [a] timer (for|with) {seconds} seconde[s]" + "(start|set) [(a|an)] {seconds} second[s] timer" + ]; + slots = { + hours = 0; + minutes = 0; + }; + } + + { + sentences = [ + "(start|set) [a] timer (for|with) {minutes} minute[s] [and] {seconds} seconde[s]" + ]; + slots.hours = 0; + } + + { + sentences = [ + "(start|set) [a] timer (for|with) {hours} hour[s] [and] {minutes} minute[s]" + ]; + slots.seconds = 0; + } + ]; + + TimerStop.data = [ + {sentences = ["(stop|cancel|turn off) [the] {entity_id} timer[s]"];} + { + sentences = ["(stop|cancel|turn off) [the] timer[s]"]; + slots.entity_id = "null"; + } + ]; + }; + + lists = { + entity_id.values = [ + { + "in" = "(first|one|1)"; + out = "timer.assist_timer1"; + } + { + "in" = "(second|two|2)"; + out = "timer.assist_timer2"; + } + { + "in" = "(third|three|3)"; + out = "timer.assist_timer3"; + } + { + "in" = "(all|every)"; + out = "all"; + } + ]; + + hours.values = [ + { + "in" = "(one|1)"; + out = 1; + } + { + "in" = "(two|2)"; + out = 2; + } + { + "in" = "(three|3)"; + out = 3; + } + { + "in" = "(four|4)"; + out = 4; + } + { + "in" = "(five|5)"; + out = 5; + } + { + "in" = "(six|6)"; + out = 6; + } + { + "in" = "(seven|7)"; + out = 7; + } + { + "in" = "(eight|8)"; + out = 8; + } + { + "in" = "(nine|9)"; + out = 9; + } + { + "in" = "(ten|10)"; + out = 10; + } + { + "in" = "(eleven|11)"; + out = 11; + } + { + "in" = "(twelve|12)"; + out = 12; + } + { + "in" = "(thirteen|13)"; + out = 13; + } + { + "in" = "(fourteen|14)"; + out = 14; + } + { + "in" = "(fifteen|15)"; + out = 15; + } + { + "in" = "(sixteen|16)"; + out = 16; + } + { + "in" = "(seventeen|17)"; + out = 17; + } + { + "in" = "(eighteen|18)"; + out = 18; + } + { + "in" = "(nineteen|19)"; + out = 19; + } + { + "in" = "(twenty|20)"; + out = 20; + } + { + "in" = "(twenty-one|21)"; + out = 21; + } + { + "in" = "(twenty-two|22)"; + out = 22; + } + { + "in" = "(twenty-three|23)"; + out = 23; + } + { + "in" = "(twenty-four|24)"; + out = 24; + } + ]; + + minutes.values = [ + { + "in" = "(one|1)"; + out = 1; + } + { + "in" = "(two|2)"; + out = 2; + } + { + "in" = "(three|3)"; + out = 3; + } + { + "in" = "(four|4)"; + out = 4; + } + { + "in" = "(five|5)"; + out = 5; + } + { + "in" = "(six|6)"; + out = 6; + } + { + "in" = "(seven|7)"; + out = 7; + } + { + "in" = "(eight|8)"; + out = 8; + } + { + "in" = "(nine|9)"; + out = 9; + } + { + "in" = "(ten|10)"; + out = 10; + } + { + "in" = "(eleven|11)"; + out = 11; + } + { + "in" = "(twelve|12)"; + out = 12; + } + { + "in" = "(thirteen|13)"; + out = 13; + } + { + "in" = "(fourteen|14)"; + out = 14; + } + { + "in" = "(fifteen|15)"; + out = 15; + } + { + "in" = "(sixteen|16)"; + out = 16; + } + { + "in" = "(seventeen|17)"; + out = 17; + } + { + "in" = "(eighteen|18)"; + out = 18; + } + { + "in" = "(nineteen|19)"; + out = 19; + } + { + "in" = "(twenty|20)"; + out = 20; + } + { + "in" = "(twenty-one|21)"; + out = 21; + } + { + "in" = "(twenty-two|22)"; + out = 22; + } + { + "in" = "(twenty-three|23)"; + out = 23; + } + { + "in" = "(twenty-four|24)"; + out = 24; + } + { + "in" = "(twenty-five|25)"; + out = 25; + } + { + "in" = "(twenty-six|26)"; + out = 26; + } + { + "in" = "(twenty-seven|27)"; + out = 27; + } + { + "in" = "(twenty-eight|28)"; + out = 28; + } + { + "in" = "(twenty-nine|29)"; + out = 29; + } + { + "in" = "(thirty|30)"; + out = 30; + } + { + "in" = "(thirty-one|31)"; + out = 31; + } + { + "in" = "(thirty-two|32)"; + out = 32; + } + { + "in" = "(thirty-three|33)"; + out = 33; + } + { + "in" = "(thirty-four|34)"; + out = 34; + } + { + "in" = "(thirty-five|35)"; + out = 35; + } + { + "in" = "(thirty-six|36)"; + out = 36; + } + { + "in" = "(thirty-seven|37)"; + out = 37; + } + { + "in" = "(thirty-eight|38)"; + out = 38; + } + { + "in" = "(thirty-nine|39)"; + out = 39; + } + { + "in" = "(forty|40)"; + out = 40; + } + { + "in" = "(forty-one|41)"; + out = 41; + } + { + "in" = "(forty-two|42)"; + out = 42; + } + { + "in" = "(forty-three|43)"; + out = 43; + } + { + "in" = "(forty-four|44)"; + out = 44; + } + { + "in" = "(forty-five|45)"; + out = 45; + } + { + "in" = "(forty-six|46)"; + out = 46; + } + { + "in" = "(forty-seven|47)"; + out = 47; + } + { + "in" = "(forty-eight|48)"; + out = 48; + } + { + "in" = "(forty-nine|49)"; + out = 49; + } + { + "in" = "(fifty|50)"; + out = 50; + } + { + "in" = "(fifty-one|51)"; + out = 51; + } + { + "in" = "(fifty-two|52)"; + out = 52; + } + { + "in" = "(fifty-three|53)"; + out = 53; + } + { + "in" = "(fifty-four|54)"; + out = 54; + } + { + "in" = "(fifty-five|55)"; + out = 55; + } + { + "in" = "(fifty-six|56)"; + out = 56; + } + { + "in" = "(fifty-seven|57)"; + out = 57; + } + { + "in" = "(fifty-eight|58)"; + out = 58; + } + { + "in" = "(fifty-nine|59)"; + out = 59; + } + { + "in" = "(sixty|60)"; + out = 60; + } + ]; + + seconds.values = [ + { + "in" = "(one|1)"; + out = 1; + } + { + "in" = "(two|2)"; + out = 2; + } + { + "in" = "(three|3)"; + out = 3; + } + { + "in" = "(four|4)"; + out = 4; + } + { + "in" = "(five|5)"; + out = 5; + } + { + "in" = "(six|6)"; + out = 6; + } + { + "in" = "(seven|7)"; + out = 7; + } + { + "in" = "(eight|8)"; + out = 8; + } + { + "in" = "(nine|9)"; + out = 9; + } + { + "in" = "(ten|10)"; + out = 10; + } + { + "in" = "(eleven|11)"; + out = 11; + } + { + "in" = "(twelve|12)"; + out = 12; + } + { + "in" = "(thirteen|13)"; + out = 13; + } + { + "in" = "(fourteen|14)"; + out = 14; + } + { + "in" = "(fifteen|15)"; + out = 15; + } + { + "in" = "(sixteen|16)"; + out = 16; + } + { + "in" = "(seventeen|17)"; + out = 17; + } + { + "in" = "(eighteen|18)"; + out = 18; + } + { + "in" = "(nineteen|19)"; + out = 19; + } + { + "in" = "(twenty|20)"; + out = 20; + } + { + "in" = "(twenty-one|21)"; + out = 21; + } + { + "in" = "(twenty-two|22)"; + out = 22; + } + { + "in" = "(twenty-three|23)"; + out = 23; + } + { + "in" = "(twenty-four|24)"; + out = 24; + } + { + "in" = "(twenty-five|25)"; + out = 25; + } + { + "in" = "(twenty-six|26)"; + out = 26; + } + { + "in" = "(twenty-seven|27)"; + out = 27; + } + { + "in" = "(twenty-eight|28)"; + out = 28; + } + { + "in" = "(twenty-nine|29)"; + out = 29; + } + { + "in" = "(thirty|30)"; + out = 30; + } + { + "in" = "(thirty-one|31)"; + out = 31; + } + { + "in" = "(thirty-two|32)"; + out = 32; + } + { + "in" = "(thirty-three|33)"; + out = 33; + } + { + "in" = "(thirty-four|34)"; + out = 34; + } + { + "in" = "(thirty-five|35)"; + out = 35; + } + { + "in" = "(thirty-six|36)"; + out = 36; + } + { + "in" = "(thirty-seven|37)"; + out = 37; + } + { + "in" = "(thirty-eight|38)"; + out = 38; + } + { + "in" = "(thirty-nine|39)"; + out = 39; + } + { + "in" = "(forty|40)"; + out = 40; + } + { + "in" = "(forty-one|41)"; + out = 41; + } + { + "in" = "(forty-two|42)"; + out = 42; + } + { + "in" = "(forty-three|43)"; + out = 43; + } + { + "in" = "(forty-four|44)"; + out = 44; + } + { + "in" = "(forty-five|45)"; + out = 45; + } + { + "in" = "(forty-six|46)"; + out = 46; + } + { + "in" = "(forty-seven|47)"; + out = 47; + } + { + "in" = "(forty-eight|48)"; + out = 48; + } + { + "in" = "(forty-nine|49)"; + out = 49; + } + { + "in" = "(fifty|50)"; + out = 50; + } + { + "in" = "(fifty-one|51)"; + out = 51; + } + { + "in" = "(fifty-two|52)"; + out = 52; + } + { + "in" = "(fifty-three|53)"; + out = 53; + } + { + "in" = "(fifty-four|54)"; + out = 54; + } + { + "in" = "(fifty-five|55)"; + out = 55; + } + { + "in" = "(fifty-six|56)"; + out = 56; + } + { + "in" = "(fifty-seven|57)"; + out = 57; + } + { + "in" = "(fifty-eight|58)"; + out = 58; + } + { + "in" = "(fifty-nine|59)"; + out = 59; + } + { + "in" = "(sixty|60)"; + out = 60; + } + ]; + }; + + responses.intents = { + TimerDuration.default = '' + {%- set timer_amount = states.timer + | selectattr('state','eq','active') + | selectattr('entity_id','match','timer.assist_timer*') + | map(attribute='entity_id') + | list + | length -%} + + {% if timer_amount == 0 %} + There are no timers active. + {% else %} + {%- if slots.entity_id != 'all' and slots.entity_id != 'null' %} + {%- set active_timers = states.timer + | selectattr('state','eq','active') + | selectattr('entity_id','match',slots.entity_id) + | list -%} + {%- else%} + {%- set active_timers = states.timer + | selectattr('state','eq','active') + | selectattr('entity_id','match','timer.assist_timer*') + | list -%} + {%- endif %} + + {% if active_timers|length == 0 %} + {%- if slots.entity_id != 'all' and slots.entity_id != 'null' %} + This timer is not active. + {%- else %} + There are no timers active. + {%- endif %} + {% elif active_timers|length > 1 %} + There are {{active_timers|length }} timers active. + {% endif %} + + {% for timer in active_timers %} + {% set timer_id = timer.entity_id %} + {% set timer_finishes_at = state_attr(timer_id, 'finishes_at') %} + + {% set time_remaining = as_datetime(timer_finishes_at) - now() %} + {% set hours_remaining = time_remaining.total_seconds() // 3600 %} + {% set minutes_remaining = (time_remaining.total_seconds() % 3600) // 60 %} + {% set seconds_remaining = time_remaining.total_seconds() % 60 %} + + {% if timer.state == "active" or timer.state == "paused" %} + {% if slots.entity_id != timer_id %} + {{ state_attr(timer_id, 'friendly_name')[9:] }} + + {% if timer.state == "paused" %} + is paused and + {% endif %} + has + {% else %} + There are + {% endif %} + + {% if hours_remaining > 0 %} + {{ hours_remaining | round }} hours + {% endif %} + + {% if minutes_remaining == 1 %} + 1 minute + {% endif %} + + {% if minutes_remaining > 1 %} + {{ minutes_remaining | round }} minutes + {% endif %} + + {% if seconds_remaining == 1 and hours_remaining == 0%} + 1 seconde + {% endif %} + + {% if seconds_remaining > 1 and hours_remaining == 0 %} + {{ seconds_remaining | round }} seconds + {% endif %} + remaining. + {% endif %} + {% endfor %} + {% endif %} + ''; + + TimerPause.default = '' + {%- if slots.timer_action is set or slots.timer_action != "" -%} + {%- set timer_action = slots.timer_action -%} + {%- else -%} + {%- set timer_action = "resume" -%} + {%- endif -%} + + {%- set timer_amount = states.timer + | selectattr('state','eq','active') + | selectattr('entity_id','match','timer.assist_timer*') + | map(attribute='entity_id') + | list + | length -%} + + {% if timer_amount == 0 %} + There are no timers active. + + {% elif timer_amount > 1 and slots.entity_id == 'null' %} + There are multiple timers active. + {{ (["Please specify which timer you mean.", "Please specify which timer.", "Specify which timer you mean.", ""] | random) }} + + {% elif slots.entity_id == 'all' %} + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) }}. + All timers + {% if timer_action == "pause" %} + paused + {% else %} + resumed + {% endif %} + . + + {% elif (as_timestamp(now()) - as_timestamp(states.timer.assist_timer1.last_changed) < 3 and states('timer.assist_timer1') == 'idle') or + (as_timestamp(now()) - as_timestamp(states.timer.assist_timer2.last_changed) < 3 and states('timer.assist_timer2') == 'idle') or + (as_timestamp(now()) - as_timestamp(states.timer.assist_timer3.last_changed) < 3 and states('timer.assist_timer3') == 'idle') %} + Timer + {% if timer_action == "pause" %} + paused + {% else %} + resumed + {% endif %} + . + + {% elif (timer_amount == 1 and slots.entity_id == 'null') or + (slots.entity_id == 'timer.assist_timer1' and states('timer.assist_timer1') != 'idle') or + (slots.entity_id == 'timer.assist_timer2' and states('timer.assist_timer2') != 'idle') or + (slots.entity_id == 'timer.assist_timer3' and states('timer.assist_timer3') != 'idle') %} + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) }}Timer + {% if timer_action == "pause" %} + paused + {% else %} + resumed + {% endif %} + . + {% else %} + + This timer is not active. + {% endif %} + ''; + + TimerStart.default = '' + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) + + (["I will start a timer for ", "Timer started with ", "Starting timer with ", "Timer active for "] | random)}} + + {% if (slots.hours | int(default=0)) == 1 %} + 1 hour + {% elif (slots.hours | int(default=0)) > 1 %} + {{ (slots.hours | int)}} hours + {% endif %} + + {% if (slots.hours | int(default=0)) > 0 and ((slots.minutes | int(default=0)) > 0 or (slots.seconds | int(default=0)) > 0) %} + and + {% endif %} + + {% if (slots.minutes | int(default=0)) == 1 %} + 1 minute + {% elif (slots.minutes | int(default=0)) > 1 %} + {{ (slots.minutes | int)}} minutes + {% endif %} + + {% if (slots.minutes | int(default=0)) > 0 and (slots.seconds | int(default=0)) > 0 %} + and + {% endif %} + + {% if (slots.seconds | int(default=0)) == 1 %} + 1 second + {% elif (slots.seconds | int(default=0)) > 1 %} + {{ (slots.seconds | int)}} secondes + {% endif %}. + ''; + + TimerStop.default = '' + {%- set timer_amount = states.timer + | selectattr('state','eq','active') + | selectattr('entity_id','match','timer.assist_timer*') + | map(attribute='entity_id') + | list + | length -%} + + {% set mediaplayer = namespace(entity=[]) %} + + {% for player in states.media_player %} + {%- if ((state_attr(player.entity_id, 'media_content_id') | lower != 'none' + and state_attr(player.entity_id, 'media_content_id')[:47][38:] == 'Timer.mp3') + or state_attr(player.entity_id, 'media_title') | lower == 'timer') + and states(player.entity_id) == 'playing' -%} + {%- set mediaplayer.entity = player.entity_id -%} + {% endif -%} + {% endfor %} + + {% if mediaplayer.entity[:12] == 'media_player' %} + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) }}Timer stopped. + {% elif timer_amount == 0 and + (as_timestamp(now()) - as_timestamp(states.timer.assist_timer1.last_changed) > 3 and states('timer.assist_timer1') == 'idle') and + (as_timestamp(now()) - as_timestamp(states.timer.assist_timer2.last_changed) > 3 and states('timer.assist_timer2') == 'idle') and + (as_timestamp(now()) - as_timestamp(states.timer.assist_timer3.last_changed) > 3 and states('timer.assist_timer3') == 'idle') %} + There are no timers active. + {% elif (slots_entity_id == 'timer.assist_timer1' and states('timer.assist_timer1') == 'idle') or + (slots_entity_id == 'timer.assist_timer2' and states('timer.assist_timer2') == 'idle') or + (slots_entity_id == 'timer.assist_timer3' and states('timer.assist_timer3') == 'idle') %} + This timer is not active. + {% elif timer_amount > 1 and slots_entity_id == 'null' %} + There are multiple timers active. + {{ (["Please specify which timer you mean.", "Please specify which timer.", "Specify which timer you mean.", ""] | random) }} + {% elif slots_entity_id == 'all' %} + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) }}All timers stopped. + {% else %} + {{ (["Understood. ", "Okay. ", "Of course. ", ""] | random) }}Timer stopped. + {% endif %} + ''; + }; +} diff --git a/devices/homie/modules/home-assistant/timer.nix b/devices/homie/modules/home-assistant/timer.nix index a30b51c8..5f718058 100644 --- a/devices/homie/modules/home-assistant/timer.nix +++ b/devices/homie/modules/home-assistant/timer.nix @@ -1,6 +1,11 @@ # From https://github.com/don86nl/ha_intents/blob/main/config/packages/assist_timers.yaml -{lib, ...}: let - inherit (lib) concatStrings concatStringsSep; +{ + lib, + pkgs, + ... +}: let + inherit (lib) concatStrings concatStringsSep getExe; + inherit (pkgs.writers) writeYAML; mkTimer = id: { "assist_timer${toString id}" = { @@ -38,6 +43,19 @@ timer_media_location = "/path/to/file.mp3"; }; in { + systemd.services.home-assistant.preStart = let + WorkingDirectory = "/var/lib/hass"; + + timer = writeYAML "assist_timers.yaml" (import ./timer-sentences.nix); + in + getExe (pkgs.writeShellApplication { + name = "timer-files"; + text = '' + mkdir -p ${WorkingDirectory}/custom_sentences/en + cp -f ${timer} ${WorkingDirectory}/custom_sentences/en + ''; + }); + services.home-assistant = { config = { homeassistant.customize."script.assist_timerstart" = {inherit settings;}; @@ -48,6 +66,43 @@ in { # Makes location of a timer customizable from the UI input_text = (mkLocation 1) // (mkLocation 2) // (mkLocation 3); + intent_script = { + TimerStart = { + async_action = "false"; + action = [ + { + service = "script.assist_timerstart"; + data.duration = "{{hours | int(default=0)}}:{{ minutes | int(default=0) }}:{{ seconds | int(default=0) }}"; + } + ]; + }; + TimerStop = { + async_action = true; + action = [ + { + service = "script.assist_timerstop"; + data.entity_id = "{{ entity_id }}"; + } + ]; + }; + TimerPause = { + async_action = true; + action = [ + { + service = "script.assist_timerpause"; + data = { + entity_id = "{{ entity_id }}"; + timer_action = "{{ timer_action }}"; + }; + } + ]; + }; + TimerDuration = { + async_action = true; + action = [{stop = "";}]; + }; + }; + # Automate some logic automation = [ { @@ -133,14 +188,6 @@ in { }; } - { - alias = "Store current device volume"; - - variables = { - device_volume = ''{{ state_attr(timer_target, 'volume_level') }}''; - }; - } - { alias = "Media file or TTS"; @@ -229,17 +276,6 @@ in { } ]; } - - { - alias = "Restore device previous volume"; - service = "media_player.volume_set"; - - target.entity_id = ''{{ timer_target }}''; - - data = { - volume_level = ''{{ device_volume }}''; - }; - } ]; } diff --git a/flake.lock b/flake.lock index 5531408a2c96fd6a6c0f0a9580cf9f9f5d89611b..009719cfcc0b93b607574d0b1072adf7865e8633 100644 GIT binary patch delta 436 zcmZvYPfG$p0L2N(g3e}yVo6dc9%g2D+zGrGg_Wk#AFE5v&d%zl&F1d<_mt)MQ7;?6olx(3XwqoWeDgdp$Gv$h^A7_1S!*?nxdyP1~Dh{gXU%;R%ALITcB5#@VJoc zt9d_25fu=5KZwN5<3cfedV}1j3WE$m47n;tC_g^~QHsIggVJuq kr^Zh|n{*u(-Cm1UC%(LX35UiVJa>}#-|4x-(Q6C*0z_<;@Bjb+ delta 19 bcmeyojrsXH<_$e2n^i2z3ODnuN;L!kU^NJ= diff --git a/flake.nix b/flake.nix index 74232a60a6a31bd4268e79e1c0b300c81fabe9c2..aae9c3773b384c7b083a132094709042bb2d0132 100644 GIT binary patch delta 128 zcmdlKb}V8;D35reZdzhaPEulWwr+BMURi2Uabihke%@q5HeH2WQv)SyE(I_sN-fA& zuvJjXK+-T-oZXNkBQZWRuOu~ZvOb%>VoH9Vg;`z>7F`fUC6h0RscjD7xhDqza+)Z@ delta 12 TcmX>Wu_