diff --git a/display.py b/display.py index b538cfd..e173884 100644 --- a/display.py +++ b/display.py @@ -210,3 +210,62 @@ class Display: self.tft.line(x, y + 5, x + 3, y + 8, 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) diff --git a/font.py b/font.py index b357c92..0199f82 100644 --- a/font.py +++ b/font.py @@ -54,9 +54,11 @@ class Font: d = binascii.unhexlify(parts[2]) self.cache[c] = d - # 清除重试计数(如果有) + # 清除重试计数(如果有)和 pending if c in self.retry_count: del self.retry_count[c] + if c in self.pending_requests: + self.pending_requests.remove(c) return True except Exception as e: print(f"Font data parse error: {e}") @@ -110,6 +112,11 @@ class Font: if wait: print(f"Batch requesting fonts: {req_str}") 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}") if wait: self._wait_for_fonts(missing_codes) diff --git a/main.py b/main.py index d02bf7f..d8aefde 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ UI_SCREEN_RECORDING = 1 UI_SCREEN_CONFIRM = 2 UI_SCREEN_RESULT = 3 -BOOT_SHORT_MS = 500 +BOOT_SHORT_MS = 100 BOOT_LONG_MS = 2000 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) -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: 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.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=""): @@ -251,14 +254,14 @@ def render_result_screen(display, status="", prompt="", image_received=False): display.text("AI 生成中", 80, 8, st7789.BLACK) 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.text("提示词:", 15, 145, st7789.CYAN) 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? # 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.text("长按返回", 90, 215, st7789.WHITE) @@ -502,19 +505,20 @@ def main(): # WiFi 和 WS 都连接成功后,进入录音界面 ui_screen = UI_SCREEN_RECORDING if display.tft: - render_recording_screen(display, "", 0) + render_recording_screen(display, "", 0, False) else: print("Running in offline mode") # 即使离线也进入录音界面(虽然不能用) ui_screen = UI_SCREEN_RECORDING if display.tft: - render_recording_screen(display, "离线模式", 0) + render_recording_screen(display, "离线模式", 0, False) read_buf = bytearray(4096) last_audio_level = 0 memory_check_counter = 0 spinner_angle = 0 last_spinner_time = 0 + wait_for_release = False while True: try: @@ -544,134 +548,67 @@ def main(): btn_action = get_boot_button_action(boot_btn) - if btn_action == 1: - if is_recording: - print(">>> Stop recording") - if ws and ws.is_connected(): - try: - 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 = "" + # Hold to Record Logic (Press to Start, Release to Stop) + if ui_screen == UI_SCREEN_RECORDING: + if boot_btn.value() == 0 and not is_recording: + print(">>> Start recording (Hold)") + is_recording = True 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_prompt = "" current_status = "" image_generation_done = False - confirm_waiting = False - 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: print(">>> Config mode") @@ -684,82 +621,58 @@ def main(): 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: ws = None - if ui_screen == UI_SCREEN_RESULT and ws and ws.is_connected(): - try: - poller = uselect.poll() - poller.register(ws.sock, uselect.POLLIN) - events = poller.poll(100) - if events: - msg = ws.recv() - if msg: - 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] + # 在录音结束后(CONFIRM状态)或 RESULT 状态,才接收消息 + if (ui_screen == UI_SCREEN_CONFIRM or ui_screen == UI_SCREEN_RESULT or ui_screen == UI_SCREEN_RECORDING) and not is_recording: + if ws and ws.is_connected(): + try: + poller = uselect.poll() + poller.register(ws.sock, uselect.POLLIN) + events = poller.poll(100) + if events: + msg = ws.recv() + if msg: + image_state, event_data = process_message(msg, display, image_state, image_data_list) - 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] - if display.tft: - render_result_screen(display, current_status, current_prompt, image_generation_done) - - 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: - pass + if event_data: + if event_data[0] == "asr": + current_asr_text = event_data[1] + print(f"Received ASR: {current_asr_text}") + + # 收到 ASR 结果,跳转到 CONFIRM 界面 + if ui_screen == UI_SCREEN_RECORDING or ui_screen == UI_SCREEN_CONFIRM: + ui_screen = UI_SCREEN_CONFIRM + if display.tft: + render_confirm_screen(display, current_asr_text) + + elif event_data[0] == "font_update": + # 如果还在录音界面等待,刷新一下(虽然可能已经跳到 CONFIRM 了) + pass + + elif event_data[0] == "status": + current_status = event_data[1] + status_text = event_data[2] if len(event_data) > 2 else "" + if display.tft and ui_screen == UI_SCREEN_RESULT: + render_result_screen(display, current_status, current_prompt, image_generation_done) + + 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) except Exception as e: diff --git a/static_font_data.py b/static_font_data.py index 8676595..10183ef 100644 --- a/static_font_data.py +++ b/static_font_data.py @@ -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', # 停 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', # 到 + 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', # 化 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', # 回 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', # 失 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', # 完 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', # 录 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', # 成 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', # 提 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', # 未 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', # 止 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', # 生 @@ -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', # 短 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', # 示 + 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', # 绘 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', # 认 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', # 词 + 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', # 语 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', # 败 + 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', # 返 + 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', # 重 + 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', # 长 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', # ? diff --git a/websocket_server/__pycache__/server.cpython-312.pyc b/websocket_server/__pycache__/server.cpython-312.pyc index 2205138..5d03aab 100644 Binary files a/websocket_server/__pycache__/server.cpython-312.pyc and b/websocket_server/__pycache__/server.cpython-312.pyc differ diff --git a/websocket_server/generate_static_font.py b/websocket_server/generate_static_font.py index b65a18b..5dcc512 100644 --- a/websocket_server/generate_static_font.py +++ b/websocket_server/generate_static_font.py @@ -21,7 +21,18 @@ FIXED_STRINGS = [ "生成完成!", "生成失败", "提示词:", - "返回录音" + "返回录音", + "重试", + "长按返回", + "连接服务器...", + "服务器连接失败", + "离线模式", + "量迹AI贴纸生成", + "正在启动...", + "WiFi连接中...", + "WiFi连接成功!", + "WiFi连接失败", + "请重试" ] def generate_static_font(): diff --git a/websocket_server/generated_thumb.bin b/websocket_server/generated_thumb.bin index b2cc009..867e843 100644 Binary files a/websocket_server/generated_thumb.bin and b/websocket_server/generated_thumb.bin differ diff --git a/websocket_server/received_audio.mp3 b/websocket_server/received_audio.mp3 index 660aefd..4c0a783 100644 Binary files a/websocket_server/received_audio.mp3 and b/websocket_server/received_audio.mp3 differ diff --git a/websocket_server/received_audio.raw b/websocket_server/received_audio.raw index 4c076b4..ba5119c 100644 Binary files a/websocket_server/received_audio.raw and b/websocket_server/received_audio.raw differ diff --git a/websocket_server/server.py b/websocket_server/server.py index 24856f7..94e1ab1 100644 --- a/websocket_server/server.py +++ b/websocket_server/server.py @@ -851,6 +851,17 @@ async def websocket_endpoint(websocket: WebSocket): # 4. 如果有识别结果,发送ASR文字到ESP32 if 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 显示 await websocket.send_text(f"ASR:{asr_text}")