mirror of
https://github.com/randybb/esphome-configs.git
synced 2026-10-14 16:35:12 +02:00
mcu-stackchan: add M5Stack StackChan config
Voice assistant satellite with animated face on the CoreS3 display: - power rails, IO expander resets and servo power as in the M5 BSP - mic and speaker share one I2S bus, so the mic is released before playback - AW88298 volume limited to the audible range (0.5-1.0) - partial display redraws, status screen until Home Assistant connects - battery and wifi indicators, listening icon - camera converted to JPEG on demand, no periodic frames
This commit is contained in:
1 parent
6348e6bbb6
commit
39e744900d
2 files changed
+696
No files matched your search
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1194" height="240" fill="none" viewBox="0 0 1194 240"><g clip-path="url(#clip0_2_63)"><path fill="#18BCF2" d="M240 219C240 227.24 233.24 234 225 234H15C6.76 234 0 227.24 0 219V129C0 120.76 4.78 109.22 10.6 103.4L109.4 4.61999C115.24 -1.22001 124.78 -1.22001 130.62 4.61999L229.4 103.4C235.24 109.24 240 120.76 240 129V219Z"/><path fill="#F2F4F9" d="M160 78.66H80C76.68 78.66 74 81.34 74 84.66V234H86V90.66H154V102.66H104C100.68 102.66 98 105.34 98 108.66V132.66C98 135.98 100.68 138.66 104 138.66H154V150.66H104C100.68 150.66 98 153.34 98 156.66V180.66C98 183.98 100.68 186.66 104 186.66H154V198.66H104C100.68 198.66 98 201.34 98 204.66C98 207.98 100.68 210.66 104 210.66H160C163.32 210.66 166 207.98 166 204.66V180.66C166 177.34 163.32 174.66 160 174.66H110V162.66H160C163.32 162.66 166 159.98 166 156.66V132.66C166 129.34 163.32 126.66 160 126.66H110V114.66H160C163.32 114.66 166 111.98 166 108.66V84.66C166 81.34 163.32 78.66 160 78.66Z"/><path fill="#F2F4F9" d="M423 167.61V190.11H336V49.95H423V72.63H359.82V108.91H416.26V130.01H359.82V167.61H423Z"/><path fill="#F2F4F9" d="M491.08 47.41C503.9 47.41 514.54 50.53 523.04 56.79C531.54 63.03 536.76 71.39 538.7 81.83L516.3 87.93C515.04 81.93 512.12 77.31 507.54 74.05C502.94 70.81 497.28 69.17 490.52 69.17C483.26 69.17 477.52 70.91 473.28 74.37C469.02 77.83 466.9 82.45 466.9 88.19C466.9 97.25 472.52 103.13 483.78 105.81L506.66 111.53C518.48 114.59 527.32 119.45 533.2 126.11C539.08 132.77 542.02 140.97 542.02 150.71C542.02 163.21 537.48 173.31 528.42 181.03C519.36 188.75 507.24 192.61 492.04 192.61C478.36 192.61 466.78 189.39 457.36 182.95C448.04 176.27 442.54 167.45 440.86 156.51L463.26 150.69C464.08 156.87 467.16 161.73 472.5 165.23C477.84 168.73 484.64 170.47 492.9 170.47C501.16 170.47 507.08 168.79 511.6 165.45C516.14 162.11 518.4 157.57 518.4 151.81C518.4 142.81 512.78 136.87 501.52 133.99L478.64 128.45C466.88 125.77 458.06 120.99 452.16 114.15C446.26 107.31 443.3 98.95 443.3 89.07C443.3 76.69 447.7 66.65 456.48 58.93C465.26 51.21 476.8 47.35 491.12 47.35L491.08 47.41Z"/><path fill="#F2F4F9" d="M666.1 95.61C666.1 109.37 661.92 120.49 653.58 128.99C645.24 137.49 634 141.75 619.88 141.75H588.38V190.13H564.56V49.95H620.06C634.3 49.95 645.54 54.07 653.76 62.27C661.98 70.49 666.08 81.59 666.08 95.59L666.1 95.61ZM641.16 95.05C641.16 87.99 638.96 82.25 634.56 77.85C630.16 73.45 623.92 71.25 615.86 71.25H588.4V120.85H615.5C623.74 120.85 630.1 118.51 634.54 113.81C638.98 109.13 641.2 102.87 641.2 95.05H641.16Z"/><path fill="#F2F4F9" d="M687.38 49.95H711.2V108.55L772.98 108.73V49.95H796.98V190.11H772.98V129.83L711.2 129.65V190.11H687.38V49.95Z"/><path fill="#F2F4F9" d="M868.6 92.79C883.1 92.79 894.92 97.45 904.04 106.75C913.16 116.07 917.72 128.03 917.72 142.65C917.72 157.27 913.16 169.15 904.04 178.47C894.92 187.79 883.1 192.43 868.6 192.43C854.1 192.43 842.1 187.77 832.98 178.47C823.86 169.15 819.3 157.23 819.3 142.65C819.3 128.07 823.86 115.97 832.98 106.69C842.1 97.41 853.98 92.77 868.6 92.77V92.79ZM868.6 172.01C876.34 172.01 882.64 169.27 887.5 163.81C892.34 158.35 894.76 151.23 894.76 142.49C894.76 133.75 892.34 126.67 887.5 121.25C882.66 115.85 876.36 113.15 868.6 113.15C860.84 113.15 854.26 115.85 849.38 121.25C844.5 126.65 842.06 133.73 842.06 142.49C842.06 151.25 844.5 158.35 849.38 163.81C854.26 169.27 860.66 172.01 868.6 172.01Z"/><path fill="#F2F4F9" d="M1081.6 129.83V190.11H1059.56V133.29C1059.56 126.73 1057.88 121.61 1054.5 117.91C1051.12 114.23 1046.54 112.37 1040.72 112.37C1034.54 112.37 1029.6 114.39 1025.9 118.41C1022.22 122.45 1020.36 128.09 1020.36 135.33V190.09H998.04V133.27C998.04 126.71 996.4 121.59 993.12 117.89C989.84 114.21 985.3 112.35 979.48 112.35C973.3 112.35 968.32 114.37 964.58 118.39C960.84 122.41 958.96 128.07 958.96 135.31V190.07H936.28V95.39H957.38L958.14 104.49C963.64 96.43 972.6 92.39 985.04 92.39C992.28 92.39 998.58 93.89 1003.88 96.89C1009.2 99.89 1013.22 104.27 1015.98 110.01C1018.3 104.51 1022.12 100.19 1027.46 97.07C1032.8 93.95 1039.16 92.39 1046.54 92.39C1057.28 92.39 1065.82 95.71 1072.14 102.33C1078.46 108.95 1081.6 118.11 1081.6 129.79V129.83Z"/><path fill="#F2F4F9" d="M1193.54 151.11H1120.98C1121.74 158.23 1124.42 163.67 1129.04 167.43C1133.66 171.19 1139.54 173.05 1146.66 173.05C1158.22 173.05 1166.32 168.23 1170.94 158.61L1189.88 166.11C1186.32 174.43 1180.7 180.93 1173.06 185.61C1165.4 190.29 1156.6 192.65 1146.66 192.65C1132.66 192.65 1121.3 188.07 1112.58 178.91C1103.86 169.75 1099.5 157.73 1099.5 142.87C1099.5 128.01 1103.9 115.91 1112.68 106.59C1121.46 97.27 1132.94 92.63 1147.14 92.63C1161.34 92.63 1172.3 97.23 1180.8 106.41C1189.3 115.59 1193.56 127.69 1193.56 142.69V151.13L1193.54 151.11ZM1121.36 135.17H1170.48C1170.04 127.49 1167.72 121.63 1163.5 117.59C1159.28 113.55 1153.6 111.55 1146.48 111.55C1139.36 111.55 1133.66 113.59 1129.24 117.69C1124.8 121.79 1122.18 127.61 1121.36 135.17Z"/></g><defs><clipPath id="clip0_2_63"><rect width="1193.56" height="240" fill="#fff"/></cliLine truncated
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,695 @@
|
||||
substitutions:
|
||||
device: stackchan
|
||||
name: StackChan
|
||||
area: DEV
|
||||
comment: StackChan
|
||||
|
||||
esphome:
|
||||
name: mcu-${device}
|
||||
friendly_name: ${name}
|
||||
area: ${area}
|
||||
comment: ${comment}
|
||||
on_boot:
|
||||
- priority: -100
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: light_bar
|
||||
brightness: 20%
|
||||
red: 0%
|
||||
green: 0%
|
||||
blue: 100%
|
||||
- component.update: display_tft
|
||||
|
||||
esp32:
|
||||
variant: esp32s3
|
||||
flash_size: 16MB
|
||||
toolchain: esp-idf
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
packages:
|
||||
common: !include common/common.yaml
|
||||
|
||||
external_components:
|
||||
- source: github://m5stack/esphome-yaml/components
|
||||
components: [axp2101, aw88298, aw9523b, si12t, m5ioe1, ftservo, scs9009]
|
||||
refresh: 0s
|
||||
|
||||
globals:
|
||||
- id: show_status
|
||||
type: bool
|
||||
initial_value: 'true'
|
||||
- id: mouth_open
|
||||
type: float
|
||||
initial_value: '0.0'
|
||||
- id: eyes_open
|
||||
type: bool
|
||||
initial_value: 'true'
|
||||
- id: speaking
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: listening
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: gaze_x
|
||||
type: int
|
||||
initial_value: '0'
|
||||
- id: gaze_y
|
||||
type: int
|
||||
initial_value: '0'
|
||||
|
||||
i2c:
|
||||
- id: i2c_bsp
|
||||
sda: 12
|
||||
scl: 11
|
||||
frequency: 100kHz
|
||||
|
||||
axp2101:
|
||||
id: axp2101_pmu
|
||||
i2c_id: i2c_bsp
|
||||
|
||||
aw9523b:
|
||||
id: aw9523b_hub
|
||||
i2c_id: i2c_bsp
|
||||
p0_drive_mode: PUSH_PULL
|
||||
|
||||
m5ioe1:
|
||||
id: m5ioe1_hub
|
||||
i2c_id: i2c_bsp
|
||||
reset: true
|
||||
|
||||
output:
|
||||
- platform: axp2101
|
||||
id: lcd_backlight_output
|
||||
type: range
|
||||
channel: DLDO1
|
||||
min_voltage: 2600
|
||||
max_voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: ALDO1
|
||||
voltage: 1800
|
||||
- platform: axp2101
|
||||
channel: ALDO2
|
||||
voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: ALDO3
|
||||
voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: ALDO4
|
||||
voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: BLDO1
|
||||
voltage: 2800
|
||||
- platform: axp2101
|
||||
channel: BLDO2
|
||||
voltage: 1500
|
||||
- platform: axp2101
|
||||
channel: DCDC1
|
||||
voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: DCDC4
|
||||
voltage: 1800
|
||||
- platform: axp2101
|
||||
channel: DCDC3
|
||||
voltage: 3300
|
||||
- platform: axp2101
|
||||
channel: DCDC5
|
||||
voltage: 3300
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
id: boost_en
|
||||
name: Boost
|
||||
entity_category: config
|
||||
pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 15
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
- platform: gpio
|
||||
id: usb_otg_en
|
||||
name: USB OTG
|
||||
entity_category: config
|
||||
pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 5
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
- platform: gpio
|
||||
id: bus_out_en
|
||||
name: BUS Out
|
||||
entity_category: config
|
||||
pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 1
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
- platform: gpio
|
||||
id: light_bar_en
|
||||
internal: true
|
||||
pin:
|
||||
m5ioe1_id: m5ioe1_hub
|
||||
number: 13
|
||||
mode:
|
||||
output: true
|
||||
pullup: true
|
||||
restore_mode: ALWAYS_ON
|
||||
- platform: gpio
|
||||
id: aw_rst
|
||||
internal: true
|
||||
pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 2
|
||||
restore_mode: ALWAYS_ON
|
||||
- platform: gpio
|
||||
id: cam_rst
|
||||
internal: true
|
||||
pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 8
|
||||
restore_mode: ALWAYS_ON
|
||||
- platform: gpio
|
||||
id: servo_power
|
||||
internal: true
|
||||
pin:
|
||||
m5ioe1_id: m5ioe1_hub
|
||||
number: 0
|
||||
mode:
|
||||
output: true
|
||||
pullup: true
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_bus
|
||||
i2s_lrclk_pin: 33
|
||||
i2s_bclk_pin: 34
|
||||
i2s_mclk_pin:
|
||||
number: 0
|
||||
ignore_strapping_warning: true
|
||||
|
||||
audio_adc:
|
||||
- platform: es7210
|
||||
id: es7210_adc
|
||||
i2c_id: i2c_bsp
|
||||
bits_per_sample: 16bit
|
||||
sample_rate: 16000
|
||||
mic_gain: 36
|
||||
|
||||
audio_dac:
|
||||
- platform: aw88298
|
||||
id: aw88298_dac
|
||||
i2c_id: i2c_bsp
|
||||
sample_rate: 48000
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: stackchan_microphone
|
||||
i2s_audio_id: i2s_bus
|
||||
i2s_din_pin: 14
|
||||
adc_type: external
|
||||
sample_rate: 16000
|
||||
bits_per_sample: 16bit
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: speaker_id
|
||||
i2s_audio_id: i2s_bus
|
||||
dac_type: external
|
||||
i2s_dout_pin: 13
|
||||
audio_dac: aw88298_dac
|
||||
sample_rate: 48000
|
||||
buffer_duration: 100ms
|
||||
- platform: mixer
|
||||
id: mixer_speaker_id
|
||||
output_speaker: speaker_id
|
||||
num_channels: 1
|
||||
source_speakers:
|
||||
- id: announcement_spk_mixer_input
|
||||
- id: media_spk_mixer_input
|
||||
- platform: resampler
|
||||
id: media_spk_resampling_input
|
||||
output_speaker: media_spk_mixer_input
|
||||
- platform: resampler
|
||||
id: announcement_spk_resampling_input
|
||||
output_speaker: announcement_spk_mixer_input
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
id: media_out
|
||||
name: Player
|
||||
buffer_size: 14000
|
||||
# AW88298 maps volume linearly to -96..0 dB, the lower half is inaudible
|
||||
volume_min: 0.5
|
||||
volume_max: 1.0
|
||||
media_pipeline:
|
||||
speaker: media_spk_resampling_input
|
||||
format: NONE
|
||||
announcement_pipeline:
|
||||
speaker: announcement_spk_resampling_input
|
||||
format: NONE
|
||||
on_announcement:
|
||||
- script.execute: release_i2s_bus
|
||||
on_play:
|
||||
- script.execute: release_i2s_bus
|
||||
on_idle:
|
||||
- if:
|
||||
condition:
|
||||
not:
|
||||
voice_assistant.is_running:
|
||||
then:
|
||||
- micro_wake_word.start:
|
||||
|
||||
micro_wake_word:
|
||||
id: mww
|
||||
microphone: stackchan_microphone
|
||||
models:
|
||||
- model: okay_nabu
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
microphone: stackchan_microphone
|
||||
media_player: media_out
|
||||
micro_wake_word: mww
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
on_client_connected:
|
||||
- micro_wake_word.start:
|
||||
- delay: 2s
|
||||
- lambda: id(show_status) = false;
|
||||
- component.update: display_tft
|
||||
on_client_disconnected:
|
||||
- micro_wake_word.stop:
|
||||
- lambda: id(show_status) = true;
|
||||
- component.update: display_tft
|
||||
on_listening:
|
||||
- lambda: |-
|
||||
id(listening) = true;
|
||||
id(gaze_x) = 0;
|
||||
id(gaze_y) = 0;
|
||||
- component.update: display_tft
|
||||
- light.turn_on: {id: light_bar, brightness: 60%, red: 0%, green: 60%, blue: 100%}
|
||||
on_stt_end:
|
||||
- lambda: id(listening) = false;
|
||||
- component.update: display_tft
|
||||
- light.turn_on: {id: light_bar, brightness: 40%, red: 80%, green: 50%, blue: 0%}
|
||||
on_tts_start:
|
||||
- lambda: id(speaking) = true;
|
||||
- light.turn_on: {id: light_bar, brightness: 60%, red: 0%, green: 100%, blue: 20%}
|
||||
on_end:
|
||||
- wait_until:
|
||||
not:
|
||||
media_player.is_announcing: media_out
|
||||
- lambda: |-
|
||||
id(speaking) = false;
|
||||
id(listening) = false;
|
||||
id(mouth_open) = 0.0f;
|
||||
- component.update: display_tft
|
||||
- light.turn_on: {id: light_bar, brightness: 20%, red: 0%, green: 0%, blue: 100%}
|
||||
- wait_until:
|
||||
not: voice_assistant.is_running
|
||||
- micro_wake_word.start:
|
||||
on_error:
|
||||
- light.turn_on: {id: light_bar, brightness: 60%, red: 100%, green: 0%, blue: 0%}
|
||||
|
||||
script:
|
||||
# mic and speaker share one I2S bus - the mic must be released before playback
|
||||
- id: release_i2s_bus
|
||||
mode: single
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
microphone.is_capturing:
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
# stop VA only while it is listening (e.g. continued conversation), not during its own response
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(listening);
|
||||
then:
|
||||
- voice_assistant.stop:
|
||||
- wait_until:
|
||||
condition:
|
||||
not:
|
||||
microphone.is_capturing:
|
||||
timeout: 2s
|
||||
|
||||
interval:
|
||||
- interval: 1s # status screen refresh until Home Assistant connects
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(show_status);
|
||||
then:
|
||||
- component.update: display_tft
|
||||
- interval: 90ms
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(speaking)) {
|
||||
id(mouth_open) = 0.15f + (esp_random() % 85) / 100.0f;
|
||||
id(display_tft).update();
|
||||
} else if (id(mouth_open) > 0) {
|
||||
id(mouth_open) = 0;
|
||||
id(display_tft).update();
|
||||
}
|
||||
- interval: 3s # blinking
|
||||
then:
|
||||
- delay: !lambda return esp_random() % 3000;
|
||||
- lambda: id(eyes_open) = false;
|
||||
- component.update: display_tft
|
||||
- delay: 150ms
|
||||
- lambda: id(eyes_open) = true;
|
||||
- component.update: display_tft
|
||||
- interval: 7s # looking around while idle
|
||||
then:
|
||||
- lambda: |-
|
||||
if (!id(speaking) && !id(listening)) {
|
||||
id(gaze_x) = (int) (esp_random() % 21) - 10;
|
||||
id(gaze_y) = (int) (esp_random() % 11) - 5;
|
||||
id(display_tft).update();
|
||||
}
|
||||
|
||||
spi:
|
||||
- id: spi_bus
|
||||
clk_pin: 36
|
||||
mosi_pin: 37
|
||||
|
||||
image:
|
||||
- platform: file
|
||||
file: assets/images/esphome-logo-text-on-dark.svg
|
||||
id: esphome_logo
|
||||
resize: 280x56
|
||||
type: RGB565
|
||||
transparency: alpha_channel
|
||||
- platform: file
|
||||
file: mdi:ear-hearing
|
||||
id: icon_listening
|
||||
resize: 28x28
|
||||
type: BINARY
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_title
|
||||
size: 24
|
||||
- file: "gfonts://Roboto"
|
||||
id: font_status
|
||||
size: 16
|
||||
|
||||
color:
|
||||
- id: status_ok
|
||||
red: 30%
|
||||
green: 85%
|
||||
blue: 40%
|
||||
- id: status_wait
|
||||
red: 100%
|
||||
green: 70%
|
||||
blue: 0%
|
||||
- id: status_low
|
||||
red: 100%
|
||||
green: 20%
|
||||
blue: 20%
|
||||
- id: status_dim
|
||||
red: 55%
|
||||
green: 55%
|
||||
blue: 55%
|
||||
- id: face_fg
|
||||
red: 100%
|
||||
green: 100%
|
||||
blue: 100%
|
||||
- id: face_bg
|
||||
red: 0%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
id: display_tft
|
||||
model: M5CORE
|
||||
dc_pin: 35
|
||||
reset_pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 9
|
||||
cs_pin:
|
||||
number: 3
|
||||
ignore_strapping_warning: true
|
||||
data_rate: 40MHz
|
||||
invert_colors: true
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
lambda: |-
|
||||
static bool first_draw = true;
|
||||
static bool dot_shown = false;
|
||||
static bool bar_drawn = false;
|
||||
static int last_bars = -1, last_batt = -2;
|
||||
static bool last_charging = false;
|
||||
|
||||
const bool wifi_ok = wifi::global_wifi_component->is_connected();
|
||||
const int8_t rssi = wifi_ok ? wifi::global_wifi_component->wifi_rssi() : -127;
|
||||
const int wifi_bars = !wifi_ok ? 0 : rssi > -55 ? 4 : rssi > -65 ? 3 : rssi > -75 ? 2 : 1;
|
||||
const int batt = std::isnan(id(battery_level).state) ? -1 : (int) roundf(id(battery_level).state);
|
||||
const bool charging = id(battery_charging).state;
|
||||
|
||||
// status screen until Home Assistant connects
|
||||
if (id(show_status)) {
|
||||
first_draw = true;
|
||||
it.fill(id(face_bg));
|
||||
it.image(160, 45, id(esphome_logo), ImageAlign::CENTER);
|
||||
it.print(160, 95, id(font_title), id(face_fg), TextAlign::CENTER, "${name}");
|
||||
|
||||
if (wifi_ok) {
|
||||
it.printf(20, 135, id(font_status), id(status_ok), "WiFi: %s (%d dBm)",
|
||||
id(ip_address_sensor).state.c_str(), rssi);
|
||||
} else {
|
||||
it.print(20, 135, id(font_status), id(status_wait), "WiFi: connecting...");
|
||||
}
|
||||
|
||||
const bool api_ok = api::global_api_server->is_connected();
|
||||
it.print(20, 162, id(font_status), api_ok ? id(status_ok) : id(status_wait),
|
||||
api_ok ? "Home Assistant: connected" : "Home Assistant: waiting...");
|
||||
|
||||
if (batt >= 0) {
|
||||
it.printf(20, 189, id(font_status), batt <= 20 && !charging ? id(status_low) : id(status_dim),
|
||||
"Battery: %d%%%s", batt, charging ? " (charging)" : "");
|
||||
}
|
||||
it.printf(20, 212, id(font_status), id(status_dim), "ESPHome %s", ESPHOME_VERSION);
|
||||
it.printf(300, 212, id(font_status), id(status_dim), TextAlign::TOP_RIGHT, "up %us",
|
||||
(unsigned) (millis() / 1000));
|
||||
return;
|
||||
}
|
||||
|
||||
// stack-chan face: 2 eyes + mouth, 320x240
|
||||
// the whole screen is cleared only once, then just the face areas, so that only the
|
||||
// changed region is sent over SPI instead of the whole 150 kB buffer
|
||||
if (first_draw) {
|
||||
it.fill(id(face_bg));
|
||||
first_draw = false;
|
||||
bar_drawn = false;
|
||||
} else {
|
||||
it.filled_rectangle(70, 78, 181, 31, id(face_bg)); // eyes including gaze range
|
||||
it.filled_rectangle(105, 130, 111, 38, id(face_bg)); // mouth
|
||||
if (dot_shown && !id(listening)) {
|
||||
it.filled_rectangle(254, 2, 28, 28, id(face_bg));
|
||||
}
|
||||
}
|
||||
dot_shown = id(listening);
|
||||
|
||||
// status bar (battery top-left, wifi top-right), redrawn only when something changes
|
||||
if (!bar_drawn || wifi_bars != last_bars || batt != last_batt || charging != last_charging) {
|
||||
bar_drawn = true;
|
||||
last_bars = wifi_bars;
|
||||
last_batt = batt;
|
||||
last_charging = charging;
|
||||
it.filled_rectangle(4, 4, 90, 22, id(face_bg));
|
||||
it.filled_rectangle(288, 4, 28, 22, id(face_bg));
|
||||
|
||||
// battery: outline, terminal nub and fill proportional to the charge
|
||||
if (batt >= 0) {
|
||||
const Color c = charging ? id(status_ok) : batt <= 20 ? id(status_low) : id(status_dim);
|
||||
it.rectangle(8, 8, 26, 13, c);
|
||||
it.filled_rectangle(34, 12, 2, 5, c);
|
||||
it.filled_rectangle(10, 10, 22 * batt / 100, 9, c);
|
||||
it.printf(40, 6, id(font_status), c, "%d%%", batt);
|
||||
}
|
||||
|
||||
// wifi: 4 bars, unused ones dimmed; red when disconnected
|
||||
for (int i = 0; i < 4; i++) {
|
||||
const int h = 4 + 3 * i;
|
||||
const Color c = !wifi_ok ? id(status_low) : i < wifi_bars ? id(status_dim) : Color(40, 40, 40);
|
||||
it.filled_rectangle(290 + i * 6, 21 - h, 4, h, c);
|
||||
}
|
||||
}
|
||||
const int ex = id(gaze_x), ey = id(gaze_y);
|
||||
const int eye_y = 93 + ey, eye_r = 8;
|
||||
const int lx = 90 + ex, rx = 230 + ex;
|
||||
|
||||
if (id(eyes_open)) {
|
||||
it.filled_circle(lx, eye_y, eye_r, id(face_fg));
|
||||
it.filled_circle(rx, eye_y, eye_r, id(face_fg));
|
||||
} else {
|
||||
it.filled_rectangle(lx - eye_r, eye_y - 2, eye_r * 2, 4, id(face_fg));
|
||||
it.filled_rectangle(rx - eye_r, eye_y - 2, eye_r * 2, 4, id(face_fg));
|
||||
}
|
||||
|
||||
const float m = id(mouth_open);
|
||||
const int mw = 90 - (int) (40 * m);
|
||||
const int mh = 4 + (int) (26 * m);
|
||||
it.filled_rectangle(160 - mw / 2 + ex / 2, 148 - mh / 2 + ey / 2, mw, mh, id(face_fg));
|
||||
|
||||
if (id(listening)) {
|
||||
it.image(254, 2, id(icon_listening), id(face_fg), id(face_bg)); // left of the wifi bars
|
||||
}
|
||||
|
||||
touchscreen:
|
||||
- platform: ft63x6
|
||||
id: touch_lcd
|
||||
i2c_id: i2c_bsp
|
||||
reset_pin:
|
||||
aw9523b_id: aw9523b_hub
|
||||
number: 0
|
||||
on_touch:
|
||||
- voice_assistant.start:
|
||||
|
||||
esp32_camera:
|
||||
id: camera_id
|
||||
name: Camera
|
||||
i2c_id: i2c_bsp
|
||||
setup_priority: -200 # after power rails and IO expanders
|
||||
pixel_format: RGB565
|
||||
max_framerate: 15 fps
|
||||
frame_buffer_count: 1
|
||||
# GC0308 has no JPEG, convert RGB565 -> JPEG for HA (as in BSP); runs in the main loop, so no periodic frames
|
||||
jpeg_quality: 12
|
||||
idle_framerate: 0 fps
|
||||
vsync_pin:
|
||||
number: 46
|
||||
ignore_strapping_warning: true
|
||||
href_pin: 38
|
||||
external_clock:
|
||||
pin: 2
|
||||
frequency: 20MHz
|
||||
pixel_clock_pin:
|
||||
number: 45
|
||||
ignore_strapping_warning: true
|
||||
data_pins: [39, 40, 41, 42, 15, 16, 48, 47]
|
||||
resolution: 320x240
|
||||
frame_buffer_location: PSRAM
|
||||
|
||||
sensor:
|
||||
- platform: axp2101
|
||||
id: axp2101_sensor
|
||||
battery_level:
|
||||
id: battery_level
|
||||
name: Battery Level
|
||||
filters:
|
||||
- delta: 1.0
|
||||
on_value:
|
||||
- component.update: display_tft
|
||||
battery_charging:
|
||||
id: battery_charging
|
||||
name: Battery Charging
|
||||
on_state:
|
||||
- component.update: display_tft
|
||||
- platform: ltr_als_ps
|
||||
i2c_id: i2c_bsp
|
||||
address: 0x23
|
||||
type: ALS_PS
|
||||
ambient_light:
|
||||
id: ambient_light
|
||||
name: Ambient Light
|
||||
ps_counts:
|
||||
id: proximity_counts
|
||||
name: Proximity Counts
|
||||
- platform: ina226
|
||||
i2c_id: i2c_bsp
|
||||
address: 0x41
|
||||
shunt_resistance: 0.01 ohm
|
||||
max_current: 3.2A
|
||||
current:
|
||||
id: ina226_current
|
||||
name: Current
|
||||
power:
|
||||
id: ina226_power
|
||||
name: Power
|
||||
bus_voltage:
|
||||
id: ina226_bus_voltage
|
||||
name: Bus Voltage
|
||||
|
||||
si12t:
|
||||
id: touch_hub
|
||||
i2c_id: i2c_bsp
|
||||
|
||||
text_sensor:
|
||||
- platform: si12t
|
||||
id: head_touch_1
|
||||
name: Head Touch 1
|
||||
channel: CH_1
|
||||
update_interval: 500ms
|
||||
- platform: si12t
|
||||
id: head_touch_2
|
||||
name: Head Touch 2
|
||||
channel: CH_2
|
||||
update_interval: 500ms
|
||||
- platform: si12t
|
||||
id: head_touch_3
|
||||
name: Head Touch 3
|
||||
channel: CH_3
|
||||
update_interval: 500ms
|
||||
|
||||
uart:
|
||||
- id: uart_servo
|
||||
tx_pin: 6
|
||||
rx_pin: 7
|
||||
baud_rate: 1000000
|
||||
|
||||
scs9009:
|
||||
|
||||
ftservo:
|
||||
- platform: scs9009
|
||||
id: x_servo
|
||||
address: 1
|
||||
- platform: scs9009
|
||||
id: y_servo
|
||||
address: 2
|
||||
|
||||
number:
|
||||
- platform: ftservo
|
||||
ftservo_id: x_servo
|
||||
angle:
|
||||
id: servo_x_angle
|
||||
name: Servo X Angle
|
||||
min_value: -164
|
||||
max_value: 164
|
||||
use_raw_angle: false
|
||||
angle_offset: 164
|
||||
step: 5
|
||||
|
||||
light:
|
||||
- platform: m5ioe1
|
||||
id: light_bar
|
||||
name: Light Bar
|
||||
num_led: 12
|
||||
- platform: monochromatic
|
||||
id: lcd_backlight
|
||||
name: LCD Backlight
|
||||
output: lcd_backlight_output
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
remote_transmitter:
|
||||
id: ir_tx
|
||||
pin: 5
|
||||
carrier_duty_percent: 50%
|
||||
non_blocking: true
|
||||
|
||||
remote_receiver:
|
||||
id: ir_rx
|
||||
pin: 10
|
||||
|
||||
time:
|
||||
- platform: bm8563
|
||||
id: rtc_time
|
||||
i2c_id: i2c_bsp
|
||||
update_interval: never
|
||||
- !include common/time/homeassistant.yaml
|
||||
Reference in new issue
Block a user