168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
|
|
#!/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()
|