578 lines
20 KiB
YAML
578 lines
20 KiB
YAML
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);
|