Last active
September 6, 2025 09:17
-
-
Save MarkWattTech/4c248cee42b32d1c5f84091256cc3279 to your computer and use it in GitHub Desktop.
ESPHome Code For Onju Voice
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| substitutions: | |
| name: "onju-voice2" | |
| friendly_name: "Onju Voice 2" | |
| wifi_ap_password: "password" | |
| esphome: | |
| name: ${name} | |
| friendly_name: ${friendly_name} | |
| name_add_mac_suffix: false | |
| min_version: 2023.10.1 | |
| on_boot: | |
| then: | |
| - light.turn_on: | |
| id: top_led | |
| effect: slow_pulse | |
| red: 100% | |
| green: 60% | |
| blue: 0% | |
| - wait_until: | |
| condition: | |
| wifi.connected: | |
| - light.turn_on: | |
| id: top_led | |
| effect: pulse | |
| red: 0% | |
| green: 100% | |
| blue: 0% | |
| - wait_until: | |
| condition: | |
| api.connected: | |
| - light.turn_on: | |
| id: top_led | |
| effect: none | |
| red: 0% | |
| green: 100% | |
| blue: 0% | |
| - delay: 1s | |
| - script.execute: reset_led | |
| esp32: | |
| board: esp32-s3-devkitc-1 | |
| framework: | |
| type: arduino | |
| logger: | |
| api: | |
| encryption: | |
| key: "CHANGE ME" | |
| services: | |
| - service: start_va | |
| then: | |
| - voice_assistant.start | |
| - service: stop_va | |
| then: | |
| - voice_assistant.stop | |
| ota: | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| ap: | |
| password: "${wifi_ap_password}" | |
| captive_portal: | |
| globals: | |
| - id: thresh_percent | |
| type: float | |
| initial_value: "0.03" | |
| restore_value: false | |
| - id: touch_calibration_values_left | |
| type: uint32_t[5] | |
| restore_value: false | |
| - id: touch_calibration_values_center | |
| type: uint32_t[5] | |
| restore_value: false | |
| - id: touch_calibration_values_right | |
| type: uint32_t[5] | |
| restore_value: false | |
| interval: | |
| - interval: 1s | |
| then: | |
| - script.execute: | |
| id: calibrate_touch | |
| button: 0 | |
| - script.execute: | |
| id: calibrate_touch | |
| button: 1 | |
| - script.execute: | |
| id: calibrate_touch | |
| button: 2 | |
| i2s_audio: | |
| - i2s_lrclk_pin: GPIO13 | |
| i2s_bclk_pin: GPIO18 | |
| media_player: | |
| - platform: i2s_audio | |
| name: None | |
| id: onju_out | |
| dac_type: external | |
| i2s_dout_pin: GPIO12 | |
| mode: mono | |
| mute_pin: | |
| number: GPIO21 | |
| inverted: True | |
| ###### | |
| # speaker: | |
| # - platform: i2s_audio | |
| # id: onju_out | |
| # dac_type: external | |
| # i2s_dout_pin: GPIO12 | |
| # mode: stereo | |
| ###### | |
| microphone: | |
| - platform: i2s_audio | |
| id: onju_microphone | |
| i2s_din_pin: GPIO17 | |
| adc_type: external | |
| pdm: false | |
| voice_assistant: | |
| id: va | |
| microphone: onju_microphone | |
| media_player: onju_out | |
| ###### | |
| # speaker: onju_out | |
| ###### | |
| use_wake_word: true | |
| on_start: | |
| - light.turn_on: | |
| id: top_led | |
| blue: 100% | |
| red: 0% | |
| green: 0% | |
| effect: none | |
| on_listening: | |
| - light.turn_on: | |
| id: top_led | |
| blue: 100% | |
| red: 0% | |
| green: 0% | |
| brightness: 100% | |
| effect: pulse | |
| on_tts_end: | |
| - media_player.play_media: !lambda return x; | |
| - light.turn_on: | |
| id: top_led | |
| blue: 0% | |
| red: 20% | |
| green: 100% | |
| effect: pulse | |
| on_end: | |
| - delay: 100ms | |
| - wait_until: | |
| not: | |
| media_player.is_playing: onju_out | |
| - script.execute: reset_led | |
| on_client_connected: | |
| - if: | |
| condition: | |
| and: | |
| - switch.is_on: use_wake_word | |
| - binary_sensor.is_off: mute_switch | |
| then: | |
| - voice_assistant.start_continuous: | |
| on_client_disconnected: | |
| - if: | |
| condition: | |
| and: | |
| - switch.is_on: use_wake_word | |
| - binary_sensor.is_off: mute_switch | |
| then: | |
| - voice_assistant.stop: | |
| on_error: | |
| - light.turn_on: | |
| id: top_led | |
| blue: 0% | |
| red: 100% | |
| green: 0% | |
| - delay: 1s | |
| - script.execute: reset_led | |
| number: | |
| - platform: template | |
| name: "Touch threshold percentage" | |
| id: touch_threshold_percentage | |
| update_interval: never | |
| entity_category: config | |
| initial_value: 1.25 | |
| min_value: -1 | |
| max_value: 5 | |
| step: 0.25 | |
| optimistic: true | |
| on_value: | |
| then: | |
| - lambda: !lambda |- | |
| id(thresh_percent) = 0.01 * x; | |
| esp32_touch: | |
| setup_mode: false | |
| sleep_duration: 2ms | |
| measurement_duration: 800us | |
| low_voltage_reference: 0.8V | |
| high_voltage_reference: 2.4V | |
| filter_mode: IIR_16 | |
| debounce_count: 2 | |
| noise_threshold: 0 | |
| jitter_step: 0 | |
| smooth_mode: IIR_2 | |
| denoise_grade: BIT8 | |
| denoise_cap_level: L0 | |
| binary_sensor: | |
| - platform: esp32_touch | |
| id: volume_down | |
| pin: GPIO4 | |
| threshold: 539000 # 533156-551132 | |
| on_press: | |
| then: | |
| - light.turn_on: left_led | |
| - script.execute: | |
| id: set_volume | |
| volume: -0.05 | |
| - delay: 1s | |
| - while: | |
| condition: | |
| binary_sensor.is_on: volume_down | |
| then: | |
| - script.execute: | |
| id: set_volume | |
| volume: -0.05 | |
| - delay: 150ms | |
| on_release: | |
| then: | |
| - light.turn_off: left_led | |
| - platform: esp32_touch | |
| id: volume_up | |
| pin: GPIO2 | |
| threshold: 580000 # 575735-593064 | |
| on_press: | |
| then: | |
| - light.turn_on: right_led | |
| - script.execute: | |
| id: set_volume | |
| volume: 0.05 | |
| - delay: 1s | |
| - while: | |
| condition: | |
| binary_sensor.is_on: volume_up | |
| then: | |
| - script.execute: | |
| id: set_volume | |
| volume: 0.05 | |
| - delay: 150ms | |
| on_release: | |
| then: | |
| - light.turn_off: right_led | |
| - platform: esp32_touch | |
| id: action | |
| pin: GPIO3 | |
| threshold: 751000 # 745618-767100 | |
| on_click: | |
| - if: | |
| condition: | |
| or: | |
| - switch.is_off: use_wake_word | |
| - binary_sensor.is_on: mute_switch | |
| 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: | |
| - platform: gpio | |
| id: mute_switch | |
| pin: | |
| number: GPIO38 | |
| mode: INPUT_PULLUP | |
| name: Disable wake word | |
| on_press: | |
| - script.execute: turn_on_wake_word | |
| on_release: | |
| - script.execute: turn_off_wake_word | |
| - platform: status | |
| id: api_connection | |
| filters: | |
| - delayed_on: 1s | |
| on_press: | |
| - if: | |
| condition: | |
| and: | |
| - switch.is_on: use_wake_word | |
| - binary_sensor.is_off: mute_switch | |
| then: | |
| - voice_assistant.start_continuous: | |
| on_release: | |
| - if: | |
| condition: | |
| and: | |
| - switch.is_on: use_wake_word | |
| - binary_sensor.is_off: mute_switch | |
| then: | |
| - voice_assistant.stop: | |
| light: | |
| - platform: esp32_rmt_led_strip | |
| id: leds | |
| pin: GPIO11 | |
| chipset: SK6812 | |
| num_leds: 6 | |
| rgb_order: grb | |
| rmt_channel: 0 | |
| default_transition_length: 0s | |
| gamma_correct: 2.8 | |
| - platform: partition | |
| id: left_led | |
| segments: | |
| - id: leds | |
| from: 0 | |
| to: 0 | |
| - platform: partition | |
| id: top_led | |
| segments: | |
| - id: leds | |
| from: 1 | |
| to: 4 | |
| effects: | |
| - pulse: | |
| name: pulse | |
| transition_length: 250ms | |
| update_interval: 250ms | |
| - pulse: | |
| name: slow_pulse | |
| transition_length: 1s | |
| update_interval: 2s | |
| - addressable_lambda: | |
| name: show_volume | |
| update_interval: 50ms | |
| lambda: |- | |
| int int_volume = int(id(onju_out).volume * 100.0f * it.size()); | |
| int full_leds = int_volume / 100; | |
| int last_brightness = int_volume % 100; | |
| int i = 0; | |
| for(; i < full_leds; i++) { | |
| it[i] = Color::WHITE; | |
| } | |
| if(i < 4) { | |
| it[i++] = Color(0,0,0).fade_to_white(last_brightness*256/100); | |
| } | |
| for(; i < it.size(); i++) { | |
| it[i] = Color::BLACK; | |
| } | |
| - platform: partition | |
| id: right_led | |
| segments: | |
| - id: leds | |
| from: 5 | |
| to: 5 | |
| script: | |
| - id: reset_led | |
| then: | |
| - if: | |
| condition: | |
| and: | |
| - switch.is_on: use_wake_word | |
| - binary_sensor.is_off: mute_switch | |
| then: | |
| - light.turn_on: | |
| id: top_led | |
| blue: 100% | |
| red: 100% | |
| green: 0% | |
| brightness: 100% | |
| effect: none | |
| else: | |
| - light.turn_off: top_led | |
| - id: set_volume | |
| mode: restart | |
| parameters: | |
| volume: float | |
| then: | |
| - media_player.volume_set: | |
| id: onju_out | |
| volume: !lambda return clamp(id(onju_out).volume+volume, 0.0f, 1.0f); | |
| - light.turn_on: | |
| id: top_led | |
| effect: show_volume | |
| - delay: 1s | |
| - script.execute: reset_led | |
| - id: turn_on_wake_word | |
| then: | |
| - if: | |
| condition: | |
| and: | |
| - binary_sensor.is_off: mute_switch | |
| - switch.is_on: use_wake_word | |
| then: | |
| - 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: turn_off_wake_word | |
| then: | |
| - voice_assistant.stop | |
| - lambda: id(va).set_use_wake_word(false); | |
| - script.execute: reset_led | |
| - id: calibrate_touch | |
| parameters: | |
| button: int | |
| then: | |
| - lambda: |- | |
| static byte thresh_indices[3] = {0, 0, 0}; | |
| static uint32_t sums[3] = {0, 0, 0}; | |
| static byte qsizes[3] = {0, 0, 0}; | |
| static int consecutive_anomalies_per_button[3] = {0, 0, 0}; | |
| uint32_t newval; | |
| uint32_t* calibration_values; | |
| switch(button) { | |
| case 0: | |
| newval = id(volume_down).get_value(); | |
| calibration_values = id(touch_calibration_values_left); | |
| break; | |
| case 1: | |
| newval = id(action).get_value(); | |
| calibration_values = id(touch_calibration_values_center); | |
| break; | |
| case 2: | |
| newval = id(volume_up).get_value(); | |
| calibration_values = id(touch_calibration_values_right); | |
| break; | |
| default: | |
| ESP_LOGE("touch_calibration", "Invalid button ID (%d)", button); | |
| return; | |
| } | |
| if(newval == 0) return; | |
| //ESP_LOGD("touch_calibration", "[%d] qsize %d, sum %d, thresh_index %d, consecutive_anomalies %d", button, qsizes[button], sums[button], thresh_indices[button], consecutive_anomalies_per_button[button]); | |
| //ESP_LOGD("touch_calibration", "[%d] New value is %d", button, newval); | |
| if(qsizes[button] == 5) { | |
| float avg = float(sums[button])/float(qsizes[button]); | |
| if((fabs(float(newval)-avg)/avg) > id(thresh_percent)) { | |
| consecutive_anomalies_per_button[button]++; | |
| //ESP_LOGD("touch_calibration", "[%d] %d anomalies detected.", button, consecutive_anomalies_per_button[button]); | |
| if(consecutive_anomalies_per_button[button] < 10) | |
| return; | |
| } | |
| } | |
| //ESP_LOGD("touch_calibration", "[%d] Resetting consecutive anomalies counter.", button); | |
| consecutive_anomalies_per_button[button] = 0; | |
| if(qsizes[button] == 5) { | |
| //ESP_LOGD("touch_calibration", "[%d] Queue full, removing %d.", button, id(touch_calibration_values)[thresh_indices[button]]); | |
| sums[button] -= (uint32_t) *(calibration_values+thresh_indices[button]);// id(touch_calibration_values)[thresh_indices[button]]; | |
| qsizes[button]--; | |
| } | |
| *(calibration_values+thresh_indices[button]) = newval; | |
| sums[button] += newval; | |
| qsizes[button]++; | |
| thresh_indices[button] = (thresh_indices[button] + 1) % 5; | |
| //ESP_LOGD("touch_calibration", "[%d] Average value is %d", button, sums[button]/qsizes[button]); | |
| uint32_t newthresh = uint32_t((sums[button]/qsizes[button]) * (1.0 + id(thresh_percent))); | |
| //ESP_LOGD("touch_calibration", "[%d] Setting threshold %d", button, newthresh); | |
| switch(button) { | |
| case 0: | |
| id(volume_down).set_threshold(newthresh); | |
| break; | |
| case 1: | |
| id(action).set_threshold(newthresh); | |
| break; | |
| case 2: | |
| id(volume_up).set_threshold(newthresh); | |
| break; | |
| default: | |
| ESP_LOGE("touch_calibration", "Invalid button ID (%d)", button); | |
| return; | |
| } | |
| switch: | |
| - platform: template | |
| name: Use Wake Word | |
| id: use_wake_word | |
| optimistic: true | |
| restore_mode: RESTORE_DEFAULT_ON | |
| on_turn_on: | |
| - script.execute: turn_on_wake_word | |
| on_turn_off: | |
| - script.execute: turn_off_wake_word |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment