This commit is contained in:
jeremygan2021
2026-03-03 23:31:06 +08:00
parent 0aa8f5f473
commit 20d2e72c51
10 changed files with 220 additions and 201 deletions

View File

@@ -210,3 +210,62 @@ class Display:
self.tft.line(x, y + 5, x + 3, y + 8, st7789.GREEN) self.tft.line(x, y + 5, x + 3, y + 8, st7789.GREEN)
self.tft.line(x + 3, y + 8, x + 10, y, st7789.GREEN) self.tft.line(x + 3, y + 8, x + 10, y, st7789.GREEN)
def render_confirm_screen(self, asr_text=""):
"""渲染确认界面"""
if not self.tft:
return
self.tft.fill(st7789.BLACK)
# Header
self.tft.fill_rect(0, 0, 240, 30, st7789.CYAN)
self.text("说完了吗?", 75, 8, st7789.BLACK)
# Content box
self.tft.fill_rect(10, 50, 220, 90, 0x4208) # DARKGREY
if asr_text:
# 自动换行逻辑
max_width = 200
lines = []
current_line = ""
current_width = 0
for char in asr_text:
char_width = 16 if ord(char) > 127 else 8
if current_width + char_width > max_width:
lines.append(current_line)
current_line = char
current_width = char_width
else:
current_line += char
current_width += char_width
if current_line:
lines.append(current_line)
# 限制显示行数,避免溢出
lines = lines[:4]
# 计算起始Y坐标以垂直居中
total_height = len(lines) * 20
start_y = 50 + (90 - total_height) // 2
for i, line in enumerate(lines):
# 计算水平居中
line_width = 0
for c in line:
line_width += 16 if ord(c) > 127 else 8
center_x = 20 + (200 - line_width) // 2
self.text(line, center_x, start_y + i * 20, st7789.WHITE, wait=False)
else:
self.text("未识别到文字", 70, 85, st7789.WHITE)
# Buttons
self.tft.fill_rect(20, 160, 90, 30, st7789.GREEN)
self.text("短按确认", 30, 168, st7789.BLACK)
self.tft.fill_rect(130, 160, 90, 30, st7789.RED)
self.text("长按重录", 140, 168, st7789.WHITE)

View File

@@ -54,9 +54,11 @@ class Font:
d = binascii.unhexlify(parts[2]) d = binascii.unhexlify(parts[2])
self.cache[c] = d self.cache[c] = d
# 清除重试计数(如果有) # 清除重试计数(如果有)和 pending
if c in self.retry_count: if c in self.retry_count:
del self.retry_count[c] del self.retry_count[c]
if c in self.pending_requests:
self.pending_requests.remove(c)
return True return True
except Exception as e: except Exception as e:
print(f"Font data parse error: {e}") print(f"Font data parse error: {e}")
@@ -110,6 +112,11 @@ class Font:
if wait: if wait:
print(f"Batch requesting fonts: {req_str}") print(f"Batch requesting fonts: {req_str}")
try: try:
# Add Pending requests to retry count to avoid spamming
for c in missing_list:
if c not in self.pending_requests:
self.pending_requests.add(c)
self.ws.send(f"GET_FONTS_BATCH:{req_str}") self.ws.send(f"GET_FONTS_BATCH:{req_str}")
if wait: if wait:
self._wait_for_fonts(missing_codes) self._wait_for_fonts(missing_codes)

311
main.py
View File

@@ -28,7 +28,7 @@ UI_SCREEN_RECORDING = 1
UI_SCREEN_CONFIRM = 2 UI_SCREEN_CONFIRM = 2
UI_SCREEN_RESULT = 3 UI_SCREEN_RESULT = 3
BOOT_SHORT_MS = 500 BOOT_SHORT_MS = 100
BOOT_LONG_MS = 2000 BOOT_LONG_MS = 2000
BOOT_EXTRA_LONG_MS = 5000 BOOT_EXTRA_LONG_MS = 5000
@@ -170,7 +170,7 @@ def draw_progress_bar(display, x, y, width, height, progress, color=st7789.CYAN)
display.tft.fill_rect(x, y, bar_width, height, color) display.tft.fill_rect(x, y, bar_width, height, color)
def render_recording_screen(display, asr_text="", audio_level=0): def render_recording_screen(display, asr_text="", audio_level=0, is_recording=False):
"""渲染录音界面""" """渲染录音界面"""
if not display or not display.tft: if not display or not display.tft:
return return
@@ -190,7 +190,10 @@ def render_recording_screen(display, asr_text="", audio_level=0):
display.text(asr_text[:20], 20, 130, st7789.WHITE, wait=False) display.text(asr_text[:20], 20, 130, st7789.WHITE, wait=False)
display.tft.fill_rect(60, 200, 120, 25, st7789.RED) display.tft.fill_rect(60, 200, 120, 25, st7789.RED)
display.text("松开停止", 85, 205, st7789.WHITE) if is_recording:
display.text("松开停止", 85, 205, st7789.WHITE)
else:
display.text("长按录音", 85, 205, st7789.WHITE)
def render_confirm_screen(display, asr_text=""): def render_confirm_screen(display, asr_text=""):
@@ -251,14 +254,14 @@ def render_result_screen(display, status="", prompt="", image_received=False):
display.text("AI 生成中", 80, 8, st7789.BLACK) display.text("AI 生成中", 80, 8, st7789.BLACK)
display.text("生成失败", 80, 50, st7789.RED) display.text("生成失败", 80, 50, st7789.RED)
if prompt and not image_received and not image_generation_done: if prompt and not image_received:
display.tft.fill_rect(10, 140, 220, 50, 0x2124) # Dark Grey display.tft.fill_rect(10, 140, 220, 50, 0x2124) # Dark Grey
display.text("提示词:", 15, 145, st7789.CYAN) display.text("提示词:", 15, 145, st7789.CYAN)
display.text(prompt[:25] + "..." if len(prompt) > 25 else prompt, 15, 165, st7789.WHITE) display.text(prompt[:25] + "..." if len(prompt) > 25 else prompt, 15, 165, st7789.WHITE)
# Only show back button if not showing full image, or maybe show it transparently? # Only show back button if not showing full image, or maybe show it transparently?
# For now, let's not cover the image with the button hint # For now, let's not cover the image with the button hint
if not image_received and not image_generation_done: if not image_received:
display.tft.fill_rect(60, 210, 120, 25, st7789.BLUE) display.tft.fill_rect(60, 210, 120, 25, st7789.BLUE)
display.text("长按返回", 90, 215, st7789.WHITE) display.text("长按返回", 90, 215, st7789.WHITE)
@@ -502,19 +505,20 @@ def main():
# WiFi 和 WS 都连接成功后,进入录音界面 # WiFi 和 WS 都连接成功后,进入录音界面
ui_screen = UI_SCREEN_RECORDING ui_screen = UI_SCREEN_RECORDING
if display.tft: if display.tft:
render_recording_screen(display, "", 0) render_recording_screen(display, "", 0, False)
else: else:
print("Running in offline mode") print("Running in offline mode")
# 即使离线也进入录音界面(虽然不能用) # 即使离线也进入录音界面(虽然不能用)
ui_screen = UI_SCREEN_RECORDING ui_screen = UI_SCREEN_RECORDING
if display.tft: if display.tft:
render_recording_screen(display, "离线模式", 0) render_recording_screen(display, "离线模式", 0, False)
read_buf = bytearray(4096) read_buf = bytearray(4096)
last_audio_level = 0 last_audio_level = 0
memory_check_counter = 0 memory_check_counter = 0
spinner_angle = 0 spinner_angle = 0
last_spinner_time = 0 last_spinner_time = 0
wait_for_release = False
while True: while True:
try: try:
@@ -544,134 +548,67 @@ def main():
btn_action = get_boot_button_action(boot_btn) btn_action = get_boot_button_action(boot_btn)
if btn_action == 1: # Hold to Record Logic (Press to Start, Release to Stop)
if is_recording: if ui_screen == UI_SCREEN_RECORDING:
print(">>> Stop recording") if boot_btn.value() == 0 and not is_recording:
if ws and ws.is_connected(): print(">>> Start recording (Hold)")
try: is_recording = True
ws.send("STOP_RECORDING")
except:
ws = None
is_recording = False
ui_screen = UI_SCREEN_RESULT
image_generation_done = False
if display.tft:
render_result_screen(display, "OPTIMIZING", current_asr_text, False)
time.sleep(0.5)
elif ui_screen == UI_SCREEN_RECORDING:
if not is_recording:
print(">>> Recording...")
is_recording = True
confirm_waiting = False
current_asr_text = ""
current_prompt = ""
current_status = ""
image_generation_done = False
if display.tft:
render_recording_screen(display, "", 0)
if ws is None or not ws.is_connected():
connect_ws()
if ws and ws.is_connected():
try:
ws.send("START_RECORDING")
except:
ws = None
elif ui_screen == UI_SCREEN_CONFIRM:
print(">>> Confirm and generate")
# 发送生成图片指令
if ws and ws.is_connected():
try:
# 明确发送生成指令
ws.send(f"GENERATE_IMAGE:{current_asr_text}")
except:
ws = None
is_recording = False
ui_screen = UI_SCREEN_RESULT
image_generation_done = False
if display.tft:
render_result_screen(display, "OPTIMIZING", current_asr_text, False)
time.sleep(0.5)
elif ui_screen == UI_SCREEN_RESULT:
# Ignore short press in result screen to keep image displayed
# unless image generation failed or is still in progress?
# User request: "只有长按boot才离开" (Only leave on long press)
# So we do nothing here.
pass
elif btn_action == 2:
if is_recording:
print(">>> Stop recording (long press)")
if ws and ws.is_connected():
try:
ws.send("STOP_RECORDING")
except:
ws = None
is_recording = False
# If in recording screen or (not recording AND not result screen), then regenerate/re-record
# This ensures result screen is handled by its own block below
if ui_screen == UI_SCREEN_RECORDING:
if current_asr_text:
print(">>> Generate image with ASR text")
ui_screen = UI_SCREEN_RESULT
image_generation_done = False
if display.tft:
render_result_screen(display, "OPTIMIZING", current_asr_text, False)
time.sleep(0.5)
else:
print(">>> Re-record")
current_asr_text = ""
confirm_waiting = False
ui_screen = UI_SCREEN_RECORDING
if display.tft:
render_recording_screen(display, "", 0)
elif ui_screen == UI_SCREEN_CONFIRM:
print(">>> Re-record")
current_asr_text = ""
confirm_waiting = False confirm_waiting = False
ui_screen = UI_SCREEN_RECORDING
if display.tft:
render_recording_screen(display, "", 0)
elif ui_screen == UI_SCREEN_RESULT:
print(">>> Back to recording")
# Stop recording if it was somehow started or just reset state
if ws and ws.is_connected():
try:
ws.send("STOP_RECORDING")
except:
ws = None
ui_screen = UI_SCREEN_RECORDING
is_recording = False
current_asr_text = "" current_asr_text = ""
current_prompt = "" current_prompt = ""
current_status = "" current_status = ""
image_generation_done = False image_generation_done = False
confirm_waiting = False
if display.tft: if display.tft:
render_recording_screen(display, "", 0) render_recording_screen(display, "", 0, True)
if ws is None or not ws.is_connected():
connect_ws()
if ws and ws.is_connected():
try:
ws.send("START_RECORDING")
except:
ws = None
elif boot_btn.value() == 1 and is_recording:
print(">>> Stop recording (Release)")
if ws and ws.is_connected():
try:
ws.send("STOP_RECORDING")
except:
ws = None
is_recording = False
ui_screen = UI_SCREEN_CONFIRM
image_generation_done = False
if display.tft:
render_confirm_screen(display, current_asr_text)
# Consume action to prevent triggering other events
btn_action = 0
if btn_action == 1:
if ui_screen == UI_SCREEN_CONFIRM:
print(">>> Confirm and generate")
if ws and ws.is_connected():
try:
ws.send(f"GENERATE_IMAGE:{current_asr_text}")
except:
ws = None
is_recording = False
ui_screen = UI_SCREEN_RESULT
image_generation_done = False
if display.tft:
render_result_screen(display, "OPTIMIZING", current_asr_text, False)
time.sleep(0.5)
elif btn_action == 2:
if ui_screen == UI_SCREEN_CONFIRM or ui_screen == UI_SCREEN_RESULT:
print(">>> Re-record")
current_asr_text = ""
confirm_waiting = False
ui_screen = UI_SCREEN_RECORDING
is_recording = False
image_generation_done = False
if display.tft:
render_recording_screen(display, "", 0, False)
time.sleep(0.5)
elif btn_action == 3: elif btn_action == 3:
print(">>> Config mode") print(">>> Config mode")
@@ -684,82 +621,58 @@ def main():
ws.send(read_buf[:num_read], opcode=2) ws.send(read_buf[:num_read], opcode=2)
# 移除录音时的消息接收,确保录音流畅 # 移除录音时的消息接收,确保录音流畅
# poller = uselect.poll()
# poller.register(ws.sock, uselect.POLLIN)
# events = poller.poll(0)
# if events:
# msg = ws.recv()
# image_state, event_data = process_message(msg, display, image_state, image_data_list)
#
# if event_data:
# if event_data[0] == "asr":
# current_asr_text = event_data[1]
# if display.tft:
# render_recording_screen(display, current_asr_text, last_audio_level)
#
# elif event_data[0] == "font_update":
# if ui_screen == UI_SCREEN_RECORDING and display.tft:
# render_recording_screen(display, current_asr_text, last_audio_level)
#
# elif event_data[0] == "status":
# current_status = event_data[1]
# status_text = event_data[2] if len(event_data) > 2 else ""
# if display.tft:
# render_result_screen(display, current_status, current_prompt, image_generation_done)
#
# elif event_data[0] == "prompt":
# current_prompt = event_data[1]
#
# elif event_data[0] == "image_done":
# image_generation_done = True
# if display.tft:
# render_result_screen(display, "COMPLETE", current_prompt, True)
#
# elif event_data[0] == "error":
# if display.tft:
# render_result_screen(display, "ERROR", current_prompt, False)
except: except:
ws = None ws = None
if ui_screen == UI_SCREEN_RESULT and ws and ws.is_connected(): # 在录音结束后CONFIRM状态或 RESULT 状态,才接收消息
try: if (ui_screen == UI_SCREEN_CONFIRM or ui_screen == UI_SCREEN_RESULT or ui_screen == UI_SCREEN_RECORDING) and not is_recording:
poller = uselect.poll() if ws and ws.is_connected():
poller.register(ws.sock, uselect.POLLIN) try:
events = poller.poll(100) poller = uselect.poll()
if events: poller.register(ws.sock, uselect.POLLIN)
msg = ws.recv() events = poller.poll(100)
if msg: if events:
image_state, event_data = process_message(msg, display, image_state, image_data_list) msg = ws.recv()
if msg:
if event_data: image_state, event_data = process_message(msg, display, image_state, image_data_list)
if event_data[0] == "asr":
current_asr_text = event_data[1]
elif event_data[0] == "status": if event_data:
current_status = event_data[1] if event_data[0] == "asr":
status_text = event_data[2] if len(event_data) > 2 else "" current_asr_text = event_data[1]
if display.tft: print(f"Received ASR: {current_asr_text}")
render_result_screen(display, current_status, current_prompt, image_generation_done)
# 收到 ASR 结果,跳转到 CONFIRM 界面
elif event_data[0] == "prompt": if ui_screen == UI_SCREEN_RECORDING or ui_screen == UI_SCREEN_CONFIRM:
current_prompt = event_data[1] ui_screen = UI_SCREEN_CONFIRM
if display.tft: if display.tft:
render_result_screen(display, current_status, current_prompt, image_generation_done) render_confirm_screen(display, current_asr_text)
elif event_data[0] == "image_done": elif event_data[0] == "font_update":
image_generation_done = True # 如果还在录音界面等待,刷新一下(虽然可能已经跳到 CONFIRM 了)
if display.tft: pass
render_result_screen(display, "COMPLETE", current_prompt, True)
elif event_data[0] == "status":
elif event_data[0] == "error": current_status = event_data[1]
if display.tft: status_text = event_data[2] if len(event_data) > 2 else ""
render_result_screen(display, "ERROR", current_prompt, False) if display.tft and ui_screen == UI_SCREEN_RESULT:
except: render_result_screen(display, current_status, current_prompt, image_generation_done)
pass
elif event_data[0] == "prompt":
current_prompt = event_data[1]
if display.tft and ui_screen == UI_SCREEN_RESULT:
render_result_screen(display, current_status, current_prompt, image_generation_done)
elif event_data[0] == "image_done":
image_generation_done = True
if display.tft and ui_screen == UI_SCREEN_RESULT:
render_result_screen(display, "COMPLETE", current_prompt, True)
elif event_data[0] == "error":
if display.tft and ui_screen == UI_SCREEN_RESULT:
render_result_screen(display, "ERROR", current_prompt, False)
except Exception as e:
print(f"WS Recv Error: {e}")
continue
time.sleep(0.01) time.sleep(0.01)
except Exception as e: except Exception as e:

View File

@@ -9,22 +9,31 @@ FONTS = {
20572: b'\x08\x80\x08\x78\x17\x80\x10\x60\x23\xa0\x22\x60\x63\x80\xa0\x7c\x2f\x88\x28\x30\x23\xc0\x20\x40\x21\x40\x20\xc0\x20\x40\x00\x00', # 停 20572: b'\x08\x80\x08\x78\x17\x80\x10\x60\x23\xa0\x22\x60\x63\x80\xa0\x7c\x2f\x88\x28\x30\x23\xc0\x20\x40\x21\x40\x20\xc0\x20\x40\x00\x00', # 停
21035: b'\x00\x08\x06\x08\x3a\x08\x22\x48\x26\x48\x38\x48\x28\x48\x0f\x48\x71\x48\x11\x48\x11\x08\x22\x08\x2a\x28\x44\x18\x80\x08\x00\x00', # 别 21035: b'\x00\x08\x06\x08\x3a\x08\x22\x48\x26\x48\x38\x48\x28\x48\x0f\x48\x71\x48\x11\x48\x11\x08\x22\x08\x2a\x28\x44\x18\x80\x08\x00\x00', # 别
21040: b'\x00\x08\x00\x08\x07\x88\x38\x28\x0a\x28\x11\x28\x23\xa8\x7c\xa8\x04\x28\x07\x28\x3c\x28\x07\x88\x18\x28\x60\x18\x00\x08\x00\x00', # 到 21040: b'\x00\x08\x00\x08\x07\x88\x38\x28\x0a\x28\x11\x28\x23\xa8\x7c\xa8\x04\x28\x07\x28\x3c\x28\x07\x88\x18\x28\x60\x18\x00\x08\x00\x00', # 到
21151: b'\x00\x40\x00\x40\x00\x40\x06\x40\x78\x78\x09\xc8\x08\x48\x08\x48\x08\x88\x0e\x88\x71\x08\x01\x10\x02\x50\x04\x30\x08\x00\x00\x00', # 功
21153: b'\x02\x00\x02\x00\x04\xe0\x07\x40\x0a\x80\x11\x00\x22\xc0\x0d\x30\x31\x0e\xc1\xe0\x1e\x20\x02\x20\x04\x40\x19\x40\x60\x80\x00\x00', # 务
21160: b'\x00\x20\x00\x20\x06\x20\x18\x20\x00\x38\x0e\xe8\x70\x28\x10\x48\x14\x48\x22\x88\x2e\x88\x31\x10\x01\x50\x02\x20\x04\x00\x00\x00', # 动
21270: b'\x04\x00\x04\x80\x08\x80\x08\x88\x08\x88\x18\x90\x28\xa0\x48\xc0\x09\x80\x0a\x80\x08\x84\x08\x84\x08\x84\x08\x7c\x08\x00\x00\x00', # 化 21270: b'\x04\x00\x04\x80\x08\x80\x08\x88\x08\x88\x18\x90\x28\xa0\x48\xc0\x09\x80\x0a\x80\x08\x84\x08\x84\x08\x84\x08\x7c\x08\x00\x00\x00', # 化
21527: b'\x00\x60\x03\xa0\x00\x20\x19\x20\x69\x20\x49\x20\x59\x20\x61\x78\x01\x88\x00\x08\x00\xe8\x0f\x08\x00\x10\x00\x50\x00\x20\x00\x00', # 吗 21527: b'\x00\x60\x03\xa0\x00\x20\x19\x20\x69\x20\x49\x20\x59\x20\x61\x78\x01\x88\x00\x08\x00\xe8\x0f\x08\x00\x10\x00\x50\x00\x20\x00\x00', # 吗
21551: b'\x01\x00\x00\x80\x00\x70\x0f\x90\x08\x10\x08\x70\x0f\x80\x08\x00\x10\x70\x17\x90\x14\x10\x24\x10\x24\x70\x47\x80\x84\x00\x00\x00', # 启
22120: b'\x00\x00\x06\x30\x1a\xd0\x12\x90\x16\xb0\x18\xc0\x12\x20\x03\xf8\x7c\x40\x08\x20\x36\x3e\xda\xd0\x12\x90\x16\xb0\x18\xc0\x00\x00', # 器
22238: b'\x00\x00\x00\x00\x01\xf8\x3e\x08\x20\x08\x21\x88\x26\x88\x24\x88\x25\x88\x26\x08\x20\x08\x20\xf8\x3f\x00\x00\x00\x00\x00\x00\x00', # 回 22238: b'\x00\x00\x00\x00\x01\xf8\x3e\x08\x20\x08\x21\x88\x26\x88\x24\x88\x25\x88\x26\x08\x20\x08\x20\xf8\x3f\x00\x00\x00\x00\x00\x00\x00', # 回
22312: b'\x01\x00\x01\x00\x02\x00\x03\xf8\x7c\x00\x04\x80\x18\x80\x10\x80\x30\xf0\x57\x80\x90\x80\x10\x80\x10\xfc\x1f\x00\x10\x00\x00\x00', # 在 22312: b'\x01\x00\x01\x00\x02\x00\x03\xf8\x7c\x00\x04\x80\x18\x80\x10\x80\x30\xf0\x57\x80\x90\x80\x10\x80\x10\xfc\x1f\x00\x10\x00\x00\x00', # 在
22833: b'\x01\x00\x09\x00\x09\x00\x09\xf0\x1f\x00\x11\x00\x21\x00\x01\xf8\x7e\x80\x02\x80\x04\x40\x04\x40\x08\x20\x10\x38\x20\x00\x00\x00', # 失 22833: b'\x01\x00\x09\x00\x09\x00\x09\xf0\x1f\x00\x11\x00\x21\x00\x01\xf8\x7e\x80\x02\x80\x04\x40\x04\x40\x08\x20\x10\x38\x20\x00\x00\x00', # 失
23383: b'\x02\x00\x01\x00\x01\xfc\x3e\x08\x21\xe0\x0e\x40\x00\x80\x01\x00\x00\xfc\x7f\x80\x00\x80\x00\x80\x00\x80\x02\x80\x01\x00\x00\x00', # 字 23383: b'\x02\x00\x01\x00\x01\xfc\x3e\x08\x21\xe0\x0e\x40\x00\x80\x01\x00\x00\xfc\x7f\x80\x00\x80\x00\x80\x00\x80\x02\x80\x01\x00\x00\x00', # 字
23436: b'\x02\x00\x01\x00\x00\xfc\x3f\x08\x20\x00\x00\xc0\x07\x00\x00\x78\x3f\x80\x04\x80\x04\x80\x08\x84\x08\x84\x10\x84\x60\x7c\x00\x00', # 完 23436: b'\x02\x00\x01\x00\x00\xfc\x3f\x08\x20\x00\x00\xc0\x07\x00\x00\x78\x3f\x80\x04\x80\x04\x80\x08\x84\x08\x84\x10\x84\x60\x7c\x00\x00', # 完
24320: b'\x00\x00\x03\xf0\x1c\x40\x04\x40\x04\x40\x04\x40\x07\xfc\x7c\x40\x04\x40\x04\x40\x08\x40\x08\x40\x10\x40\x20\x40\x40\x40\x00\x00', # 开 24320: b'\x00\x00\x03\xf0\x1c\x40\x04\x40\x04\x40\x04\x40\x07\xfc\x7c\x40\x04\x40\x04\x40\x08\x40\x08\x40\x10\x40\x20\x40\x40\x40\x00\x00', # 开
24335: b'\x01\x00\x01\x40\x01\x20\x01\x00\x01\x78\x3f\x80\x00\x80\x03\x80\x1c\x80\x04\x40\x04\x40\x07\x24\x18\x14\x60\x0c\x00\x04\x00\x00', # 式
24405: b'\x01\xc0\x0e\x40\x01\xc0\x0e\x40\x00\xfc\x3f\x00\x01\x10\x11\x10\x09\xa0\x05\x40\x09\x20\x11\x18\x61\x06\x03\x00\x01\x00\x00\x00', # 录 24405: b'\x01\xc0\x0e\x40\x01\xc0\x0e\x40\x00\xfc\x3f\x00\x01\x10\x11\x10\x09\xa0\x05\x40\x09\x20\x11\x18\x61\x06\x03\x00\x01\x00\x00\x00', # 录
24605: b'\x00\xf0\x1f\x10\x11\x10\x11\xf0\x1f\x10\x11\x10\x11\xf0\x1e\x00\x10\x00\x23\x18\x28\x84\x24\x10\x43\x10\x40\xf0\x00\x00\x00\x00', # 思 24605: b'\x00\xf0\x1f\x10\x11\x10\x11\xf0\x1f\x10\x11\x10\x11\xf0\x1e\x00\x10\x00\x23\x18\x28\x84\x24\x10\x43\x10\x40\xf0\x00\x00\x00\x00', # 思
25104: b'\x00\xa0\x00\x90\x00\x80\x00\xf0\x1f\x80\x10\x90\x10\x90\x1e\xa0\x12\xa0\x22\x40\x22\x44\x24\xa4\x55\x14\x48\x0c\x80\x04\x00\x00', # 成 25104: b'\x00\xa0\x00\x90\x00\x80\x00\xf0\x1f\x80\x10\x90\x10\x90\x1e\xa0\x12\xa0\x22\x40\x22\x44\x24\xa4\x55\x14\x48\x0c\x80\x04\x00\x00', # 成
25353: b'\x10\x40\x10\x20\x10\x3c\x13\xc8\x1e\x40\x70\x40\x10\x80\x18\xfe\x37\x10\xd1\x10\x11\x20\x10\xa0\x50\x60\x31\x90\x16\x08\x00\x00', # 按 25353: b'\x10\x40\x10\x20\x10\x3c\x13\xc8\x1e\x40\x70\x40\x10\x80\x18\xfe\x37\x10\xd1\x10\x11\x20\x10\xa0\x50\x60\x31\x90\x16\x08\x00\x00', # 按
25509: b'\x10\x80\x10\x40\x10\x18\x13\xf0\x1d\x10\x70\xa0\x14\xfc\x1b\x80\x30\x80\xd7\xfc\x11\x20\x12\x40\x11\xc0\x30\xa0\x17\x18\x00\x00', # 接
25552: b'\x10\x30\x11\xd0\x11\x10\x11\xd0\x1d\x30\x71\xc0\x15\x00\x18\x38\x37\xc0\xd2\x70\x12\x40\x13\x40\x54\xc0\x34\x30\x18\x0e\x00\x00', # 提 25552: b'\x10\x30\x11\xd0\x11\x10\x11\xd0\x1d\x30\x71\xc0\x15\x00\x18\x38\x37\xc0\xd2\x70\x12\x40\x13\x40\x54\xc0\x34\x30\x18\x0e\x00\x00', # 提
25991: b'\x02\x00\x01\x00\x01\x00\x00\x38\x3f\xc0\x00\x40\x04\x40\x02\x80\x02\x80\x01\x00\x01\x00\x02\x80\x0c\x40\x30\x30\xc0\x0e\x00\x00', # 文 25991: b'\x02\x00\x01\x00\x01\x00\x00\x38\x3f\xc0\x00\x40\x04\x40\x02\x80\x02\x80\x01\x00\x01\x00\x02\x80\x0c\x40\x30\x30\xc0\x0e\x00\x00', # 文
26381: b'\x00\x30\x0d\xd0\x35\x10\x25\x50\x2d\x20\x35\x00\x25\x30\x2d\xd0\x35\x90\x25\x50\x25\x20\x45\x30\x45\x48\x4d\x86\x85\x00\x00\x00', # 服
26410: b'\x01\x00\x01\x00\x01\x00\x01\xf0\x1f\x00\x01\x00\x01\x78\x7f\x80\x03\x40\x05\x40\x09\x20\x11\x20\x61\x1c\x81\x00\x01\x00\x00\x00', # 未 26410: b'\x01\x00\x01\x00\x01\x00\x01\xf0\x1f\x00\x01\x00\x01\x78\x7f\x80\x03\x40\x05\x40\x09\x20\x11\x20\x61\x1c\x81\x00\x01\x00\x00\x00', # 未
26494: b'\x00\x40\x10\x40\x10\xa0\x10\xa0\x1c\xa0\x71\x10\x19\x50\x36\x4c\x52\x40\x54\x80\x90\xa0\x11\x10\x11\x38\x13\xc8\x10\x00\x00\x00', # 松 26494: b'\x00\x40\x10\x40\x10\xa0\x10\xa0\x1c\xa0\x71\x10\x19\x50\x36\x4c\x52\x40\x54\x80\x90\xa0\x11\x10\x11\x38\x13\xc8\x10\x00\x00\x00', # 松
27169: b'\x11\x10\x11\x10\x11\xfc\x13\x20\x1d\xf0\x73\x10\x1a\x70\x37\x90\x52\x70\x53\xc0\x90\x7c\x17\xa0\x11\x10\x12\x08\x14\x0e\x00\x00', # 模
27490: b'\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x11\x00\x11\x30\x11\xc0\x11\x00\x11\x00\x11\x00\x11\x00\x11\x00\x11\xfc\xfe\x00\x00\x00', # 止 27490: b'\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x11\x00\x11\x30\x11\xc0\x11\x00\x11\x00\x11\x00\x11\x00\x11\x00\x11\xfc\xfe\x00\x00\x00', # 止
27491: b'\x00\x00\x00\xf0\x1f\x00\x01\x00\x01\x00\x01\x00\x09\x30\x09\xc0\x09\x00\x09\x00\x09\x00\x09\x00\x09\xfc\x7e\x00\x00\x00\x00\x00', # 正 27491: b'\x00\x00\x00\xf0\x1f\x00\x01\x00\x01\x00\x01\x00\x09\x30\x09\xc0\x09\x00\x09\x00\x09\x00\x09\x00\x09\xfc\x7e\x00\x00\x00\x00\x00', # 正
29983: b'\x01\x00\x01\x00\x01\x00\x09\x00\x09\x00\x11\xf0\x1f\x00\x21\x00\x21\x00\x41\xe0\x0f\x00\x01\x00\x01\x00\x01\xfc\x7e\x00\x00\x00', # 生 29983: b'\x01\x00\x01\x00\x01\x00\x09\x00\x09\x00\x11\xf0\x1f\x00\x21\x00\x21\x00\x41\xe0\x0f\x00\x01\x00\x01\x00\x01\xfc\x7e\x00\x00\x00', # 生
@@ -32,16 +41,25 @@ FONTS = {
30701: b'\x10\x00\x10\x3c\x11\xc0\x16\x18\x38\xe8\x28\x88\x48\x98\x0e\xe0\x78\x10\x08\x90\x14\x50\x12\x50\x20\x3c\x43\xc0\x00\x00\x00\x00', # 短 30701: b'\x10\x00\x10\x3c\x11\xc0\x16\x18\x38\xe8\x28\x88\x48\x98\x0e\xe0\x78\x10\x08\x90\x14\x50\x12\x50\x20\x3c\x43\xc0\x00\x00\x00\x00', # 短
30830: b'\x00\x80\x00\x80\x0c\xf0\x71\x20\x11\x40\x12\x78\x21\xc8\x2d\x68\x75\xc8\xa5\x68\x2d\xc8\x32\x48\x22\x48\x04\x18\x08\x08\x00\x00', # 确 30830: b'\x00\x80\x00\x80\x0c\xf0\x71\x20\x11\x40\x12\x78\x21\xc8\x2d\x68\x75\xc8\xa5\x68\x2d\xc8\x32\x48\x22\x48\x04\x18\x08\x08\x00\x00', # 确
31034: b'\x00\x00\x00\xe0\x0f\x00\x00\x00\x00\xfc\x7f\x00\x01\x00\x05\x00\x05\x20\x09\x10\x11\x08\x21\x08\x45\x00\x03\x00\x01\x00\x00\x00', # 示 31034: b'\x00\x00\x00\xe0\x0f\x00\x00\x00\x00\xfc\x7f\x00\x01\x00\x05\x00\x05\x20\x09\x10\x11\x08\x21\x08\x45\x00\x03\x00\x01\x00\x00\x00', # 示
31163: b'\x02\x00\x01\xf8\x3f\x00\x02\xa0\x09\x20\x0a\xe0\x0f\x00\x09\x00\x01\xf0\x3e\x90\x22\x50\x25\xd0\x26\x10\x20\x30\x20\x10\x00\x00', # 离
32440: b'\x10\x00\x10\x10\x10\x60\x21\xa0\x29\x20\x49\x20\xf1\x3c\x11\xe0\x11\x20\x2d\x20\x71\x10\x01\x52\x0d\x8a\x31\x06\xc0\x02\x00\x00', # 纸
32447: b'\x01\x00\x11\x40\x11\x20\x11\x00\x25\x60\x27\x80\x78\x98\x08\xe0\x13\x90\x38\x90\x00\xa0\x04\x44\x19\xa4\x66\x1c\x00\x04\x00\x00', # 线
32472: b'\x00\x80\x10\x80\x10\xc0\x21\x40\x25\x20\x4a\x10\x74\x6e\x11\x80\x2c\x38\x73\xc0\x00\x80\x0c\xa0\x31\x10\xc2\x78\x03\x88\x00\x00', # 绘 32472: b'\x00\x80\x10\x80\x10\xc0\x21\x40\x25\x20\x4a\x10\x74\x6e\x11\x80\x2c\x38\x73\xc0\x00\x80\x0c\xa0\x31\x10\xc2\x78\x03\x88\x00\x00', # 绘
32771: b'\x02\x00\x02\x10\x03\xa0\x0e\x40\x02\x80\x03\xfc\x7e\x00\x07\xf0\x1a\x00\x22\x60\xc3\xa0\x00\x20\x00\x40\x01\x40\x00\x80\x00\x00', # 考 32771: b'\x02\x00\x02\x10\x03\xa0\x0e\x40\x02\x80\x03\xfc\x7e\x00\x07\xf0\x1a\x00\x22\x60\xc3\xa0\x00\x20\x00\x40\x01\x40\x00\x80\x00\x00', # 考
35748: b'\x00\x00\x10\x40\x08\x40\x08\x40\x00\x40\x00\x40\x70\x40\x10\x40\x10\xa0\x10\xa0\x15\x10\x19\x10\x12\x08\x04\x0e\x08\x00\x00\x00', # 认 35748: b'\x00\x00\x10\x40\x08\x40\x08\x40\x00\x40\x00\x40\x70\x40\x10\x40\x10\xa0\x10\xa0\x15\x10\x19\x10\x12\x08\x04\x0e\x08\x00\x00\x00', # 认
35782: b'\x00\x00\x10\x38\x09\xc8\x09\x08\x01\x08\x71\x38\x11\xc0\x11\x00\x10\x00\x14\x90\x18\x88\x11\x04\x02\x04\x04\x00\x00\x00\x00\x00', # 识 35782: b'\x00\x00\x10\x38\x09\xc8\x09\x08\x01\x08\x71\x38\x11\xc0\x11\x00\x10\x00\x14\x90\x18\x88\x11\x04\x02\x04\x04\x00\x00\x00\x00\x00', # 识
35789: b'\x20\x00\x10\x78\x0b\x88\x00\x08\x00\xe8\x77\x08\x10\xc8\x13\x48\x12\x48\x12\xc8\x13\x08\x1a\x08\x10\x28\x00\x18\x00\x08\x00\x00', # 词 35789: b'\x20\x00\x10\x78\x0b\x88\x00\x08\x00\xe8\x77\x08\x10\xc8\x13\x48\x12\x48\x12\xc8\x13\x08\x1a\x08\x10\x28\x00\x18\x00\x08\x00\x00', # 词
35797: b'\x00\x40\x20\x50\x10\x48\x10\x40\x00\x78\x07\xc0\x70\x40\x10\xc0\x17\x40\x11\x20\x11\x20\x15\x54\x19\x94\x16\x0c\x00\x04\x00\x00', # 试
35821: b'\x00\x00\x20\x70\x13\x80\x10\x80\x00\xe0\x03\x20\xe1\x20\x21\xfc\x26\x00\x20\x70\x23\x90\x2a\x10\x32\x70\x23\x80\x02\x00\x00\x00', # 语 35821: b'\x00\x00\x20\x70\x13\x80\x10\x80\x00\xe0\x03\x20\xe1\x20\x21\xfc\x26\x00\x20\x70\x23\x90\x2a\x10\x32\x70\x23\x80\x02\x00\x00\x00', # 语
35828: b'\x02\x10\x21\x10\x11\x20\x10\x20\x00\x70\x03\x90\x72\x10\x12\x70\x13\xa0\x10\xa0\x14\xa0\x19\x22\x11\x22\x02\x22\x0c\x1e\x00\x00', # 说 35828: b'\x02\x10\x21\x10\x11\x20\x10\x20\x00\x70\x03\x90\x72\x10\x12\x70\x13\xa0\x10\xa0\x14\xa0\x19\x22\x11\x22\x02\x22\x0c\x1e\x00\x00', # 说
35831: b'\x00\x40\x20\x70\x13\xc0\x10\x70\x01\xc0\x00\x7c\x77\x80\x10\x70\x13\x90\x12\x70\x13\x90\x1a\x70\x13\x90\x02\x30\x02\x10\x00\x00', # 请
36133: b'\x00\x40\x06\x40\x3a\x40\x22\x4c\x2a\x70\x2a\x90\x2a\x90\x2b\x50\x2a\x50\x28\x20\x14\x20\x12\x50\x20\x90\x21\x0c\x42\x00\x00\x00', # 败 36133: b'\x00\x40\x06\x40\x3a\x40\x22\x4c\x2a\x70\x2a\x90\x2a\x90\x2b\x50\x2a\x50\x28\x20\x14\x20\x12\x50\x20\x90\x21\x0c\x42\x00\x00\x00', # 败
36148: b'\x00\x20\x06\x20\x3a\x20\x22\x20\x2a\x3c\x2a\x20\x2a\x20\x2a\x20\x2a\x38\x2a\xc8\x28\x88\x14\x88\x22\x98\x42\xe0\x80\x80\x00\x00', # 贴
36820: b'\x00\x00\x00\x38\x13\xc0\x0a\x00\x02\x70\x03\x90\x3a\x10\xca\xa0\x12\x60\x12\x50\x0c\x88\x09\x00\x7c\x00\x01\xc0\x00\x3e\x00\x00', # 返 36820: b'\x00\x00\x00\x38\x13\xc0\x0a\x00\x02\x70\x03\x90\x3a\x10\xca\xa0\x12\x60\x12\x50\x0c\x88\x09\x00\x7c\x00\x01\xc0\x00\x3e\x00\x00', # 返
36830: b'\x00\x40\x20\x40\x10\x78\x13\x80\x01\x40\x1a\x70\x6b\xc0\x08\x40\x10\x78\x17\xc0\x08\x40\x70\x40\x1e\x00\x01\xc0\x00\x3e\x00\x00', # 连
36857: b'\x00\x80\x00\x40\x20\x1c\x13\xe0\x00\xa0\x02\xa0\x1a\xb0\x6a\xa8\x15\x24\x11\x20\x0a\x60\x10\x20\x7c\x00\x03\xc0\x00\x3c\x00\x00', # 迹
37325: b'\x00\x20\x00\xc0\x1f\x00\x01\xfc\x7f\x00\x01\xf0\x1f\x10\x11\xd0\x17\x10\x11\xf0\x1f\x00\x01\xe0\x1f\x00\x01\xfc\x7e\x00\x00\x00', # 重 37325: b'\x00\x20\x00\xc0\x1f\x00\x01\xfc\x7f\x00\x01\xf0\x1f\x10\x11\xd0\x17\x10\x11\xf0\x1f\x00\x01\xe0\x1f\x00\x01\xfc\x7e\x00\x00\x00', # 重
37327: b'\x01\xe0\x0e\x20\x09\xe0\x0e\x20\x08\xfc\x7f\x00\x00\xe0\x1f\x20\x11\xe0\x17\x20\x11\xe0\x1f\x00\x01\x00\x01\xf8\x3e\x00\x00\x00', # 量
38271: b'\x08\x00\x08\x20\x08\x40\x08\x80\x0b\x00\x0c\x00\x09\xf8\x7e\x00\x0a\x00\x09\x00\x08\x80\x08\x40\x0a\x30\x0c\x0c\x08\x00\x00\x00', # 长 38271: b'\x08\x00\x08\x20\x08\x40\x08\x80\x0b\x00\x0c\x00\x09\xf8\x7e\x00\x0a\x00\x09\x00\x08\x80\x08\x40\x0a\x30\x0c\x0c\x08\x00\x00\x00', # 长
38899: b'\x02\x00\x01\x00\x01\xf0\x1e\x40\x04\x40\x04\x80\x01\xfc\x7e\x00\x01\xe0\x0e\x20\x09\xa0\x0e\x20\x08\x20\x09\xe0\x0e\x20\x00\x00', # 音 38899: b'\x02\x00\x01\x00\x01\xf0\x1e\x40\x04\x40\x04\x80\x01\xfc\x7e\x00\x01\xe0\x0e\x20\x09\xa0\x0e\x20\x08\x20\x09\xe0\x0e\x20\x00\x00', # 音
65311: b'\x00\x00\x00\x00\x1c\x00\x22\x00\x22\x00\x04\x00\x08\x00\x08\x00\x08\x00\x00\x00\x18\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00', # 65311: b'\x00\x00\x00\x00\x1c\x00\x22\x00\x22\x00\x04\x00\x08\x00\x08\x00\x08\x00\x00\x00\x18\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00', #

View File

@@ -21,7 +21,18 @@ FIXED_STRINGS = [
"生成完成!", "生成完成!",
"生成失败", "生成失败",
"提示词:", "提示词:",
"返回录音" "返回录音",
"重试",
"长按返回",
"连接服务器...",
"服务器连接失败",
"离线模式",
"量迹AI贴纸生成",
"正在启动...",
"WiFi连接中...",
"WiFi连接成功!",
"WiFi连接失败",
"请重试"
] ]
def generate_static_font(): def generate_static_font():

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -851,6 +851,17 @@ async def websocket_endpoint(websocket: WebSocket):
# 4. 如果有识别结果发送ASR文字到ESP32 # 4. 如果有识别结果发送ASR文字到ESP32
if asr_text: if asr_text:
print(f"ASR result: {asr_text}") print(f"ASR result: {asr_text}")
# 主动下发字体数据
try:
unique_chars = set(asr_text)
code_list = [str(ord(c)) for c in unique_chars]
print(f"Sending font data for {len(code_list)} characters...")
success_count, failed = await send_font_batch_with_retry(websocket, code_list)
print(f"Font data sent: {success_count} success, {len(failed)} failed")
except Exception as e:
print(f"Error sending font data: {e}")
# 发送 ASR 文字到 ESP32 显示 # 发送 ASR 文字到 ESP32 显示
await websocket.send_text(f"ASR:{asr_text}") await websocket.send_text(f"ASR:{asr_text}")