Add meppa the kobold chapters 1-3
All checks were successful
Build and Publish Docker Image / build (push) Successful in 2m21s

This commit is contained in:
2026-05-27 16:13:05 -06:00
parent 68cc231799
commit a74e45f707
8 changed files with 997 additions and 0 deletions

View File

@@ -20,6 +20,8 @@ enableStructuredBreadcrumbs = false
replyByEmail = false
smartTOC = true
# mainSections = ["section1", "section2"]
# robots = ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,38 @@
+++
date = '2026-05-27T16:00:00-06:00'
draft = false
title = "Meppa the Kobold, Chapter One"
tags = ['story', 'meppa the kobold']
showTableOfContents = false
showPagination = false
showZenMode = false
+++
## Chapter One
A sinking feeling crept through her stomach as the sun worked its way through her tent flap. She had not slept that night, how could she? It was finally the day for her pilgrimage and **Meppa** did not feel like she had the strength to do it.
She lay there allowing time to pass, listening to the sounds of her kin barking and making their way. She could stay here forever, hiding from the world. The warm breeze flowed into the tent, touching her face gently with the first warmth of the day. The breeze was heavy with the humidity of summer, her journey would not be fraught with the fogs of spring or fall. This did not raise her spirits, but instead made her stomach sink further at the thought of the dangers awaiting on the road.
Her mind wandered to vivid visions of being torn to pieces by wolves. She shook her head to try and rid her mind of these thoughts and instead imagined being crushed by a large bear. She clenched her eyes tightly and felt herself falling, and beneath her was a gathering of sharp rocks greeting her like the teeth of some horrific beast.
She screamed and jumped out of her cot.
“I need control, I need control…” she began to chirp under her breath as she feverishly looked around.
She went over to her travel pack and began to meticulously check everything she had packed. She knew it was all there, but the ritual gave her comfort. It was a tactic she used to ground herself in times of uncertainty or fear. Slowly she calmed down, things would be OK, she would not be alone; **Dekkara** would be with her.
After wrapping herself in her travel garb, she carefully wrapped her tail and grabbed the only thing her parents left behind for her, a chipped and fractured scale. Despite its age, It still shone brightly with a fiery red; proof of the longevity and divinity of **Kaelrath**.
Leaving her tent, she saw that most of the camp had already been broken down in preparation for the beginning of the journey. Sitting next to a small fire was **Meppas** sister, **Dekkara**, who quickly noticed her sister and motioned for her to join her.
“A late start today!” **Dekkara** gleefully exclaimed.
“Was not ready,” **Meppa** grumbled.
Her sister was always the optimist, she had been waiting for this pilgrimage nearly her whole life. **Meppa**, however, had been dreading it. Her sister pulled a rusty pan with some sizzling eggs on it and offered it to **Meppa**.
**Meppa** excitedly started scarfing them down, she loved the crunchy shells the most. Her sister proceeded to go and pack up their tent, and **Meppas** eyes slowly drifted southward. Just barely visible over the horizon was a gleaming red jewel, **Kaelrath the Ember of the South**, perched upon her mountain gazing upon the valley of **Fallenstar**. That was her destination, that was her pilgrimage.
## Continue reading
{{< article link="/posts/meppa-2/" showSummary=true compactSummary=true >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,48 @@
+++
date = '2026-05-28T16:00:00-06:00'
draft = false
title = "Meppa the Kobold, Chapter Two"
tags = ['story', 'meppa the kobold']
showTableOfContents = false
showPagination = false
showZenMode = true
+++
## Chapter Two
The journey was slow and arduous. **Meppa** had never walked so far in her entire life. Her travel party avoided the main paths, which was the custom to avoid the **Tall Creatures** as they were considered dangerous. This did mean that they had to travel through the wilds, which brought their own perils to the small group. Thankfully, they had **Kip**, the navigator.
This was not **Kips** first pilgrimage, his first journey was during the cold months, and he still has never talked about the journey. All that is known is of the 7 that set out for **Kaelrath**, only 4 returned. Since then **Kip** had become a bit of a loner, preferring the wilds to the warm safety of **Yipton**. Supposedly, the eldest council even requested to speak with him in **Fathomsreach** but **Kip** never talked much of that if it did happen. All **Meppa** knew was she was thankful **Kip** was there now. No one knew the wilds like he did.
Their camp was outside the settlement **Holey Town**. The **Tall Creatures** built this town in a foolish location as it seemed to be constantly under barrage of falling stars. The name, however, did elicit a humorous reaction from many in **Meppas** party, and it was often joked about back in **Yipton**. Honestly, **Meppa** was a bit curious about the settlement. She had never seen how the **Tall Creatures** lived, only heard of it. It was hard to object to **Dekkara** when she suggested going and checking it out. The lights of the town were within sight of their camp, and they shone as tantalizing beacons, beckoning her to delight in their presence.
Her willpower caved when the stars began to fall, and she and **Ekkard** left in the middle of the night.
Outside Holey Town**, the two sisters found themselves before an ocean of wheat. **Dekkara** reassured **Meppa** that it was safe, any of the **Tall Creatures** would be asleep at this time. They both crawled cautiously through the seemingly endless field of wheat until they finally found the end. **Meppas** heart sank, before them stood another wall of wheat. They cautiously started to travel through the next endless forest of wheat until they exited, facing yet another wall of wheat.
“Where is the town?” Meppa asked nervously.
“It can't be much farther.” Dekkara replied.
They slowly ventured into the next field of wheat, and **Meppa** could have sworn she heard rustling. She tugged on **Dekkaras** tail and whispered in her ear.
“I think I heard something, be still”
Both sisters froze in place and listened carefully. They could hear the wind, and a gentle crash in the distance as a star came crashing down to earth. What could only have been a few moments felt like an age, as they listened and waited in fear. **Meppa** could swear she heard something, she cautiously turned to her sister, ready to whisper that perhaps it was nothing when in a blink of an eye, her sister vanished.
**Meppa** could feel the wheat whipping by her and a bizarre pain in her tail as if a trap had snared her. She tried in desperation to grab anything to stop her unplanned abduction. Whatever had her whipped her around with ease, threatening to snap her neck with force. For only a moment whatever had taken her let go before a mouth of fangs and stinging pain gripped her by her neck. This was it, this was how **Meppa** would die.
Snarling, pain, the taste of blood, the burning of her neck, the sound of the breeze blowing through the wheat. **Meppa** could feel her body failing her. The beast had her, she was nothing, she was weak.
A sudden howl of pain followed by a guttural screeching suddenly ceased the pain of daggers digging into **Meppas** neck, she tried to look and see, but her body refused to respond. Soon all she could hear was the sound of a blade stabbing a corpse repeatedly. The world went black.
**Meppa** awoke, being pulled by her tail once again; the creature still had her, where was her sister? She tried to look, tried to do anything to escape her fate, to find **Dekkara**.
“Please dont struggle.” Came **Dekkaras** voice.
“You are wounded, I must get you to camp”
**Meppa** relaxed and felt herself losing consciousness. The embrace of the earth under her and the incessant itching sensation in her neck was the only thing keeping her awake. She would later learn that **Dekkara** had drugged her all the way back to camp, stopping several times to try and staunch her bleeding. **Meppa** and **Dekkara** were both spared the lectures due to **Meppas** condition, but they could feel the disapproval of the group. Such mistakes could not be made again, for both their sakes.
## Continue reading
{{< article link="/posts/meppa-3/" showSummary=true compactSummary=true >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -0,0 +1,23 @@
+++
date = '2026-05-29T16:00:00-06:00'
draft = true
title = "Meppa the Kobold, Chapter Three"
tags = ['story', 'meppa the kobold']
showTableOfContents = false
showPagination = false
showZenMode = true
+++
## Chapter Three
He vanished in the night. No one knew where he went or if he went there voluntarily, but **Kip** was gone. **Meppa** was convinced he had probably been eaten by some monster, perhaps the same that had mauled her a week prior. Her neck had still not healed fully, even with medical attention from **Silbin** the healer of the group. Thanks to her, **Meppa** was likely to make a full recovery, but that was not her primary concern.
With the group gathered around their meager fire, one question remained, where do we go from here? **Meppa** just wanted to curl up and die, to dig a hole to hide in, maybe fill that hole with spikes...
It was **Dekkara** who took charge in the end. She said simply that, "If we head towards **Kaelrath**, we head towards our goal. She is watching over us!" This did seem to inspire the group with some hope, and even **Meppa** felt a bit of hope despite the desperation that she was drowning in. They decided the best course of action was to keep heading towards **Kaelrath** and avoid any **Tall Creature** towns.
Leaving the outskirts of **Holey Town**, they could see **Kaelrath** through a break in the mountain range decorated with thick fogs. This was their most promising direction to head, and **Meppa** was certain their path would not take them through fogs. Without their navigator or even a map, their best bet was to travel as directly as they could. They soon found a river that cut through the mountain range. Following it, they reached a point in the river where it was joined by another creating an impassible obstacle, water.
They put as much ingenuity as they could come up with to solve the problem, one considered a raft, but without proper materials or time this was not an option, another was to follow the river until its beginning. The idea that took the most interest was a catapult. They would simply propel themselves across, and its design was going to be significantly simpler than a raft. **Meppa** found herself as the unwilling test subject for the catapult, not only since she was unskilled at the arts of construction, but also due to the animosity that her traveling party had grown for her and her sister. **Dekkara** reassured her that it would be OK, **Meppa** was uncertain, she was not a strong swimmer, and the river was likely to sweep her away.
When the contraption was finalized, a rudimentary rope and a couple logs quickly assembled, **Meppa** found herself seated on the device with great uncertainty. The rope used to provide tension creaked and groaned under the stress it found itself in, threatening to explode at any moment. Her group released the catapult arm and **Meppa** found herself flying, she felt her stomach fly into her chest. The world went into slow motion as her heart raced

886
temp.yaml Normal file
View File

@@ -0,0 +1,886 @@
###################################
# _ __ _ _ _ #
# | |/ / | | | | | | #
# | ' / ___ | |__ ___ | | __| | #
# | < / _ \| '_ \ / _ \| |/ _` | #
# | . \ (_) | |_) | (_) | | (_| | #
# |_|\_\___/|_.__/ \___/|_|\__,_| #
###################################
# Voice assistant based on
# ESP32-S3-Touch-AMOLED-1.75
# Incorporates the following:
# - Display -- CO5300
# - Microphone -- ES7210
# - Speaker -- ES8311
##################################
esphome:
name: kobold
friendly_name: Kobold
esp32:
board: esp32-s3-devkitc-1
flash_size: 16MB
cpu_frequency: 240MHZ
framework:
type: esp-idf
###################################
# _____ __ _ #
# / ____| / _(_) #
# | | ___ _ __ | |_ _ __ _ #
# | | / _ \| '_ \| _| |/ _` | #
# | |___| (_) | | | | | | | (_| | #
# \_____\___/|_| |_|_| |_|\__, | #
# __/ | #
# |___/ #
#Config############################
substitutions:
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"
voice_assist_timer_finished_phase_id: "20"
i2s_bps_spk: 16bit
i2s_bps_mic: 16bit
i2s_sample_rate_spk: 44100
i2s_sample_rate_mic: 16000
loading_illustration_file: kobold/loading.png
listening_illustration_file: kobold/listening.png
thinking_illustration_file: kobold/thinking.png
replying_illustration_file: kobold/responding.png
error_illustration_file: kobold/error.png
logger:
level: DEBUG
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}
- id: global_first_active_timer
type: voice_assistant::Timer
restore_value: false
- id: global_is_timer_active
type: bool
restore_value: false
- id: global_first_timer
type: voice_assistant::Timer
restore_value: false
- id: global_is_timer
type: bool
restore_value: false
font:
- file: gfonts://Nanum+Gothic+Coding
id: clock_font
size: 160
color:
- id: black
hex: "000000"
- id: white
hex: "FFFFFF"
image:
- file: ${error_illustration_file}
id: kobold_error
type: RGB565
- file: ${listening_illustration_file}
id: kobold_listening
type: RGB565
- file: ${thinking_illustration_file}
id: kobold_thinking
type: RGB565
- file: ${replying_illustration_file}
id: kobold_replying
type: RGB565
- file: ${loading_illustration_file}
id: kobold_initializing
type: RGB565
external_components:
- source:
type: git
url: https://github.com/shelson/esphome-cst9217
##########################
# ____ #
# | _ \ #
# | |_) | __ _ ___ ___ #
# | _ < / _` / __|/ _ \ #
# | |_) | (_| \__ \ __/ #
# |____/ \__,_|___/\___| #
#Base#####################
#time:
# - platform: homeassistant
# id: esptime
# on_time_sync:
# - script.execute: time_update
# on_time:
# - minutes: '*'
# seconds: 0
# then:
# - script.execute: time_update
api:
encryption:
key: "DxR50LKDN8Msy5qOfP5e9C5xStJhLfxbTp+6rVMmfUQ="
ota:
- platform: esphome
password: "bd5f419dd73f955f31027dd98a607df4"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Kobold-Test Fallback Hotspot"
password: "o58qyk1mgr0b"
psram:
mode: octal
speed: 80MHz
i2c:
sda: GPIO15
scl: GPIO14
scan: true
id: bus_a
pca9554:
- id: 'pca9554a_device'
address: 0x18
# Example configuration entry
spi:
- id: spi_bus
clk_pin: GPIO2
mosi_pin: GPIO1
miso_pin:
number: GPIO3
ignore_strapping_warning: true
- id: quad_spi_bus
type: quad
clk_pin: GPIO38
data_pins:
- GPIO4
- GPIO5
- GPIO6
- GPIO7
# Example configuration entry
uart:
tx_pin: GPIO43
rx_pin: GPIO44
baud_rate: 256000
rx_buffer_size: 1024
parity: NONE
stop_bits: 1
# Example configuration entry
ld2410:
binary_sensor:
- platform: ld2410
has_target:
name: Presence
has_moving_target:
name: Moving Target
has_still_target:
name: Still Target
out_pin_presence_status:
name: Out pin presence status
sensor:
- platform: ld2410
light:
name: Light
moving_distance:
name: Moving Distance
still_distance:
name: Still Distance
moving_energy:
name: Move Energy
still_energy:
name: Still Energy
detection_distance:
name: Detection Distance
g0:
move_energy:
name: G0 move energy
still_energy:
name: G0 still energy
g1:
move_energy:
name: G1 move energy
still_energy:
name: G1 still energy
g2:
move_energy:
name: G2 move energy
still_energy:
name: G2 still energy
g3:
move_energy:
name: G3 move energy
still_energy:
name: G3 still energy
g4:
move_energy:
name: G4 move energy
still_energy:
name: G4 still energy
g5:
move_energy:
name: G5 move energy
still_energy:
name: G5 still energy
g6:
move_energy:
name: G6 move energy
still_energy:
name: G6 still energy
g7:
move_energy:
name: G7 move energy
still_energy:
name: G7 still energy
g8:
move_energy:
name: G8 move energy
still_energy:
name: G8 still energy
- platform: apds9960
type: CLEAR
name: "APDS9960 Clear Channel"
number:
- platform: ld2410
timeout:
name: Timeout
light_threshold:
name: Light threshold
max_move_distance_gate:
name: Max move distance gate
max_still_distance_gate:
name: Max still distance gate
g0:
move_threshold:
name: G0 move threshold
still_threshold:
name: G0 still threshold
g1:
move_threshold:
name: G1 move threshold
still_threshold:
name: G1 still threshold
g2:
move_threshold:
name: G2 move threshold
still_threshold:
name: G2 still threshold
g3:
move_threshold:
name: G3 move threshold
still_threshold:
name: G3 still threshold
g4:
move_threshold:
name: G4 move threshold
still_threshold:
name: G4 still threshold
g5:
move_threshold:
name: G5 move threshold
still_threshold:
name: G5 still threshold
g6:
move_threshold:
name: G6 move threshold
still_threshold:
name: G6 still threshold
g7:
move_threshold:
name: G7 move threshold
still_threshold:
name: G7 still threshold
g8:
move_threshold:
name: G8 move threshold
still_threshold:
name: G8 still threshold
apds9960:
address: 0x39
update_interval: 10s
ambient_light_gain: 64x
gesture_gain: 1x
gesture_led_drive: 100ma
gesture_wait_time: 39.2ms
select:
- platform: template
entity_category: config
name: Wake word engine location
id: wake_word_engine_location
icon: "mdi:account-voice"
optimistic: true
restore_value: true
options:
- In Home Assistant
- On device
initial_option: On device
on_value:
- if:
condition:
lambda: return !id(init_in_progress);
then:
- 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
- if:
condition:
switch.is_off: mute
then:
- micro_wake_word.start
#################################
# _ _ #
# /\ | (_) #
# / \ _ _ __| |_ ___ #
# / /\ \| | | |/ _` | |/ _ \ #
# / ____ \ |_| | (_| | | (_) | #
# /_/ \_\__,_|\__,_|_|\___/ #
#Audio###########################
i2s_audio:
- id: i2s_audio_bus
i2s_mclk_pin: GPIO42
i2s_bclk_pin: GPIO9
i2s_lrclk_pin:
number: GPIO45
ignore_strapping_warning: true
audio_adc:
- platform: es7210
id: es7210_adc
bits_per_sample: $i2s_bps_mic
sample_rate: $i2s_sample_rate_mic
audio_dac:
- platform: es8311
id: es8311_dac
bits_per_sample: $i2s_bps_spk
sample_rate: $i2s_sample_rate_spk
microphone:
- platform: i2s_audio
id: box_mic
sample_rate: $i2s_sample_rate_mic
i2s_din_pin: GPIO10
bits_per_sample: $i2s_bps_mic
adc_type: external
speaker:
- platform: i2s_audio
id: box_speaker
i2s_dout_pin: GPIO8
dac_type: external
sample_rate: $i2s_sample_rate_spk
bits_per_sample: $i2s_bps_spk
audio_dac: es8311_dac
buffer_duration: 90ms
use_apll: true
media_player:
- platform: speaker
name: player
id: speaker_media_player
volume_max: 80%
announcement_pipeline:
speaker: box_speaker
format: FLAC
on_announcement:
- if:
condition:
- microphone.is_capturing:
then:
- script.execute: stop_wake_word
# Ensure VA stops before moving on
- if:
condition:
- lambda: return id(wake_word_engine_location).state == "In Home Assistant";
then:
- wait_until:
- not:
voice_assistant.is_running:
- if:
condition:
not:
voice_assistant.is_running:
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
- logger.log: "WEH WEH!"
on_idle:
# Since VA isn't running, this is the end of user-intiated media playback. Restart the wake word.
- if:
condition:
- and:
- not:
voice_assistant.is_running:
- lambda: return id(voice_assistant_phase) != ${voice_assist_idle_phase_id};
then:
- script.execute: start_wake_word
- script.execute: set_idle_or_mute_phase
######################################
# _____ _ _ #
# | __ \(_) | | #
# | | | |_ ___ _ __ | | __ _ _ _ #
# | | | | / __| '_ \| |/ _` | | | | #
# | |__| | \__ \ |_) | | (_| | |_| | #
# |_____/|_|___/ .__/|_|\__,_|\__, | #
# | | __/ | #
# |_| |___/ #
#Display##############################
lvgl:
id: voice_assistant_display
buffer_size: 25%
# animations:
# -
# - id: text_fade_out
# duration: 1s
# timing:
# - type: ease_in_out
# weight: 1.0
# widgets:
# - id: lbl_time
# text_color:
# from: white
# to: black
# - id: text_fade_in
# duration: 1s
# timing:
# - type: ease_in_out
# weight: 1.0
# widgets:
# - id: lbl_time
# text_color:
# from: black
# to: white
pages:
- id: image_page
bg_color: black
widgets:
- image:
id: current_image
align: CENTER
src: kobold_initializing
- id: clock_page
bg_color: black
# widgets:
# - label:
# id: lbl_time
# text: "00:00"
# align: CENTER
# text_align: CENTER
# text_font: clock_font
# text_color: 0xffffff
# pad_left: 22
# outline_color: black
display:
- platform: mipi_spi
id: disp1
model: CO5300
bus_mode: quad
reset_pin: GPIO39
cs_pin: GPIO12
data_rate: 80MHz
dimensions:
height: 466
width: 466
offset_width: 6
light:
- platform: monochromatic
id: display_backlight
name: "Backlight"
output: backlight_brightness
default_transition_length:
milliseconds: 0
initial_state:
brightness: 81%
restore_mode:
ALWAYS_ON
output:
- platform: template
id: backlight_brightness
type: float
write_action:
then:
- lambda: |-
id(disp1).set_brightness(state*255);
touchscreen:
- platform: cst9217
display: disp1
id: ts_disp1
interrupt_pin: GPIO11
reset_pin: GPIO40
transform:
mirror_x: true
mirror_y: true
on_update:
- lambda: |-
for (auto touch: touches) {
if (touch.state <= 2) {
ESP_LOGI("Touch points:", "id=%d x=%d, y=%d", touch.id, touch.x, touch.y);
}
}
############################################
# _____ _ _ #
# |_ _| | | | | #
# | | _ __ | |_ ___ _ __ __ _ ___| |_ #
# | | | '_ \| __/ _ \ '__/ _` |/ __| __| #
# _| |_| | | | || __/ | | (_| | (__| |_ #
# |_____|_| |_|\__\___|_| \__,_|\___|\__| #
#Interact###################################
button:
- platform: restart
name: "Living Room Restart"
switch:
- platform: ld2410
engineering_mode:
name: Engineering mode
bluetooth:
name: Control bluetooth
- platform: gpio
name: "Speaker Enable"
pin:
number: GPIO46
ignore_strapping_warning: true
restore_mode: RESTORE_DEFAULT_ON
- platform: template
name: Mute
id: mute
icon: "mdi:microphone-off"
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
entity_category: config
on_turn_off:
- microphone.unmute:
- lambda: id(voice_assistant_phase) = ${voice_assist_idle_phase_id};
- logger.log: "Calling draw_display from switch on_turn_off"
on_turn_on:
- microphone.mute:
- lambda: id(voice_assistant_phase) = ${voice_assist_muted_phase_id};
- logger.log: "Calling draw_display from switch on_turn_on"
micro_wake_word:
id: mww
models:
- model: https://raw.githubusercontent.com/YOUR-WORST-TACO/CustomWakeupWords/refs/heads/main/models/hey_nokari/hey_nokari.json
id: hey_nokari
# probability_cutoff: 0.98
# sliding_window_size: 5
- model: https://raw.githubusercontent.com/YOUR-WORST-TACO/CustomWakeupWords/refs/heads/main/models/nokari/nokari.json
id: nokari
probability_cutoff: 0.98
sliding_window_size: 5
on_wake_word_detected:
- voice_assistant.start:
wake_word: !lambda return wake_word;
voice_assistant:
id: va
microphone: box_mic
media_player: speaker_media_player
micro_wake_word: mww
noise_suppression_level: 2
auto_gain: 31dBFS
volume_multiplier: 2.0
on_listening:
- if:
condition:
lambda: return id(voice_assistant_phase) != ${voice_assist_listening_phase_id};
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_listening_phase_id};
- logger.log: "Calling draw_display from voice_assistant on_listening"
- script.execute: draw_display
- text_sensor.template.publish:
id: text_request
state: "..."
- text_sensor.template.publish:
id: text_response
state: "..."
on_stt_vad_end:
- lambda: id(voice_assistant_phase) = ${voice_assist_thinking_phase_id};
- logger.log: "Calling draw_display from voice_assistant on_stt_vad_end"
- script.execute: draw_display
on_stt_end:
- text_sensor.template.publish:
id: text_request
state: !lambda return x;
- logger.log: "Calling draw_display from voice_assistant on_stt_end"
- script.execute: draw_display
on_tts_start:
- text_sensor.template.publish:
id: text_response
state: !lambda return x;
- lambda: id(voice_assistant_phase) = ${voice_assist_replying_phase_id};
- logger.log: "Calling draw_display from voice_assistant on_tts_start"
- script.execute: draw_display
on_end:
# Wait a short amount of time to see if an announcement starts
- wait_until:
condition:
- media_player.is_announcing:
timeout: 0.5s
# Announcement is finished and the I2S bus is free
- wait_until:
- and:
- not:
media_player.is_announcing:
- not:
speaker.is_playing:
# Restart only mWW if enabled; streaming wake words automatically restart
- if:
condition:
- lambda: return id(wake_word_engine_location).state == "On device";
then:
- lambda: id(va).set_use_wake_word(false);
- micro_wake_word.start:
- script.execute: set_idle_or_mute_phase
- logger.log: "Calling draw_display from voice_assistant on_end"
- script.execute: draw_display
# Clear text sensors
- text_sensor.template.publish:
id: text_request
state: ""
- text_sensor.template.publish:
id: text_response
state: ""
on_error:
- if:
condition:
lambda: return !id(init_in_progress);
then:
- lambda: id(voice_assistant_phase) = ${voice_assist_error_phase_id};
- logger.log: "Calling draw_display from voice_assistant on_error"
- 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};
- logger.log: "Calling draw_display from voice_assistant on_error"
- script.execute: draw_display
on_client_connected:
- lambda: id(init_in_progress) = false;
- script.execute: start_wake_word
- script.execute: set_idle_or_mute_phase
- logger.log: "Calling draw_display from voice_assistant on_client_connected"
- script.execute: draw_display
on_client_disconnected:
- script.execute: stop_wake_word
- lambda: id(voice_assistant_phase) = ${voice_assist_not_ready_phase_id};
- logger.log: "Calling draw_display from voice_assistant on_client_disconnected"
- script.execute: draw_display
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();
}
mapping:
- id: image_mapping
from: int
to: image
entries:
${voice_assist_listening_phase_id}: kobold_listening
${voice_assist_thinking_phase_id}: kobold_thinking
${voice_assist_replying_phase_id}: kobold_replying
${voice_assist_not_ready_phase_id}: kobold_error
${voice_assist_error_phase_id}: kobold_error
${voice_assist_muted_phase_id}: kobold_error
script:
# - id: time_update
# then:
# - lvgl.animation.start: text_fade_out
# - delay: 1s
# - lvgl.label.update:
# id: lbl_time
# text: !lambda |-
# return id(esptime).now().strftime("%H:%M");
# - lvgl.animation.start: text_fade_in
# - logger.log: "update Time script ran"
- id: draw_display
then:
- logger.log: "Drawing display"
- if:
condition:
lambda: return !id(init_in_progress);
then:
- if:
condition:
wifi.connected:
then:
- if:
condition:
api.connected:
then:
- if:
condition:
lambda: return id(voice_assistant_phase) == ${voice_assist_idle_phase_id};
then:
- lvgl.page.show:
id: clock_page
animation: OUT_BOTTOM
time: 500ms
else:
- lvgl.page.show:
id: image_page
animation: OUT_TOP
time: 500ms
- lvgl.image.update:
id: current_image
src: !lambda return id(image_mapping)[id(voice_assistant_phase)];
else:
- lvgl.page.show:
id: image_page
animation: OUT_TOP
time: 500ms
- lvgl.image.update:
id: current_image
src: kobold_error
else:
- lvgl.page.show:
id: image_page
animation: OUT_TOP
time: 500ms
- lvgl.image.update:
id: current_image
src: kobold_error
else:
- lvgl.page.show:
id: image_page
animation: OUT_TOP
time: 500ms
- lvgl.image.update:
id: current_image
src: kobold_initializing
# Starts either mWW or the streaming wake word, depending on the configured location
- id: start_wake_word
then:
- if:
condition:
and:
- not:
- voice_assistant.is_running:
- lambda: return id(wake_word_engine_location).state == "On device";
then:
- lambda: id(va).set_use_wake_word(false);
- micro_wake_word.start:
- if:
condition:
and:
- not:
- voice_assistant.is_running:
- lambda: return id(wake_word_engine_location).state == "In Home Assistant";
then:
- lambda: id(va).set_use_wake_word(true);
- voice_assistant.start_continuous:
# Stops either mWW or the streaming wake word, depending on the configured location
- id: stop_wake_word
then:
- 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:
# Set the voice assistant phase to idle or muted, depending on if the software mute switch is activated
- id: set_idle_or_mute_phase
then:
- 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};