Fix I2C scan on Waveshare RLCD board using SoftI2C and implement button PTT trigger fallback when touch is missing
This commit is contained in:
@@ -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('<HHIIHH', fmt_chunk[8:24])
|
||||
print(f"Playing response WAV: {sample_rate}Hz, {channels}ch, {bits}bit")
|
||||
# Parse response WAV header
|
||||
if len(response_data) >= 44 and response_data[0:4] == b'RIFF' and response_data[8:12] == b'WAVE':
|
||||
draw_status_bar("Playing response...")
|
||||
|
||||
data_idx = response_data.find(b'data')
|
||||
if data_idx != -1:
|
||||
audio_start = data_idx + 8
|
||||
# Parse channel count, sample rate, bits
|
||||
import struct
|
||||
fmt_chunk_size = struct.unpack('<I', response_data[16:20])[0]
|
||||
channels, sample_rate, byte_rate, block_align, bits = struct.unpack('<HHIIH', response_data[22:34])
|
||||
|
||||
draw_status_bar("Speaking...")
|
||||
audio_start = 20 + fmt_chunk_size
|
||||
while audio_start < len(response_data) - 8:
|
||||
if response_data[audio_start:audio_start+4] == b'data':
|
||||
audio_start += 8
|
||||
break
|
||||
chunk_size = struct.unpack('<I', response_data[audio_start+4:audio_start+8])[0]
|
||||
audio_start += 8 + chunk_size
|
||||
|
||||
# Play response
|
||||
amp_pin.value(0) # Enable Amp (Active Low)
|
||||
i2s_format = I2S.MONO if channels == 1 else I2S.STEREO
|
||||
i2s_tx = I2S(1,
|
||||
sck=Pin(5),
|
||||
ws=Pin(7),
|
||||
sd=Pin(8),
|
||||
mode=I2S.TX,
|
||||
ibuf=4096,
|
||||
rate=sample_rate,
|
||||
bits=bits,
|
||||
format=i2s_format)
|
||||
try:
|
||||
pos = audio_start
|
||||
chunk_size = 2048
|
||||
while pos < len(response_data):
|
||||
chunk = response_data[pos:pos+chunk_size]
|
||||
i2s_tx.write(chunk)
|
||||
pos += chunk_size
|
||||
finally:
|
||||
time.sleep_ms(150)
|
||||
amp_pin.value(1) # Disable Amp
|
||||
i2s_tx.deinit()
|
||||
if audio_start < len(response_data):
|
||||
# Play response using board_config parameters
|
||||
on_val = 0 if board_config.audio_amp_active_level == 0 else 1
|
||||
off_val = 1 if board_config.audio_amp_active_level == 0 else 0
|
||||
amp_pin.value(on_val) # Enable Amp
|
||||
|
||||
i2s_format = I2S.MONO if channels == 1 else I2S.STEREO
|
||||
i2s_tx = I2S(1,
|
||||
sck=Pin(board_config.audio_i2s_sck),
|
||||
ws=Pin(board_config.audio_i2s_ws),
|
||||
sd=Pin(board_config.audio_i2s_tx_sd),
|
||||
mode=I2S.TX,
|
||||
ibuf=4096,
|
||||
rate=sample_rate,
|
||||
bits=bits,
|
||||
format=i2s_format)
|
||||
try:
|
||||
pos = audio_start
|
||||
chunk_size = 2048
|
||||
while pos < len(response_data):
|
||||
chunk = response_data[pos:pos+chunk_size]
|
||||
i2s_tx.write(chunk)
|
||||
pos += chunk_size
|
||||
finally:
|
||||
time.sleep_ms(150)
|
||||
amp_pin.value(off_val) # Disable Amp
|
||||
i2s_tx.deinit()
|
||||
else:
|
||||
# MP3 warning message on display
|
||||
draw_status_bar("Error: Gateway returned MP3")
|
||||
time.sleep(3)
|
||||
else:
|
||||
@@ -393,17 +415,22 @@ def main():
|
||||
time.sleep(2)
|
||||
|
||||
# Deinit MCLK PWM
|
||||
mclk_pwm.deinit()
|
||||
if mclk_pwm:
|
||||
try:
|
||||
mclk_pwm.deinit()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Debounce touch release at the very end of wizard
|
||||
# Debounce release at the very end of wizard
|
||||
clear_status_bar()
|
||||
release_end = time.ticks_ms()
|
||||
while touch.is_touched():
|
||||
while is_talk_trigger_active():
|
||||
if time.ticks_diff(time.ticks_ms(), release_end) > 2000:
|
||||
break
|
||||
time.sleep_ms(30)
|
||||
time.sleep_ms(200)
|
||||
|
||||
voice_assistant_active = False
|
||||
last_action_str = "Voice query finished"
|
||||
force_dashboard_redraw = True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user