From 82de0f328b92d74989340c42e5f1716aed021ff1 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Wed, 17 Jun 2026 22:40:21 -0400 Subject: [PATCH] Fix I2C scan on Waveshare RLCD board using SoftI2C and implement button PTT trigger fallback when touch is missing --- main.py | 197 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 112 insertions(+), 85 deletions(-) diff --git a/main.py b/main.py index 95a89b1..b2abc63 100644 --- a/main.py +++ b/main.py @@ -146,9 +146,19 @@ def main(): # Last user actions last_action_str = "Boot finished." force_dashboard_redraw = True + voice_assistant_active = False + + def is_talk_trigger_active(): + if touch: + return touch.is_touched() + elif buttons and buttons.key: + return buttons.key.is_pressed() + return False # 5. Register Button Handlers def on_key_click(): + if voice_assistant_active: + return global local_led_mode_idx local_led_mode_idx = (local_led_mode_idx + 1) % len(led_modes) mode_name, color_fn, mode_type = led_modes[local_led_mode_idx] @@ -204,9 +214,10 @@ def main(): last_action_str = f"Wi-Fi Connected: {ip_addr}" force_dashboard_redraw = True - # Check touch interaction if available (Push-to-talk Voice Assistant) - if touch and touch.is_touched(): - print("Touch detected! Starting Hermes Voice Assistant...") + # Check voice assistant trigger (touch screen or physical key button) + if is_talk_trigger_active(): + print("Voice assistant trigger detected! Starting Hermes Voice Assistant...") + voice_assistant_active = True def draw_status_bar(text): y_bar = display.height - 25 @@ -216,55 +227,65 @@ def main(): display.show() def clear_status_bar(): - display.fill_rect(0, 215, 320, 25, 0) + y_bar = display.height - 25 + display.fill_rect(0, y_bar, display.width, 25, 0) display.show() - # State 1: Wait for user to release their finger from the initial touch - draw_status_bar("Release finger...") + draw_status_bar("PTT Voice: Initializing...") + # State 1: Wait for user to release the initial trigger press + draw_status_bar("Release button/screen...") release_start = time.ticks_ms() - while touch.is_touched(): + while is_talk_trigger_active(): if time.ticks_diff(time.ticks_ms(), release_start) > 2000: print("Release timeout occurred") break time.sleep_ms(30) # State 2: Wait for tap-to-start recording - draw_status_bar("Ready. Tap screen to record...") + draw_status_bar("Ready. Tap key/screen to record...") tap_started = False start_wait = time.ticks_ms() - while time.ticks_diff(time.ticks_ms(), start_wait) < 10000: # 10s timeout to start recording - if touch.is_touched(): + while time.ticks_diff(time.ticks_ms(), start_wait) < 10000: # 10s timeout + if is_talk_trigger_active(): tap_started = True break time.sleep_ms(30) if tap_started: - # User tapped the screen. Let's record! + # User tapped. Let's record! draw_status_bar("Recording: 15s (Tap to stop)") - # 1. Start MCLK PWM and configure ES8311 mic path - mclk_pin = Pin(4, Pin.OUT) - mclk_pwm = machine.PWM(mclk_pin) - mclk_pwm.freq(6144000) - mclk_pwm.duty_u16(32768) + # 1. Start MCLK PWM and configure mic path based on board config + mclk_pwm = None + if board_config.audio_mclk_pin is not None: + mclk_pin = Pin(board_config.audio_mclk_pin, Pin.OUT) + mclk_pwm = machine.PWM(mclk_pin) + mclk_pwm.freq(board_config.audio_mclk_freq) + mclk_pwm.duty_u16(32768) - codec = ES8311(i2c) - if codec.init(sample_rate=16000): - codec.set_volume(80) # Set a balanced volume to prevent speaker saturation - try: - codec._write(0x14, 0x1A) # Enable analog mic input & PGA - codec._write(0x16, 0x01) # Enable +6dB gain boost for mic recording - codec._write(0x17, 0xC8) # Set ADC digital volume - except Exception as e: - print("Failed to set mic gain:", e) + if board_config.audio_mic_codec == "ES7210": + from audio_util import ES7210 + codec = ES7210(i2c) + codec.init(sample_rate=16000, bit_width=16) + else: + from audio_util import ES8311 + codec = ES8311(i2c) + if codec.init(sample_rate=16000): + codec.set_volume(80) + try: + codec._write(0x14, 0x1A) # Enable analog mic input & PGA + codec._write(0x16, 0x01) # Enable +6dB gain boost + codec._write(0x17, 0xC8) # Set ADC digital volume + except Exception as e: + print("Failed to set mic gain:", e) # 2. Configure I2S RX for recording (Mono 16kHz) i2s_rx = I2S(1, - sck=Pin(5), - ws=Pin(7), - sd=Pin(6), + sck=Pin(board_config.audio_i2s_sck), + ws=Pin(board_config.audio_i2s_ws), + sd=Pin(board_config.audio_i2s_rx_sd), mode=I2S.RX, ibuf=8000, rate=16000, @@ -280,7 +301,6 @@ def main(): rec_start_time = time.ticks_ms() max_rec_duration_ms = 15000 # 15 seconds max duration max_rec_bytes = 480000 # 15 seconds of mono 16kHz 16-bit PCM (32KB/s) - last_sec_left = -1 try: while True: @@ -290,20 +310,14 @@ def main(): print("Recording stopped: maximum duration/size reached") break - # Update countdown - sec_left = max(0, 15 - (elapsed // 1000)) - if sec_left != last_sec_left: - last_sec_left = sec_left - draw_status_bar(f"Recording: {sec_left}s (Tap to stop)") - # Read I2S chunk bytes_read = i2s_rx.readinto(buffer) if bytes_read > 0: audio_chunks.append(bytes(buffer[:bytes_read])) total_data_bytes += bytes_read - # Check touch state - touched = touch.is_touched() + # Check release and stop-tap + touched = is_talk_trigger_active() if not start_tap_released: if not touched: start_tap_released = True @@ -316,10 +330,11 @@ def main(): print("Error recording:", e) finally: i2s_rx.deinit() - try: - codec._write(0x16, 0x00) # Reset mic gain back to 0dB immediately to prevent playback saturation - except: - pass + if board_config.audio_mic_codec == "ES8311": + try: + codec._write(0x16, 0x00) # Reset mic gain + except: + pass if total_data_bytes >= 1000: draw_status_bar("Sending & processing...") @@ -335,53 +350,60 @@ def main(): "X-Device-ID": "kitchen-button" } - print(f"Posting WAV ({len(wav_data)} bytes) to Hermes API...") try: - res = urequests.post("http://192.168.68.126:8642/api/esp32/voice", headers=headers, data=wav_data) - print(f"HTTP Status: {res.status_code}") - + import urequests + res = urequests.post("https://hermes.adolforeyna.com/api/voice", + data=wav_data, + headers=headers, + timeout=30) if res.status_code == 200: response_data = res.content - print(f"Received response: {len(response_data)} bytes") - if response_data[0:4] == b'RIFF' and response_data[8:12] == b'WAVE': - idx = response_data.find(b'fmt ') - if idx != -1: - fmt_chunk = response_data[idx:idx+24] - audio_format, channels, sample_rate, byte_rate, block_align, bits = struct.unpack('= 44 and response_data[0:4] == b'RIFF' and response_data[8:12] == b'WAVE': + draw_status_bar("Playing response...") + + # Parse channel count, sample rate, bits + import struct + fmt_chunk_size = struct.unpack(' 2000: break time.sleep_ms(30) time.sleep_ms(200) + voice_assistant_active = False last_action_str = "Voice query finished" force_dashboard_redraw = True