substitutions: name: esp32-s3-box-3 device_name: esp32-s3-box-3 friendly_name: ESP32 S3 Box 3 api_password: !secret esp32-s3-box-3_api ota_password: !secret ota_password wifi_ssid: !secret wifi_ssid wifi_password: !secret wifi_password gateway: !secret ip_gateway subnet: !secret ip_subnet ip: !secret esp32-s3-box-3_ip micro_wake_word_model: okay_nabu #hvac_sensor: temp_local #pins pin_mute: GPIO1 pin_but_tl: GPIO0 pin_backlight: GPIO47 pin_lcd_cs: GPIO5 pin_lcd_dc: GPIO4 pin_lcd_reset: GPIO48 pin_lcd_clk: GPIO7 pin_lcd_mosi: GPIO6 pin_ir_tx: GPIO38 pin_ir_rx: GPIO39 pin_sda: GPIO41 pin_scl: GPIO40 pin_presence: GPIO21 loading_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/loading_320_240.png idle_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/idle_320_240.png listening_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/listening_320_240.png thinking_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/thinking_320_240.png replying_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/replying_320_240.png error_illustration_file: https://github.com/esphome/firmware/raw/main/voice-assistant/casita/error_320_240.png loading_illustration_background_color: "000000" idle_illustration_background_color: "000000" listening_illustration_background_color: "FFFFFF" thinking_illustration_background_color: "FFFFFF" replying_illustration_background_color: "FFFFFF" error_illustration_background_color: "000000" voice_assist_idle_phase_id: "1" voice_assist_listening_phase_id: "2" voice_assist_thinking_phase_id: "3" voice_assist_replying_phase_id: "4" voice_assist_not_ready_phase_id: "10" voice_assist_error_phase_id: "11" voice_assist_muted_phase_id: "12" # These unqiue characters have been extracted from every test file of every language available on https://github.com/home-assistant/intents (14 March 2024) allowed_characters: " !#%'()+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWYZ[]_abcdefghijklmnopqrstuvwxyz{|}°²³µ¿ÁÂÄÅÉÖÚßàáâãäåæçèéêëìíîðñòóôõöøùúûüýþāăąćčďĐđēėęěğĮįıļľŁłńňőřśšťũūůűųźŻżŽžơưșțΆΈΌΐΑΒΓΔΕΖΗΘΚΜΝΠΡΣΤΥΦάέήίαβγδεζηθικλμνξοπρςστυφχψωϊόύώАБВГДЕЖЗИКЛМНОПРСТУХЦЧШЪЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђєіїјљњћאבגדהוזחטיכלםמןנסעפץצקרשת،ءآأإئابةتجحخدذرزسشصضطظعغفقكلمنهوىيٹپچڈکگںھہیےংকচতধনফবযরলশষস়ািু্చయలిెొ్ംഅആഇഈഉഎഓകഗങചജഞടഡണതദധനപഫബഭമയരറലളവശസഹാിീുൂെേൈ്ൺൻർൽൾაბგდევზთილმნოპრსტუფქყშჩცძჭხạảấầẩậắặẹẽếềểệỉịọỏốồổỗộớờởợụủứừửữựỳ—、一上不个中为主乾了些亮人任低佔何作供依侧係個側偵充光入全关冇冷几切到制前動區卧厅厨及口另右吊后吗启吸呀咗哪唔問啟嗎嘅嘛器圍在场執場外多大始安定客室家密寵对將小少左已帘常幫幾库度庫廊廚廳开式後恆感態成我戲戶户房所扇手打执把拔换掉控插摄整斯新明是景暗更最會有未本模機檯櫃欄次正氏水沒没洗活派温測源溫漏潮激濕灯為無煙照熱燈燥物狀玄现現瓦用發的盞目着睡私空窗立笛管節簾籬紅線红罐置聚聲脚腦腳臥色节著行衣解設調請謝警设调走路車车运連遊運過道邊部都量鎖锁門閂閉開關门闭除隱離電震霧面音頂題顏颜風风食餅餵가간감갔강개거게겨결경고공과관그금급기길깥꺼껐꼽나난내네놀누는능니다닫담대더데도동됐되된됨둡드든등디때떤뜨라래러렇렌려로료른를리림링마많명몇모무문물뭐바밝방배변보부불블빨뽑사산상색서설성세센션소쇼수스습시신실싱아안않알았애야어얼업없었에여연열옆오온완외왼요운움워원위으은을음의이인일임입있작잠장재전절정제져조족종주줄중줘지직진짐쪽차창천최추출충치침커컴켜켰쿠크키탁탄태탬터텔통트튼티파팬퍼폰표퓨플핑한함해했행혀현화활후휴힘,?" packages: board: !include boards/esp32s3box.yaml connection: !include common/wifi.yaml spk: !include interfaces/spkmicvoiceboxS3.yaml #mmwave: !include sensors/mmwave_at581x.yaml #ahtsens: !include sensors/aht30.yaml #i2c: # - id: bus_0 # sda: GPIO8 # scl: GPIO18 # scan: false # frequency: 100kHz # - id: bus_a # sda: ${pin_sda} # scl: ${pin_scl} # sda_pullup_enabled: false # scl_pullup_enabled: false # scan: false # frequency: 100kHz esphome: name: ${name} friendly_name: ${friendly_name} name_add_mac_suffix: true platformio_options: board_build.flash_mode: dio project: name: esphome.voice-assistant version: "2.0" min_version: 2023.11.5 on_boot: priority: 600 then: - script.execute: draw_display - delay: 30s - if: condition: lambda: return id(init_in_progress); then: - lambda: id(init_in_progress) = false; - script.execute: draw_display api: on_client_connected: - script.execute: draw_display on_client_disconnected: - script.execute: draw_display ota: logger: hardware_uart: USB_SERIAL_JTAG dashboard_import: package_import_url: github://esphome/firmware/wake-word-voice-assistant/esp32-s3-box-3.yaml@main wifi: ap: on_connect: - script.execute: draw_display on_disconnect: - script.execute: draw_display button: - platform: factory_reset id: factory_reset_btn name: Factory reset binary_sensor: - platform: gpio pin: number: ${pin_mute} inverted: true name: "Mute" disabled_by_default: true entity_category: diagnostic - platform: gpio pin: number: ${pin_but_tl} mode: INPUT_PULLUP inverted: true name: Top Left Button disabled_by_default: true entity_category: diagnostic on_multi_click: - timing: - ON for at least 10s then: - button.press: factory_reset_btn output: - platform: ledc pin: ${pin_backlight} id: backlight_output light: - platform: monochromatic id: led name: LCD Backlight entity_category: config output: backlight_output restore_mode: RESTORE_DEFAULT_ON default_transition_length: 250ms micro_wake_word: model: ${micro_wake_word_model} on_wake_word_detected: - voice_assistant.start: wake_word: !lambda return wake_word; voice_assistant: id: va microphone: box_mic speaker: box_speaker use_wake_word: true noise_suppression_level: 2 auto_gain: 31dBFS volume_multiplier: 2.0 vad_threshold: 3 on_listening: - lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id}; - text_sensor.template.publish: id: text_request state: "..." - text_sensor.template.publish: id: text_response state: "..." - script.execute: draw_display on_stt_vad_end: - lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id}; - script.execute: draw_display on_stt_end: - text_sensor.template.publish: id: text_request state: !lambda return x; - script.execute: draw_display on_tts_start: - text_sensor.template.publish: id: text_response state: !lambda return x; on_tts_stream_start: - lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id}; - script.execute: draw_display on_tts_stream_end: - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id}; - script.execute: draw_display on_end: - if: condition: and: - switch.is_off: mute - lambda: return id(wake_word_engine_location).state == "On device"; then: - wait_until: not: voice_assistant.is_running: - micro_wake_word.start: on_error: - if: condition: lambda: return !id(init_in_progress); then: - lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id}; - script.execute: draw_display - delay: 1s - if: condition: switch.is_off: mute then: - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id}; else: - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id}; - script.execute: draw_display on_client_connected: - if: condition: switch.is_off: mute then: # - wait_until: # not: ble.enabled - if: condition: lambda: return id(wake_word_engine_location).state == "In Home Assistant"; then: - lambda: id(va).set_use_wake_word(true); - voice_assistant.start_continuous: - if: condition: lambda: return id(wake_word_engine_location).state == "On device"; then: - micro_wake_word.start - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id}; else: - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id}; - lambda: id(init_in_progress) = false; - script.execute: draw_display on_client_disconnected: - if: condition: lambda: return id(wake_word_engine_location).state == "In Home Assistant"; then: - lambda: id(va).set_use_wake_word(false); - voice_assistant.stop: - if: condition: lambda: return id(wake_word_engine_location).state == "On device"; then: - micro_wake_word.stop - lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id}; - script.execute: draw_display script: - id: draw_display then: - if: condition: lambda: return !id(init_in_progress); then: - if: condition: wifi.connected: then: - if: condition: api.connected: then: - lambda: | switch(id(voice_assistant_phase)) { case ${voice_assist_listening_phase_id}: id(s3_box_lcd).show_page(listening_page); id(s3_box_lcd).update(); break; case ${voice_assist_thinking_phase_id}: id(s3_box_lcd).show_page(thinking_page); id(s3_box_lcd).update(); break; case ${voice_assist_replying_phase_id}: id(s3_box_lcd).show_page(replying_page); id(s3_box_lcd).update(); break; case ${voice_assist_error_phase_id}: id(s3_box_lcd).show_page(error_page); id(s3_box_lcd).update(); break; case ${voice_assist_muted_phase_id}: id(s3_box_lcd).show_page(muted_page); id(s3_box_lcd).update(); break; case ${voice_assist_not_ready_phase_id}: id(s3_box_lcd).show_page(no_ha_page); id(s3_box_lcd).update(); break; default: id(s3_box_lcd).show_page(idle_page); id(s3_box_lcd).update(); } else: - display.page.show: no_ha_page - component.update: s3_box_lcd else: - display.page.show: no_wifi_page - component.update: s3_box_lcd else: - display.page.show: initializing_page - component.update: s3_box_lcd switch: - platform: template name: Display conversation id: display_conversation optimistic: true restore_mode: RESTORE_DEFAULT_ON entity_category: config - platform: template name: Mute id: mute optimistic: true restore_mode: RESTORE_DEFAULT_OFF entity_category: config on_turn_off: - if: condition: lambda: return !id(init_in_progress); then: - lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id}; - if: condition: not: - voice_assistant.is_running then: - if: condition: lambda: return id(wake_word_engine_location).state == "In Home Assistant"; then: - lambda: id(va).set_use_wake_word(true); - voice_assistant.start_continuous - if: condition: lambda: return id(wake_word_engine_location).state == "On device"; then: - micro_wake_word.start - script.execute: draw_display on_turn_on: - if: condition: lambda: return !id(init_in_progress); then: - lambda: id(va).set_use_wake_word(false); - voice_assistant.stop - micro_wake_word.stop - lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id}; - script.execute: draw_display select: - platform: template entity_category: config name: Wake word engine location id: wake_word_engine_location optimistic: true restore_value: true options: - In Home Assistant - On device initial_option: On device on_value: - wait_until: lambda: return id(voice_assistant_phase) == ${voice_assist_muted_phase_id} || id(voice_assistant_phase) == ${voice_assist_idle_phase_id}; - if: condition: lambda: return x == "In Home Assistant"; then: - micro_wake_word.stop - delay: 500ms - if: condition: switch.is_off: mute then: - lambda: id(va).set_use_wake_word(true); - voice_assistant.start_continuous: - if: condition: lambda: return x == "On device"; then: - lambda: id(va).set_use_wake_word(false); - voice_assistant.stop - delay: 500ms - micro_wake_word.start globals: - id: init_in_progress type: bool restore_value: false initial_value: "true" - id: voice_assistant_phase type: int restore_value: false initial_value: ${voice_assist_not_ready_phase_id} image: - file: ${error_illustration_file} id: casita_error resize: 320x240 type: RGB24 use_transparency: true - file: ${idle_illustration_file} id: casita_idle resize: 320x240 type: RGB24 use_transparency: true - file: ${listening_illustration_file} id: casita_listening resize: 320x240 type: RGB24 use_transparency: true - file: ${thinking_illustration_file} id: casita_thinking resize: 320x240 type: RGB24 use_transparency: true - file: ${replying_illustration_file} id: casita_replying resize: 320x240 type: RGB24 use_transparency: true - file: ${loading_illustration_file} id: casita_initializing resize: 320x240 type: RGB24 use_transparency: true - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-wifi.png id: error_no_wifi resize: 320x240 type: RGB24 use_transparency: true - file: https://github.com/esphome/firmware/raw/main/voice-assistant/error_box_illustrations/error-no-ha.png id: error_no_ha resize: 320x240 type: RGB24 use_transparency: true font: - file: type: gfonts family: Figtree weight: 300 italic: true glyphs: ${allowed_characters} id: font_request size: 15 - file: type: gfonts family: Figtree weight: 300 glyphs: ${allowed_characters} id: font_response size: 15 text_sensor: - id: text_request platform: template on_value: lambda: |- if(id(text_request).state.length()>32) { std::string name = id(text_request).state.c_str(); std::string truncated = esphome::str_truncate(name.c_str(),31); id(text_request).state = (truncated+"...").c_str(); } - id: text_response platform: template on_value: lambda: |- if(id(text_response).state.length()>32) { std::string name = id(text_response).state.c_str(); std::string truncated = esphome::str_truncate(name.c_str(),31); id(text_response).state = (truncated+"...").c_str(); } color: - id: idle_color hex: ${idle_illustration_background_color} - id: listening_color hex: ${listening_illustration_background_color} - id: thinking_color hex: ${thinking_illustration_background_color} - id: replying_color hex: ${replying_illustration_background_color} - id: loading_color hex: ${loading_illustration_background_color} - id: error_color hex: ${error_illustration_background_color} spi: clk_pin: ${pin_lcd_clk} #7 mosi_pin: ${pin_lcd_mosi} #6 display: - platform: ili9xxx id: s3_box_lcd model: S3BOX data_rate: 40MHz cs_pin: ${pin_lcd_cs} #5 dc_pin: ${pin_lcd_dc} #4 reset_pin: number: ${pin_lcd_reset} #48 inverted: true update_interval: never pages: - id: idle_page lambda: |- it.fill(id(idle_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_idle), ImageAlign::CENTER); - id: listening_page lambda: |- it.fill(id(listening_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_listening), ImageAlign::CENTER); - id: thinking_page lambda: |- it.fill(id(thinking_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_thinking), ImageAlign::CENTER); if (id(display_conversation).state) { it.filled_rectangle(20 , 20 , 280 , 30 , Color::WHITE ); it.rectangle(20 , 20 , 280 , 30 , Color::BLACK ); it.printf(30, 25, id(font_request), Color::BLACK, "%s", id(text_request).state.c_str()); } - id: replying_page lambda: |- it.fill(id(replying_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_replying), ImageAlign::CENTER); if (id(display_conversation).state) { it.filled_rectangle(20 , 20 , 280 , 30 , Color::WHITE ); it.rectangle(20 , 20 , 280 , 30 , Color::BLACK ); it.filled_rectangle(20 , 190 , 280 , 30 , Color::WHITE ); it.rectangle(20 , 190 , 280 , 30 , Color::BLACK ); it.printf(30, 25, id(font_request), Color::BLACK, "%s", id(text_request).state.c_str()); it.printf(30, 195, id(font_response), Color::BLACK, "%s", id(text_response).state.c_str()); } - id: error_page lambda: |- it.fill(id(error_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_error), ImageAlign::CENTER); - id: no_ha_page lambda: |- it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_ha), ImageAlign::CENTER); - id: no_wifi_page lambda: |- it.image((it.get_width() / 2), (it.get_height() / 2), id(error_no_wifi), ImageAlign::CENTER); - id: initializing_page lambda: |- it.fill(id(loading_color)); it.image((it.get_width() / 2), (it.get_height() / 2), id(casita_initializing), ImageAlign::CENTER); - id: muted_page lambda: |- it.fill(Color::BLACK);