From 2ae93bc6b85ed181734f8544cba4fc9256a8a988 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Fri, 6 Sep 2024 15:00:44 -0400 Subject: [PATCH] feat(esphome): have entire config local --- .../homie/modules/home-assistant/assist.nix | 10 + .../homie/modules/home-assistant/firmware.nix | 397 +++++++++++++++++- 2 files changed, 397 insertions(+), 10 deletions(-) diff --git a/devices/homie/modules/home-assistant/assist.nix b/devices/homie/modules/home-assistant/assist.nix index bea97782..180e4b51 100644 --- a/devices/homie/modules/home-assistant/assist.nix +++ b/devices/homie/modules/home-assistant/assist.nix @@ -19,6 +19,16 @@ customComponents = builtins.attrValues { inherit (self.legacyPackages.${pkgs.system}.hass-components) home-llm; + # Home-llm prompt: + /* + You are 'Homie', a helpful AI Assistant that controls the devices in a house. Complete the following task as instructed. + + The current time and date is {{ (as_timestamp(now()) | timestamp_custom("%I:%M %p on %A %B %d, %Y", "EST")) }}. + + Services: {{ formatted_tools }} + Devices: + {{ formatted_devices }} + */ }; config = { diff --git a/devices/homie/modules/home-assistant/firmware.nix b/devices/homie/modules/home-assistant/firmware.nix index 9aa5dda0..52a2bc4d 100644 --- a/devices/homie/modules/home-assistant/firmware.nix +++ b/devices/homie/modules/home-assistant/firmware.nix @@ -4,13 +4,7 @@ firmwareConfigs = { "esp1" = { - packages.remote_package_files = { - url = "https://github.com/esphome/firmware"; - files = ["voice-assistant/m5stack-atom-echo.adopted.yaml"]; - ref = "0f6fad0860b8bd2c251162abde5064be1ae29546"; - }; - - # Enable Home Assistant API + # Device specific settings api.encryption.key = "!secret api_key"; ota = [ @@ -25,19 +19,402 @@ password = "!secret wifi_password"; manual_ip = { - # Set this to the IP of the ESP static_ip = "192.168.0.92"; - # Set this to the IP address of the router. Often ends with .1 gateway = "192.168.0.1"; subnet = "255.255.255.0"; }; - # Enable fallback hotspot (captive portal) in case wifi connection fails ap = { ssid = "Esp1 Fallback Hotspot"; password = "!secret ap_fallback"; }; }; + + # Hardware Declaration + esphome = { + friendly_name = "M5Stack Atom Echo"; + min_version = "2024.6.0"; + name = "m5stack-atom-echo"; + name_add_mac_suffix = true; + }; + + esp32 = { + board = "m5stack-atom"; + framework.type = "esp-idf"; + }; + + esp_adf = {}; + + button = [ + { + id = "button_safe_mode"; + name = "Safe Mode Boot"; + platform = "safe_mode"; + } + { + id = "factory_reset_btn"; + name = "Factory reset"; + platform = "factory_reset"; + } + ]; + + microphone = [ + { + adc_type = "external"; + i2s_din_pin = "GPIO23"; + id = "echo_microphone"; + pdm = true; + platform = "i2s_audio"; + } + ]; + + speaker = [ + { + dac_type = "external"; + i2s_dout_pin = "GPIO22"; + id = "echo_speaker"; + mode = "mono"; + platform = "i2s_audio"; + } + ]; + + i2s_audio = [ + { + id = "i2s_audio_bus"; + i2s_bclk_pin = "GPIO19"; + i2s_lrclk_pin = "GPIO33"; + } + ]; + + light = [ + { + id = "led"; + name = "None"; + entity_category = "config"; + + chipset = "SK6812"; + pin = "GPIO27"; + platform = "esp32_rmt_led_strip"; + + default_transition_length = "0s"; + disabled_by_default = false; + num_leds = 1; + rgb_order = "grb"; + rmt_channel = 0; + + effects = [ + { + pulse = { + name = "Slow Pulse"; + + max_brightness = "100%"; + min_brightness = "50%"; + transition_length = "250ms"; + update_interval = "250ms"; + }; + } + { + pulse = { + name = "Fast Pulse"; + + max_brightness = "100%"; + min_brightness = "50%"; + transition_length = "100ms"; + update_interval = "100ms"; + }; + } + ]; + } + ]; + + # Home-assistant buttons + switch = [ + { + id = "use_wake_word"; + name = "Use wake word"; + entity_category = "config"; + + optimistic = true; + platform = "template"; + restore_mode = "RESTORE_DEFAULT_ON"; + + on_turn_off = ["voice_assistant.stop" {lambda = "id(va).set_use_wake_word(false);";} {"script.execute" = "reset_led";}]; + on_turn_on = [ + {lambda = "id(va).set_use_wake_word(true);";} + { + "if" = { + condition.not = ["voice_assistant.is_running"]; + "then" = ["voice_assistant.start_continuous"]; + }; + } + {"script.execute" = "reset_led";} + ]; + } + { + id = "use_listen_light"; + name = "Use listen light"; + entity_category = "config"; + + optimistic = true; + platform = "template"; + restore_mode = "RESTORE_DEFAULT_ON"; + + on_turn_off = [{"script.execute" = "reset_led";}]; + on_turn_on = [{"script.execute" = "reset_led";}]; + } + { + id = "timer_ringing"; + + internal = true; + optimistic = true; + platform = "template"; + restore_mode = "ALWAYS_OFF"; + + on_turn_on = [ + {delay = "15min";} + {"switch.turn_off" = "timer_ringing";} + ]; + } + ]; + + binary_sensor = [ + { + id = "echo_button"; + name = "Button"; + entity_category = "diagnostic"; + + disabled_by_default = false; + + platform = "gpio"; + pin = { + inverted = true; + number = "GPIO39"; + }; + + on_multi_click = [ + { + timing = ["ON for at least 50ms" "OFF for at least 50ms"]; + "then" = [ + { + "if" = { + condition = {"switch.is_on" = "timer_ringing";}; + "then" = [{"switch.turn_off" = "timer_ringing";}]; + "else" = [ + { + "if" = { + condition = {"switch.is_off" = "use_wake_word";}; + "then" = [ + { + "if" = { + condition = "voice_assistant.is_running"; + "then" = [{"voice_assistant.stop" = {};} {"script.execute" = "reset_led";}]; + "else" = [{"voice_assistant.start" = {};}]; + }; + } + ]; + "else" = [ + "voice_assistant.stop" + {delay = "1s";} + {"script.execute" = "reset_led";} + {"script.wait" = "reset_led";} + {"voice_assistant.start_continuous" = {};} + ]; + }; + } + ]; + }; + } + ]; + } + { + timing = ["ON for at least 10s"]; + "then" = [{"button.press" = "factory_reset_btn";}]; + } + ]; + } + ]; + + # Misc + logger = {}; + + external_components = [ + { + components = ["esp_adf"]; + refresh = "0s"; + source = "github://pr#5230"; + } + { + components = ["file"]; + refresh = "0s"; + source = "github://jesserockz/esphome-components"; + } + ]; + + file = [ + { + file = "https://github.com/esphome/firmware/raw/main/voice-assistant/sounds/timer_finished.wav"; + id = "timer_finished_wave_file"; + } + ]; + + # Configs + script = [ + { + id = "reset_led"; + "then" = [ + { + "if" = { + condition = [ + {"switch.is_on" = "use_wake_word";} + {"switch.is_on" = "use_listen_light";} + ]; + "then" = [ + { + "light.turn_on" = { + id = "led"; + brightness = "60%"; + effect = "none"; + + red = "100%"; + green = "89%"; + blue = "71%"; + }; + } + ]; + "else" = [{"light.turn_off" = "led";}]; + }; + } + ]; + } + ]; + + voice_assistant = { + id = "va"; + speaker = "echo_speaker"; + microphone = "echo_microphone"; + auto_gain = "31dBFS"; + + noise_suppression_level = 2; + vad_threshold = 3; + volume_multiplier = 2; + + on_client_connected = [ + { + "if" = { + condition = {"switch.is_on" = "use_wake_word";}; + "then" = [ + {"voice_assistant.start_continuous" = {};} + {"script.execute" = "reset_led";} + ]; + }; + } + ]; + on_client_disconnected = [ + { + "if" = { + condition = {"switch.is_on" = "use_wake_word";}; + "then" = [ + {"voice_assistant.stop" = {};} + {"light.turn_off" = "led";} + ]; + }; + } + ]; + on_end = [ + {delay = "100ms";} + {wait_until.not."speaker.is_playing" = {};} + {"script.execute" = "reset_led";} + ]; + on_error = [ + { + "light.turn_on" = { + id = "led"; + brightness = "100%"; + effect = "none"; + + red = "100%"; + green = "0%"; + blue = "0%"; + }; + } + {delay = "1s";} + {"script.execute" = "reset_led";} + ]; + on_listening = [ + { + "light.turn_on" = { + id = "led"; + effect = "Slow Pulse"; + + green = "0%"; + red = "0%"; + blue = "100%"; + }; + } + ]; + on_stt_vad_end = [ + { + "light.turn_on" = { + id = "led"; + effect = "Fast Pulse"; + + red = "0%"; + green = "0%"; + blue = "100%"; + }; + } + ]; + on_timer_finished = [ + {"voice_assistant.stop" = {};} + {"switch.turn_on" = "timer_ringing";} + {wait_until.not."microphone.is_capturing" = {};} + { + "light.turn_on" = { + id = "led"; + brightness = "100%"; + effect = "Fast Pulse"; + + red = "0%"; + green = "100%"; + blue = "0%"; + }; + } + { + while = { + condition = {"switch.is_on" = "timer_ringing";}; + "then" = [ + {lambda = "id(echo_speaker).play(id(timer_finished_wave_file), sizeof(id(timer_finished_wave_file)));";} + {delay = "1s";} + ]; + }; + } + {wait_until.not."speaker.is_playing" = {};} + {"light.turn_off" = "led";} + {"switch.turn_off" = "timer_ringing";} + { + "if" = { + condition = {"switch.is_on" = "use_wake_word";}; + "then" = [ + {"voice_assistant.start_continuous" = {};} + {"script.execute" = "reset_led";} + ]; + }; + } + ]; + on_tts_start = [ + { + "light.turn_on" = { + id = "led"; + brightness = "100%"; + effect = "none"; + + red = "0%"; + green = "0%"; + blue = "100%"; + }; + } + ]; + }; }; }; };