diff --git a/modules/nixos/homeassistant/automations/voice-routines/default.nix b/modules/nixos/homeassistant/automations/voice-routines/default.nix new file mode 100644 index 0000000..019bdb0 --- /dev/null +++ b/modules/nixos/homeassistant/automations/voice-routines/default.nix @@ -0,0 +1,657 @@ +{ + config, + lib, + namespace, + ... +}: +with lib; +let + inherit (lib.${namespace}) mkBoolOpt; + cfg = config.${namespace}.services.home-assistant.automation.voice-routines; + + # --------------------------------------------------------------------------- + # BEDTIME ROUTINE + # Triggers: "goodnight", "bedtime", "I'm going to sleep" + # Actions: dim bedroom lights, set fan to sleep mode, + # turn off other lights, TTS confirm on Voice satellite, + # then turn off bedroom lights after 30s + # --------------------------------------------------------------------------- + bedtimeAutomation = '' + - id: 'voice_bedtime_routine' + alias: Voice - Bedtime Routine + description: 'Triggered by voice: dim lights, set fan to sleep mode, turn off living room' + triggers: + - trigger: conversation + command: + - goodnight + - good night + - bedtime + - time for bed + - going to sleep + - heading to bed + conditions: [] + actions: + - parallel: + - action: light.turn_on + data: + brightness_pct: 10 + color_temp_kelvin: 2700 + transition: 5 + target: + entity_id: light.bedroom_lights + - action: light.turn_off + data: + transition: 3 + target: + entity_id: + - light.living_room_lights + - light.front_closet_lights + - action: fan.set_preset_mode + data: + preset_mode: sleep + target: + entity_id: fan.bedroom_fan + - action: fan.turn_on + target: + entity_id: fan.bedroom_air_purifier + - delay: + seconds: 3 + - action: tts.speak + data: + cache: true + message: >- + Goodnight! Bedroom lights are dimmed, fan is in sleep mode, + and all other lights are off. Sleep well. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - delay: + seconds: 30 + - action: light.turn_off + data: + transition: 10 + target: + entity_id: light.bedroom_lights + mode: single + ''; + + # --------------------------------------------------------------------------- + # GOOD MORNING ROUTINE + # Triggers: "good morning", "wake up", "rise and shine" + # Actions: gradually brighten bedroom lights to warm white, announce + # weather + briefing via TTS on Voice satellite + # --------------------------------------------------------------------------- + morningAutomation = '' + - id: 'voice_morning_routine' + alias: Voice - Good Morning Routine + description: 'Triggered by voice: brighten lights gradually and announce morning briefing' + triggers: + - trigger: conversation + command: + - good morning + - good morning home + - wake up + - rise and shine + - morning briefing + - morning report + conditions: [] + actions: + - action: light.turn_on + data: + brightness_pct: 40 + color_temp_kelvin: 3000 + transition: 10 + target: + entity_id: light.bedroom_lights + - delay: + seconds: 5 + - action: light.turn_on + data: + brightness_pct: 80 + color_temp_kelvin: 4000 + transition: 15 + target: + entity_id: light.bedroom_lights + - action: tts.speak + data: + cache: false + message: >- + Good morning! Today's weather is {{ states('weather.forecast_home') }}, + with a temperature of {{ state_attr('weather.forecast_home', 'temperature') }} + degrees. + {% set lights_on = [ + ('Living Room', states('light.living_room_lights')), + ('Bedroom', states('light.bedroom_lights')), + ('Front Closet', states('light.front_closet_lights')) + ] | selectattr(1, 'eq', 'on') | map(attribute=0) | list %} + {% if lights_on | length > 0 %} + {{ lights_on | length }} lights left on. + {% endif %} + {% if states('vacuum.garbage_goober') == 'error' %} + Garbage Goober needs attention. + {% endif %} + Have a great day! + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: restart + ''; + + # --------------------------------------------------------------------------- + # HOME STATUS SUMMARY + # Triggers: "home status", "what's on", "status report" + # --------------------------------------------------------------------------- + homeStatusAutomation = '' + - id: 'voice_home_status' + alias: Voice - Home Status Summary + description: 'Triggered by voice: announce active devices and any alerts' + triggers: + - trigger: conversation + command: + - home status + - status report + - house status + - what is on + - what is running + conditions: [] + actions: + - action: tts.speak + data: + cache: false + message: >- + {% set lights_on = [ + ('Living Room', states('light.living_room_lights')), + ('Bedroom', states('light.bedroom_lights')), + ('Front Closet', states('light.front_closet_lights')) + ] | selectattr(1, 'eq', 'on') | map(attribute=0) | list %} + {% if lights_on %} + Lights on: {{ lights_on | join(', ') }}. + {% else %} + All lights are off. + {% endif %} + Bedroom fan is {{ states('fan.bedroom_fan') }}, + preset {{ state_attr('fan.bedroom_fan', 'preset_mode') or 'unknown' }}. + Living room purifier is {{ states('fan.living_room_air_purifier') }}, + bedroom purifier is {{ states('fan.bedroom_air_purifier') }}. + Garbage Goober is {{ states('vacuum.garbage_goober') }}. + BedJet is {{ states('climate.bedjet_dbbf_c222') }}. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: restart + ''; + + # --------------------------------------------------------------------------- + # PET CARE - Manual feed Joey + # --------------------------------------------------------------------------- + feedJoeyAutomation = '' + - id: 'voice_feed_joey' + alias: Voice - Feed Joey + description: 'Triggered by voice: dispense food for Joey' + triggers: + - trigger: conversation + command: + - feed Joey + - give Joey food + - Joey is hungry + - Joey needs food + - dispense food for Joey + conditions: [] + actions: + - action: button.press + target: + entity_id: button.joey_smart_feeder_manual_feed + - action: tts.speak + data: + cache: true + message: Dispensing food for Joey now. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: single + ''; + + # --------------------------------------------------------------------------- + # PET CARE - Manual feed Luci + # --------------------------------------------------------------------------- + feedLuciAutomation = '' + - id: 'voice_feed_luci' + alias: Voice - Feed Luci + description: 'Triggered by voice: dispense food for Luci' + triggers: + - trigger: conversation + command: + - feed Luci + - give Luci food + - Luci is hungry + - Luci needs food + - dispense food for Luci + conditions: [] + actions: + - action: button.press + target: + entity_id: button.luci_smart_feeder_manual_feed + - action: tts.speak + data: + cache: true + message: Dispensing food for Luci now. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: single + ''; + + # --------------------------------------------------------------------------- + # PET CARE - Last fed status + # --------------------------------------------------------------------------- + petStatusAutomation = '' + - id: 'voice_pet_status' + alias: Voice - Pet Feeding Status + description: 'Triggered by voice: announce when pets were last fed' + triggers: + - trigger: conversation + command: + - when was Joey last fed + - when did Joey eat + - has Joey been fed + - when was Luci last fed + - when did Luci eat + - has Luci been fed + - pet feeding status + - when were the cats last fed + - have the cats been fed + conditions: [] + actions: + - action: tts.speak + data: + cache: false + message: >- + {% set joey_last = state_attr('automation.joey_s_scheduled_feeding', 'last_triggered') %} + {% set luci_last = state_attr('automation.luci_s_scheduled_feeding', 'last_triggered') %} + {% if joey_last %} + Joey was last fed {{ relative_time(joey_last) }} ago. + {% else %} + I do not have a record of Joey last feeding. + {% endif %} + {% if luci_last %} + Luci was last fed {{ relative_time(luci_last) }} ago. + {% else %} + I do not have a record of Luci last feeding. + {% endif %} + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: restart + ''; + + # --------------------------------------------------------------------------- + # AIR QUALITY - Night mode: quiet purifiers at night (22:00) + # --------------------------------------------------------------------------- + airQualityNightAutomation = '' + - id: 'air_quality_night_mode' + alias: Air Quality - Night Mode + description: 'At 10pm reduce air purifiers to low for quiet sleep' + triggers: + - trigger: time + at: "22:00:00" + conditions: + - condition: state + entity_id: fan.bedroom_air_purifier + state: "on" + actions: + - action: fan.set_percentage + data: + percentage: 20 + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + mode: single + ''; + + # --------------------------------------------------------------------------- + # AIR QUALITY - Day mode: restore purifiers in morning (08:00) + # --------------------------------------------------------------------------- + airQualityDayAutomation = '' + - id: 'air_quality_day_mode' + alias: Air Quality - Day Mode + description: 'At 8am restore air purifiers to normal operating speed' + triggers: + - trigger: time + at: "08:00:00" + conditions: + - condition: state + entity_id: fan.bedroom_air_purifier + state: "on" + actions: + - action: fan.set_percentage + data: + percentage: 60 + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + mode: single + ''; + + # --------------------------------------------------------------------------- + # AIR QUALITY - Voice control + # --------------------------------------------------------------------------- + airQualityVoiceAutomation = '' + - id: 'voice_air_quality' + alias: Voice - Air Quality Control + description: 'Voice commands to control air purifiers' + triggers: + - trigger: conversation + command: + - turn on air purifiers + - start air purifiers + - air purifiers on + - turn off air purifiers + - stop air purifiers + - air purifiers off + - air purifiers max + - set air purifiers to max + - air purifiers low + - set air purifiers to low + - quiet air purifiers + - air quality status + - how is the air quality + - what is the air quality + conditions: [] + actions: + - choose: + - conditions: + - condition: template + value_template: >- + {{ 'off' in trigger.sentence.lower() or 'stop' in trigger.sentence.lower() }} + sequence: + - action: fan.turn_off + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: tts.speak + data: + cache: true + message: Air purifiers turned off. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - conditions: + - condition: template + value_template: >- + {{ 'max' in trigger.sentence.lower() }} + sequence: + - action: fan.turn_on + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: fan.set_percentage + data: + percentage: 100 + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: tts.speak + data: + cache: true + message: Air purifiers set to maximum. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - conditions: + - condition: template + value_template: >- + {{ 'low' in trigger.sentence.lower() or 'quiet' in trigger.sentence.lower() }} + sequence: + - action: fan.turn_on + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: fan.set_percentage + data: + percentage: 20 + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: tts.speak + data: + cache: true + message: Air purifiers set to low. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - conditions: + - condition: template + value_template: >- + {{ 'status' in trigger.sentence.lower() or 'quality' in trigger.sentence.lower() }} + sequence: + - action: tts.speak + data: + cache: false + message: >- + Living room purifier is {{ states('fan.living_room_air_purifier') }} + at {{ state_attr('fan.living_room_air_purifier', 'percentage') | int(default=0) }} percent. + Bedroom purifier is {{ states('fan.bedroom_air_purifier') }} + at {{ state_attr('fan.bedroom_air_purifier', 'percentage') | int(default=0) }} percent. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + default: + - action: fan.turn_on + target: + entity_id: + - fan.bedroom_air_purifier + - fan.living_room_air_purifier + - action: tts.speak + data: + cache: true + message: Air purifiers are now on. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: restart + ''; + + # --------------------------------------------------------------------------- + # VACUUM - Voice control for Garbage Goober + # --------------------------------------------------------------------------- + vacuumVoiceAutomation = '' + - id: 'voice_vacuum_control' + alias: Voice - Vacuum Control + description: 'Voice commands to start, stop, dock, and check Garbage Goober' + triggers: + - trigger: conversation + command: + - start vacuuming + - start the vacuum + - run the vacuum + - vacuum the apartment + - send out Garbage Goober + - Garbage Goober go + - clean the apartment + - stop vacuuming + - stop the vacuum + - pause the vacuum + - send the vacuum home + - dock the vacuum + - Garbage Goober return + - vacuum status + - where is the vacuum + - how is Garbage Goober doing + - what is Garbage Goober doing + conditions: [] + actions: + - choose: + - conditions: + - condition: template + value_template: >- + {{ 'stop' in trigger.sentence.lower() or 'pause' in trigger.sentence.lower() }} + sequence: + - action: vacuum.stop + target: + entity_id: vacuum.garbage_goober + - action: tts.speak + data: + cache: true + message: Garbage Goober has been stopped. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - conditions: + - condition: template + value_template: >- + {{ 'dock' in trigger.sentence.lower() + or 'home' in trigger.sentence.lower() + or 'return' in trigger.sentence.lower() }} + sequence: + - action: vacuum.return_to_base + target: + entity_id: vacuum.garbage_goober + - action: tts.speak + data: + cache: true + message: Sending Garbage Goober back to its dock. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + - conditions: + - condition: template + value_template: >- + {{ 'status' in trigger.sentence.lower() + or 'where' in trigger.sentence.lower() + or 'how' in trigger.sentence.lower() + or 'what' in trigger.sentence.lower() }} + sequence: + - action: tts.speak + data: + cache: false + message: >- + Garbage Goober is currently {{ states('vacuum.garbage_goober') }}. + {% set battery = state_attr('vacuum.garbage_goober', 'battery_level') %} + {% if battery is not none %} + Battery is at {{ battery }} percent. + {% endif %} + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + default: + - condition: state + entity_id: vacuum.garbage_goober + state: docked + - action: vacuum.start + target: + entity_id: vacuum.garbage_goober + - action: tts.speak + data: + cache: true + message: Garbage Goober is starting to clean. + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: single + ''; + + # --------------------------------------------------------------------------- + # PRESENCE - Away mode when Matt leaves home + # --------------------------------------------------------------------------- + awayModeAutomation = '' + - id: 'presence_away_mode' + alias: Presence - Away Mode + description: 'When Matt leaves: turn off all lights, dock vacuum if running' + triggers: + - trigger: state + entity_id: person.matt_jallen + from: home + to: not_home + for: + minutes: 5 + conditions: [] + actions: + - action: light.turn_off + target: + entity_id: + - light.living_room_lights + - light.bedroom_lights + - light.front_closet_lights + - if: + - condition: not + conditions: + - condition: state + entity_id: vacuum.garbage_goober + state: docked + then: + - action: vacuum.return_to_base + target: + entity_id: vacuum.garbage_goober + mode: single + ''; + + # --------------------------------------------------------------------------- + # PRESENCE - Welcome home + # --------------------------------------------------------------------------- + welcomeHomeAutomation = '' + - id: 'presence_welcome_home' + alias: Presence - Welcome Home + description: 'When Matt arrives home: turn on living room lights, TTS welcome' + triggers: + - trigger: state + entity_id: person.matt_jallen + to: home + from: not_home + conditions: [] + actions: + - action: light.turn_on + data: + brightness_pct: 80 + color_temp_kelvin: 3500 + transition: 2 + target: + entity_id: light.living_room_lights + - action: tts.speak + data: + cache: false + message: >- + Welcome home! The time is {{ now().strftime('%I:%M %p') }}. + {% if states('vacuum.garbage_goober') not in ['docked', 'idle'] %} + Garbage Goober is still {{ states('vacuum.garbage_goober') }}. + {% endif %} + media_player_entity_id: media_player.home_assistant_voice_09cf6a_media_player + target: + entity_id: tts.piper + mode: single + ''; + + voiceRoutineAutomations = concatStrings [ + bedtimeAutomation + morningAutomation + homeStatusAutomation + feedJoeyAutomation + feedLuciAutomation + petStatusAutomation + airQualityNightAutomation + airQualityDayAutomation + airQualityVoiceAutomation + vacuumVoiceAutomation + awayModeAutomation + welcomeHomeAutomation + ]; +in +{ + options.${namespace}.services.home-assistant.automation.voice-routines = { + enable = mkBoolOpt true "Enable voice routines and presence automations"; + }; + + config = lib.mkIf (config.${namespace}.services.home-assistant.enable && cfg.enable) { + environment.etc."hass/voice-routines.yaml" = { + text = voiceRoutineAutomations; + user = "hass"; + group = "hass"; + }; + }; +}