#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ text_dgceshi.py — 写死 5 组表情(已按 XML 限位约束),每 5 秒切换,循环发送到 ESP32 依赖:pip install pyserial 用法: python text_dgceshi.py /dev/ttyUSB0 [baudrate] 帧格式(与你现有固件一致): [0] 0xAA [1] count = 指令条数(每条是5字节:addr, ch, ang, durL, durH) [2..] 指令序列,共 count*5 字节 [end] XOR 校验(对前面所有字节异或) """ import sys import time import serial FRAME_HEADER = 0xAA def calc_checksum(data: bytearray) -> int: """XOR 校验""" cs = 0 for b in data: cs ^= b return cs & 0xFF def pack_pose(raw_list): """ raw_list: [addr, ch, ang, durL, durH, addr, ch, ang, durL, durH, ...] 返回带帧头/数量/校验的完整数据包 bytes """ assert len(raw_list) % 5 == 0, "原始列表长度必须是 5 的倍数" count = len(raw_list) // 5 pkt = bytearray([FRAME_HEADER, count & 0xFF]) pkt.extend(int(x) & 0xFF for x in raw_list) pkt.append(calc_checksum(pkt)) return bytes(pkt) def print_packet(pkt): print("发送数据包:", ' '.join(f"0x{b:02X}" for b in pkt)) def wakeup_esp32(port, baud=115200): """通过 RTS/DTR 尝试唤醒 ESP32(可选)""" try: ser = serial.Serial(port, baud, timeout=1) ser.setDTR(False) ser.setRTS(False) time.sleep(0.02) ser.setDTR(True) ser.setRTS(True) ser.write(b"\r\n") time.sleep(0.2) except Exception as e: print(f"[WARN] 唤醒失败:{e}") finally: try: ser.close() except: pass def open_serial(port, baud): try: ser = serial.Serial(port, baud, timeout=1) print(f"已打开串口: {port} @ {baud}bps") return ser except Exception as e: print(f"打开串口失败: {e}") sys.exit(1) # ======================= # 五组“写死”的表情数据(严格遵守你的 XML 安全限位) # 64:0~3 眉(offset 90/90/90/90,限位 45~120/…/50~120) # 64:4~9 眼(offset 90..,限位 [90~170, 10~90, 10~90, 90~170, 10~170, 10~170]) # 65:0~9 嘴(offset 90..,限位 [60~130, 60~130, 50~120, 50~130, 50~100, 50~130, 50~130, 90~120, 91~120, 60~89]) # duration 全部为 0(立即执行);如需时长,可把毫秒拆成 durL/durH 小端写入 # ======================= POSES_RAW = [ # # Pose 1 — 中性位置(全部 offset) [ # 板 64 — 眼眉 0~3 64,0,90,0,0, 64,1,90,0,0, 64,2,90,0,0, 64,3,90,0,0, # 板 64 — 眼睛 4~9(选在各自安全范围内的中值/视觉舒适值) 64,4,160,0,0, 64,5,20,0,0, 64,6,20,0,0, 64,7,160,0,0, 64,8,150,0,0, 64,9,150,0,0, # 板 65 — 嘴巴 0~9 65,0,90,0,0, 65,1,90,0,0, 65,2,85,0,0, 65,3,90,0,0, 65,4,90,0,0, 65,5,90,0,0, 65,6,90,0,0, 65,7,90,0,0, 65,8,90,0,0, 65,9,90,0,0 ], # Pose 5 — 大笑(接近上限但不越界) [ 64,0,115,0,0, 64,1,75,0,0, 64,2,75,0,0, 64,3,115,0,0, 64,4,160,0,0, 64,5,20,0,0, 64,6,20,0,0, 64,7,160,0,0, 64,8,50,0,0, 64,9,40,0,0, 65,0,50,0,0, 65,1,110,0,0, 65,2,70,0,0, 65,3,50,0,0, 65,4,100,0,0, 65,5,110,0,0, 65,6,130,0,0, 65,7,90,0,0, 65,8,110,0,0, 65,9,70,0,0 ], [ 64,0,115,0,0, 64,1,75,0,0, 64,2,75,0,0, 64,3,115,0,0, 64,4,160,0,0, 64,5,20,0,0, 64,6,20,0,0, 64,7,160,0,0, 64,8,50,0,0, 64,9,130,0,0, 65,0,50,0,0, 65,1,110,0,0, 65,2,70,0,0, 65,3,50,0,0, 65,4,90,0,0, 65,5,110,0,0, 65,6,130,0,0, 65,7,90,0,0, 65,8,90,0,0, 65,9,90,0,0 ], [ 64,0,115,0,0, 64,1,75,0,0, 64,2,75,0,0, 64,3,115,0,0, 64,4,160,0,0, 64,5,20,0,0, 64,6,20,0,0, 64,7,160,0,0, 64,8,50,0,0, 64,9,40,0,0, 65,0,50,0,0, 65,1,110,0,0, 65,2,70,0,0, 65,3,50,0,0, 65,4,90,0,0, 65,5,110,0,0, 65,6,130,0,0, 65,7,90,0,0, 65,8,110,0,0, 65,9,70,0,0 ] ] POSES_RAW1 = [[ # 板 64 — 眼眉 0~3 64,0,90,0,0, 64,1,90,0,0, 64,2,90,0,0, 64,3,90,0,0, # 板 64 — 眼睛 4~9(选在各自安全范围内的中值/视觉舒适值) 64,4,90,0,0, 64,5,90,0,0, 64,6,90,0,0, 64,7,90,0,0, 64,8,150,0,0, 64,9,150,0,0, # 板 65 — 嘴巴 0~9 65,0,90,0,0, 65,1,90,0,0, 65,2,85,0,0, 65,3,90,0,0, 65,4,90,0,0, 65,5,90,0,0, 65,6,90,0,0, 65,7,90,0,0, 65,8,90,0,0, 65,9,90,0,0 ], # Pose 5 — 大笑(接近上限但不越界) [ 64,0,115,0,0, 64,1,75,0,0, 64,2,75,0,0, 64,3,115,0,0, 64,4,160,0,0, 64,5,20,0,0, 64,6,20,0,0, 64,7,160,0,0, 64,8,50,0,0, 64,9,40,0,0, 65,0,50,0,0, 65,1,110,0,0, 65,2,70,0,0, 65,3,50,0,0, 65,4,100,0,0, 65,5,110,0,0, 65,6,130,0,0, 65,7,90,0,0, 65,8,110,0,0, 65,9,70,0,0 ], ] def main(): if len(sys.argv) < 2: print(f"用法: {sys.argv[0]} /dev/ttyUSB0 [baudrate]") sys.exit(1) port = sys.argv[1] baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200 # 可选:尝试唤醒 ESP32 wakeup_esp32(port, baud) ser = open_serial(port, baud) try: pose_idx = 0 hold_seconds = 5 print(f"开始循环播放 5 组表情,每组间隔 {hold_seconds} 秒...") while True: pose = POSES_RAW[pose_idx] pkt = pack_pose(pose) print(f"\n→ 发送 Pose {pose_idx+1}/{len(POSES_RAW)}") print_packet(pkt) ser.write(pkt) ser.flush() time.sleep(hold_seconds) pose_idx = (pose_idx + 1) % len(POSES_RAW) except KeyboardInterrupt: print("\n用户中断,退出") finally: ser.close() print("串口已关闭") if __name__ == "__main__": main()