初始化
This commit is contained in:
BIN
__pycache__/ir_send.cpython-313.pyc
Normal file
BIN
__pycache__/ir_send.cpython-313.pyc
Normal file
Binary file not shown.
362
ir_bruteforce.py
Normal file
362
ir_bruteforce.py
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
红外板 — 发送协议穷举测试
|
||||||
|
========================
|
||||||
|
从接收已知: 客户码=3BC4, 方向键=0D/10/15/12
|
||||||
|
逐个尝试常见红外板发送格式,找出正确的那一个。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python ir_bruteforce.py -p COM8 # 自动试探所有协议
|
||||||
|
python ir_bruteforce.py -p COM8 -k 0D # 指定键值测试
|
||||||
|
python ir_bruteforce.py -p COM8 --list # 列出所有候选协议
|
||||||
|
|
||||||
|
每次发送后观察投影仪是否有反应,按 y/n 确认,脚本记录有效协议。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
BAUDRATE = 9600
|
||||||
|
|
||||||
|
# 已知参数
|
||||||
|
CUSTOMER_CODE = bytes([0x3B, 0xC4]) # 客户码
|
||||||
|
KEY_CODES = {
|
||||||
|
"up": 0x0D, # 上
|
||||||
|
"left": 0x10, # 左
|
||||||
|
"down": 0x15, # 下
|
||||||
|
"right": 0x12, # 右
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_frame(proto_name: str, key: int) -> bytes | None:
|
||||||
|
"""
|
||||||
|
根据协议名构建发送帧。
|
||||||
|
|
||||||
|
常见红外板发送协议:
|
||||||
|
- raw3: 直接 3 字节 [客户码H, 客户码L, 键值]
|
||||||
|
- nec_std: NEC 标准帧 [地址, ~地址, 命令, ~命令]
|
||||||
|
- nec_3B: 地址 0x3B (单字节) 的 NEC [0x3B, 0xC4, key, ~key]
|
||||||
|
- a5_prefix: A5 前缀协议 [A5, len, 数据..., checksum]
|
||||||
|
- aa_prefix: AA 前缀协议 [AA, 数据..., sum]
|
||||||
|
- text_crlf: 文本协议 "SEND xxxxxx\\r\\n"
|
||||||
|
- text_no_cr: 文本协议 "3BC40D"
|
||||||
|
- at_cmd: AT 指令 "AT+IR=xxxxxx\\r\\n"
|
||||||
|
- nec_full: NEC 完整帧 [addrL, addrH, cmd, ~cmd] (带反码)
|
||||||
|
"""
|
||||||
|
|
||||||
|
hi, lo = CUSTOMER_CODE[0], CUSTOMER_CODE[1]
|
||||||
|
|
||||||
|
# ── 原始字节类 ──
|
||||||
|
if proto_name == "raw3":
|
||||||
|
return bytes([hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "raw3_reversed":
|
||||||
|
return bytes([key, lo, hi])
|
||||||
|
|
||||||
|
if proto_name == "raw3_with_cr":
|
||||||
|
return bytes([hi, lo, key, 0x0D])
|
||||||
|
|
||||||
|
if proto_name == "raw3_with_crlf":
|
||||||
|
return bytes([hi, lo, key, 0x0D, 0x0A])
|
||||||
|
|
||||||
|
# ── NEC 标准变体 ──
|
||||||
|
if proto_name == "nec_3bytes":
|
||||||
|
# 最简 NEC: 地址 + 命令 (不带反码)
|
||||||
|
return bytes([hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "nec_addr_inv":
|
||||||
|
# NEC: [地址, ~地址, 命令, ~命令]
|
||||||
|
return bytes([hi, hi ^ 0xFF, key, key ^ 0xFF])
|
||||||
|
|
||||||
|
if proto_name == "nec_addr_inv_both":
|
||||||
|
# NEC 有时用两字节地址: [addrL, addrH, cmd, ~cmd]
|
||||||
|
return bytes([lo, hi, key, key ^ 0xFF])
|
||||||
|
|
||||||
|
if proto_name == "nec_no_invert":
|
||||||
|
# 只发地址+命令,不做反码
|
||||||
|
return bytes([lo, key])
|
||||||
|
|
||||||
|
if proto_name == "nec_lo_cmd":
|
||||||
|
# [0x3B, key] — 只有客户码低字节+键值
|
||||||
|
return bytes([lo, key])
|
||||||
|
|
||||||
|
if proto_name == "nec_hi_cmd":
|
||||||
|
# [0x3B, 0xC4, key] — raw3 即此格式
|
||||||
|
return bytes([hi, key])
|
||||||
|
|
||||||
|
# ── 带前缀/帧头 ──
|
||||||
|
if proto_name == "a5_f5":
|
||||||
|
# 常见: [A5, len, data..., sum]
|
||||||
|
data = bytes([hi, lo, key])
|
||||||
|
frame = bytes([0xA5, len(data)]) + data
|
||||||
|
csum = sum(frame) & 0xFF
|
||||||
|
return frame + bytes([csum])
|
||||||
|
|
||||||
|
if proto_name == "a5_f5_nosum":
|
||||||
|
data = bytes([hi, lo, key])
|
||||||
|
return bytes([0xA5, len(data)]) + data
|
||||||
|
|
||||||
|
if proto_name == "aa_55":
|
||||||
|
data = bytes([hi, lo, key])
|
||||||
|
csum = sum(data) & 0xFF
|
||||||
|
return bytes([0xAA, 0x55]) + data + bytes([csum])
|
||||||
|
|
||||||
|
if proto_name == "aa_55_nosum":
|
||||||
|
return bytes([0xAA, 0x55, hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "ff_prefix":
|
||||||
|
return bytes([0xFF, 0xFF, hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "fe_prefix":
|
||||||
|
return bytes([0xFE, hi, lo, key, 0xFD])
|
||||||
|
|
||||||
|
if proto_name == "a0_prefix":
|
||||||
|
return bytes([0xA0, hi, lo, key, 0x0A])
|
||||||
|
|
||||||
|
# ── 文本协议类 ──
|
||||||
|
hex_str = f"{hi:02X}{lo:02X}{key:02X}"
|
||||||
|
|
||||||
|
if proto_name == "text_hex":
|
||||||
|
return hex_str.encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_hex_cr":
|
||||||
|
return (hex_str + "\r").encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_hex_crlf":
|
||||||
|
return (hex_str + "\r\n").encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_hex_nl":
|
||||||
|
return (hex_str + "\n").encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_send":
|
||||||
|
return f"SEND {hex_str}".encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_send_crlf":
|
||||||
|
return f"IR_SEND={hex_str}\r\n".encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_at_crlf":
|
||||||
|
return f"AT+IR={hex_str}\r\n".encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_at_send_crlf":
|
||||||
|
return f"AT+SEND={hex_str}\r\n".encode("ascii")
|
||||||
|
|
||||||
|
# ── 带空格分隔的十六进制文本 ──
|
||||||
|
if proto_name == "text_spaced_crlf":
|
||||||
|
return f"{hi:02X} {lo:02X} {key:02X}\r\n".encode("ascii")
|
||||||
|
|
||||||
|
if proto_name == "text_spaced_nl":
|
||||||
|
return f"{hi:02X} {lo:02X} {key:02X}\n".encode("ascii")
|
||||||
|
|
||||||
|
# ── 先发命令再发数据 ──
|
||||||
|
if proto_name == "cmd_tx_data":
|
||||||
|
# 先发 TX 命令,延时 50ms,再发数据
|
||||||
|
return None # 特殊处理
|
||||||
|
|
||||||
|
if proto_name == "cmd_0x01_prefix":
|
||||||
|
return bytes([0x01, hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "cmd_0x02_prefix":
|
||||||
|
return bytes([0x02, hi, lo, key])
|
||||||
|
|
||||||
|
if proto_name == "cmd_0x03_prefix":
|
||||||
|
return bytes([0x03, hi, lo, key])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 所有候选协议 (按可能性排序)
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
PROTOCOLS = [
|
||||||
|
# ── 最可能的: 二进制直发 ──
|
||||||
|
("raw3", "二进制3字节 [3B C4 XX]"),
|
||||||
|
("raw3_reversed", "二进制3字节反转 [XX C4 3B]"),
|
||||||
|
("raw3_with_cr", "二进制3字节 + CR [3B C4 XX 0D]"),
|
||||||
|
("raw3_with_crlf", "二进制3字节 + CRLF [3B C4 XX 0D 0A]"),
|
||||||
|
|
||||||
|
# ── NEC 变体 ──
|
||||||
|
("nec_3bytes", "NEC简化 3字节 [3B C4 XX]"),
|
||||||
|
("nec_addr_inv", "NEC标准 [addr, ~addr, cmd, ~cmd]"),
|
||||||
|
("nec_addr_inv_both", "NEC双字节地址 [C4 3B XX ~XX]"),
|
||||||
|
("nec_lo_cmd", "NEC短帧 [C4 XX]"),
|
||||||
|
("nec_hi_cmd", "NEC短帧 [3B XX]"),
|
||||||
|
("nec_no_invert", "NEC无反码 [C4 XX]"),
|
||||||
|
|
||||||
|
# ── 带帧头 ──
|
||||||
|
("a5_f5", "A5帧头+校验 [A5 03 3B C4 XX SUM]"),
|
||||||
|
("a5_f5_nosum", "A5帧头无校验 [A5 03 3B C4 XX]"),
|
||||||
|
("aa_55", "AA55帧头 [AA 55 3B C4 XX SUM]"),
|
||||||
|
("aa_55_nosum", "AA55帧头无校验 [AA 55 3B C4 XX]"),
|
||||||
|
("ff_prefix", "FF前缀 [FF FF 3B C4 XX]"),
|
||||||
|
("fe_prefix", "FE帧 [FE 3B C4 XX FD]"),
|
||||||
|
("a0_prefix", "A0前缀 [A0 3B C4 XX 0A]"),
|
||||||
|
("cmd_0x01_prefix", "01前缀 [01 3B C4 XX]"),
|
||||||
|
("cmd_0x02_prefix", "02前缀 [02 3B C4 XX]"),
|
||||||
|
("cmd_0x03_prefix", "03前缀 [03 3B C4 XX]"),
|
||||||
|
|
||||||
|
# ── 文本协议 ──
|
||||||
|
("text_hex", "文本HEX \"3BC40D\""),
|
||||||
|
("text_hex_cr", "文本HEX+CR"),
|
||||||
|
("text_hex_crlf", "文本HEX+CRLF"),
|
||||||
|
("text_hex_nl", "文本HEX+LF"),
|
||||||
|
("text_spaced_crlf", "文本空格HEX \"3B C4 0D\\r\\n\""),
|
||||||
|
("text_spaced_nl", "文本空格HEX \"3B C4 0D\\n\""),
|
||||||
|
("text_send", "文本 \"SEND 3BC40D\""),
|
||||||
|
("text_send_crlf", "文本 \"IR_SEND=3BC40D\\r\\n\""),
|
||||||
|
("text_at_crlf", "AT指令 \"AT+IR=3BC40D\\r\\n\""),
|
||||||
|
("text_at_send_crlf", "AT指令 \"AT+SEND=3BC40D\\r\\n\""),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def scan_usb_ttl():
|
||||||
|
"""自动找 USB-to-TTL 端口"""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
keywords = ["ch343", "ch340", "ch341", "usb-enhanced-serial",
|
||||||
|
"usb serial", "ft232", "cp210", "pl2303", "wch"]
|
||||||
|
for p in ports:
|
||||||
|
combined = f"{p.description or ''} {p.hwid or ''}".lower()
|
||||||
|
if any(kw in combined for kw in keywords):
|
||||||
|
return p.device
|
||||||
|
all_p = list(ports)
|
||||||
|
return all_p[0].device if all_p else None
|
||||||
|
|
||||||
|
|
||||||
|
def brute_force(port: str, key_hex: str = "0D", start_from: int = 0):
|
||||||
|
"""
|
||||||
|
逐个尝试所有协议。
|
||||||
|
每次发送后等待用户确认 (y=有效/n=无效/q=退出)
|
||||||
|
"""
|
||||||
|
key = int(key_hex, 16)
|
||||||
|
hi, lo = CUSTOMER_CODE[0], CUSTOMER_CODE[1]
|
||||||
|
hex_3b = f"{hi:02X}{lo:02X}{key:02X}"
|
||||||
|
|
||||||
|
print(f"串口: {port} @ {BAUDRATE}bps")
|
||||||
|
print(f"客户码: {hi:02X} {lo:02X}")
|
||||||
|
print(f"测试键值: {key:02X} ({hex_3b})")
|
||||||
|
print(f"候选协议: {len(PROTOCOLS)} 个\n")
|
||||||
|
|
||||||
|
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.3)
|
||||||
|
print("=" * 65)
|
||||||
|
print(" 发送后观察投影仪是否响应,输入 y/n/q")
|
||||||
|
print("=" * 65)
|
||||||
|
|
||||||
|
working = []
|
||||||
|
total = len(PROTOCOLS)
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
for i, (name, desc) in enumerate(PROTOCOLS):
|
||||||
|
if i < start_from:
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
frame = build_frame(name, key)
|
||||||
|
|
||||||
|
# 特殊协议: 先发送 "TX" 命令再发数据
|
||||||
|
if name == "cmd_tx_data":
|
||||||
|
try:
|
||||||
|
ser.write(b"TX\r\n")
|
||||||
|
ser.flush()
|
||||||
|
time.sleep(0.05)
|
||||||
|
ser.write(bytes([hi, lo, key]))
|
||||||
|
ser.flush()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [{i+1}/{total}] {name}: 发送失败 - {e}")
|
||||||
|
continue
|
||||||
|
print(f"\n[{i+1}/{total}] {name}")
|
||||||
|
print(f" 描述: {desc}")
|
||||||
|
print(f" 发送: TX\\r\\n → [3B C4 {key:02X}]")
|
||||||
|
|
||||||
|
elif frame is not None:
|
||||||
|
try:
|
||||||
|
ser.reset_output_buffer()
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" [{i+1}/{total}] {name}: 发送失败 - {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"\n[{i+1}/{total}] {name}")
|
||||||
|
print(f" 描述: {desc}")
|
||||||
|
print(f" HEX: {frame.hex(' ').upper()}")
|
||||||
|
if all(32 <= b < 127 for b in frame):
|
||||||
|
print(f" ASCII: {frame.decode('ascii')}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 用户确认
|
||||||
|
while True:
|
||||||
|
resp = input(" 有效? [y/n/q/s(跳过)]: ").strip().lower()
|
||||||
|
if resp == 'y':
|
||||||
|
working.append((name, desc, frame.hex(" ").upper() if frame else "TX+data"))
|
||||||
|
print(f" ✓ 已记录")
|
||||||
|
break
|
||||||
|
elif resp == 'n':
|
||||||
|
break
|
||||||
|
elif resp == 'q':
|
||||||
|
print(f"\n已测试 {i+1-skipped}/{total}")
|
||||||
|
ser.close()
|
||||||
|
return working
|
||||||
|
elif resp == 's':
|
||||||
|
print(f" 跳过")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(" 请输入 y(有效) / n(无效) / q(退出)")
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
print(f"\n测试完成: {i+1-skipped}/{total}")
|
||||||
|
return working
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="红外板发送协议 — 穷举测试",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument("-p", "--port", default=None, help="串口 (如 COM8)")
|
||||||
|
parser.add_argument("-k", "--key", default="0D",
|
||||||
|
help=f"测试键值 hex (默认 0D=上). 已知: {', '.join(f'{k}={v:02X}' for k,v in KEY_CODES.items())}")
|
||||||
|
parser.add_argument("-s", "--start", type=int, default=0,
|
||||||
|
help="从第 N 个协议开始 (断点续测)")
|
||||||
|
parser.add_argument("--list", action="store_true", help="列出所有候选协议")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
print("候选发送协议:")
|
||||||
|
for i, (name, desc) in enumerate(PROTOCOLS):
|
||||||
|
print(f" [{i+1:>2}] {name:<22} {desc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
port = args.port or scan_usb_ttl()
|
||||||
|
if not port:
|
||||||
|
print("✗ 未找到串口。用 -p 指定")
|
||||||
|
return
|
||||||
|
|
||||||
|
results = brute_force(port, args.key, start_from=args.start)
|
||||||
|
|
||||||
|
if results:
|
||||||
|
print("\n" + "=" * 65)
|
||||||
|
print(" 有效协议:")
|
||||||
|
for name, desc, frame_hex in results:
|
||||||
|
print(f" ✓ {name}: {desc}")
|
||||||
|
print(f" HEX: {frame_hex}")
|
||||||
|
else:
|
||||||
|
print("\n✗ 没有找到有效协议。")
|
||||||
|
print(" 可能原因:")
|
||||||
|
print(" 1. 波特率不是 9600 — 用 --auto-baud 再测")
|
||||||
|
print(" 2. 发送和接收需不同波特率")
|
||||||
|
print(' 3. 红外板需要先发送"进入发送模式"的指令')
|
||||||
|
print(" 4. 换个键值试试 -k 15")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
316
ir_diag.py
Normal file
316
ir_diag.py
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
红外板 — 收发联合诊断
|
||||||
|
====================
|
||||||
|
一边发送候选协议帧,一边同时读取串口返回,
|
||||||
|
看板子收到数据后有没有任何回应(ACK/NAK/回显等)。
|
||||||
|
|
||||||
|
同时抓取原始遥控信号的完整原始字节(不合并,带时间戳),
|
||||||
|
检查是否有被 IDLE_TIMEOUT 截断的帧头/帧尾。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python ir_diag.py -p COM8 # 诊断模式1: 重新抓遥控信号
|
||||||
|
python ir_diag.py -p COM8 --echo # 诊断模式2: 发送+监听回应
|
||||||
|
python ir_diag.py -p COM8 --boot # 诊断模式3: 捕获上电启动信息
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
BAUDRATE = 9600
|
||||||
|
|
||||||
|
# 已知数据
|
||||||
|
CUSTOMER_HI = 0x3B
|
||||||
|
CUSTOMER_LO = 0xC4
|
||||||
|
KEY_CODES = {0x0D: "上", 0x15: "下", 0x10: "左", 0x12: "右"}
|
||||||
|
|
||||||
|
|
||||||
|
def find_port():
|
||||||
|
"""自动找 USB-to-TTL"""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
keywords = ["ch343", "ch340", "ch341", "usb-enhanced-serial",
|
||||||
|
"usb serial", "ft232", "cp210", "pl2303", "wch"]
|
||||||
|
for p in ports:
|
||||||
|
combined = f"{p.description or ''} {p.hwid or ''}".lower()
|
||||||
|
if any(kw in combined for kw in keywords):
|
||||||
|
return p.device
|
||||||
|
all_p = list(ports)
|
||||||
|
return all_p[0].device if all_p else None
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 模式1: 详细捕获原始遥控信号 (逐字节, 不合并)
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def capture_remote_detail(port: str):
|
||||||
|
"""
|
||||||
|
逐字节捕获,每个字节都带时间戳。
|
||||||
|
可能发现: 帧头(如 AA 55)、长度字节、校验和等之前被忽略的字节。
|
||||||
|
"""
|
||||||
|
print(f"打开 {port} @ {BAUDRATE} baud")
|
||||||
|
print("请按遥控器按键... (Ctrl+C 退出)\n")
|
||||||
|
|
||||||
|
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||||
|
print(f"{'时间':<12} {'字节':<6} {'HEX':<8} {'BIN':<12} {'ASCII'}")
|
||||||
|
print("-" * 65)
|
||||||
|
|
||||||
|
last_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
n = ser.in_waiting
|
||||||
|
except serial.SerialException:
|
||||||
|
break
|
||||||
|
|
||||||
|
if n > 0:
|
||||||
|
now = time.time()
|
||||||
|
gap = now - last_time
|
||||||
|
data = ser.read(n)
|
||||||
|
|
||||||
|
for i, b in enumerate(data):
|
||||||
|
ascii_ch = chr(b) if 32 <= b < 127 else "."
|
||||||
|
ts = time.strftime("%H:%M:%S") + f".{int((now % 1) * 1000):03d}"
|
||||||
|
|
||||||
|
if i == 0 and gap > 0.3:
|
||||||
|
# 新的红外信号 — 画分隔线
|
||||||
|
print(f"\n{'─' * 65}")
|
||||||
|
print(f" 新信号 ↑ 距上次 {gap:.1f}s")
|
||||||
|
print(f"{ts:<12} [{i+1}/{n}] {b:02X} {b:08b} {ascii_ch}")
|
||||||
|
|
||||||
|
last_time = now
|
||||||
|
else:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
print("\n✓ 诊断完成。检查上面的字节序列:")
|
||||||
|
print(" 1. 每次按键是否只有 3 个字节?")
|
||||||
|
print(" 2. 前后有没有额外的帧头/帧尾字节?")
|
||||||
|
print(" 3. 字节间有没有明显的停顿(说明是分帧发送)?")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 模式2: 发送并监听回应
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# 候选发送协议 — 带"编号"以便快速定位有效协议
|
||||||
|
CANDIDATES = [
|
||||||
|
# (名称, 帧bytes)
|
||||||
|
("raw_3bytes", bytes([0x3B, 0xC4, 0x0D])),
|
||||||
|
("raw_3bytes_rev", bytes([0x0D, 0xC4, 0x3B])),
|
||||||
|
("raw_plus_CR", bytes([0x3B, 0xC4, 0x0D, 0x0D])),
|
||||||
|
("raw_plus_CRLF", bytes([0x3B, 0xC4, 0x0D, 0x0D, 0x0A])),
|
||||||
|
("NEC_std", bytes([0x3B, 0xC4, 0x0D, 0xF2])),
|
||||||
|
("NEC_addr_inv", bytes([0x3B, 0xC4 ^ 0xFF, 0x0D, 0x0D ^ 0xFF])),
|
||||||
|
("A5_frame", bytes([0xA5, 0x03, 0x3B, 0xC4, 0x0D])),
|
||||||
|
("A5_frame_sum", bytes([0xA5, 0x03, 0x3B, 0xC4, 0x0D, 0xE9])),
|
||||||
|
("AA55_frame", bytes([0xAA, 0x55, 0x3B, 0xC4, 0x0D])),
|
||||||
|
("AA55_frame_sum", bytes([0xAA, 0x55, 0x3B, 0xC4, 0x0D, 0xA2])),
|
||||||
|
("FF_prefix", bytes([0xFF, 0xFF, 0x3B, 0xC4, 0x0D])),
|
||||||
|
("FE_FD_frame", bytes([0xFE, 0x3B, 0xC4, 0x0D, 0xFD])),
|
||||||
|
("A0_prefix", bytes([0xA0, 0x3B, 0xC4, 0x0D, 0x0A])),
|
||||||
|
("01_prefix", bytes([0x01, 0x3B, 0xC4, 0x0D])),
|
||||||
|
("AT_send_crlf", b"AT+SEND=3BC40D\r\n"),
|
||||||
|
("text_hex_crlf", b"3BC40D\r\n"),
|
||||||
|
("text_spaced_crlf", b"3B C4 0D\r\n"),
|
||||||
|
("text_send_crlf", b"SEND 3BC40D\r\n"),
|
||||||
|
("text_at_crlf", b"AT+IR=3BC40D\r\n"),
|
||||||
|
("IR_SEND_crlf", b"IR_SEND=3BC40D\r\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def send_and_listen(port: str):
|
||||||
|
"""
|
||||||
|
发送每个候选帧,然后立即监听串口回应。
|
||||||
|
使用非阻塞读取: 发完等100ms,把期间收到的所有字节打印出来。
|
||||||
|
"""
|
||||||
|
print(f"打开 {port} @ {BAUDRATE} baud")
|
||||||
|
print("收发联合诊断 — 每个协议发送后监听板子是否有回应\n")
|
||||||
|
|
||||||
|
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||||
|
|
||||||
|
for i, (name, frame) in enumerate(CANDIDATES):
|
||||||
|
# 清空接收缓冲
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.reset_output_buffer()
|
||||||
|
|
||||||
|
# 发送
|
||||||
|
try:
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
except serial.SerialException as e:
|
||||||
|
print(f" [{i+1:>2}] {name}: ✗ 发送失败 — {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 等待回应 (给板子足够的处理时间)
|
||||||
|
time.sleep(0.15)
|
||||||
|
|
||||||
|
# 读取所有可用数据
|
||||||
|
response = bytearray()
|
||||||
|
while True:
|
||||||
|
n = ser.in_waiting
|
||||||
|
if n == 0:
|
||||||
|
break
|
||||||
|
response.extend(ser.read(n))
|
||||||
|
time.sleep(0.02)
|
||||||
|
|
||||||
|
# 打印结果
|
||||||
|
resp_str = ""
|
||||||
|
if response:
|
||||||
|
resp_str = f"← 板子回应 {len(response)}B: {response.hex(' ').upper()}"
|
||||||
|
try:
|
||||||
|
ascii_str = response.decode("ascii", errors="replace").strip()
|
||||||
|
if ascii_str:
|
||||||
|
resp_str += f" [{ascii_str}]"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f" [{i+1:>2}] {name}")
|
||||||
|
print(f" 发送: {frame.hex(' ').upper()}")
|
||||||
|
if resp_str:
|
||||||
|
print(f" {resp_str}")
|
||||||
|
else:
|
||||||
|
print(f" (板子无回应)")
|
||||||
|
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
print("\n" + "=" * 65)
|
||||||
|
print("分析要点:")
|
||||||
|
print(" 1. 有回应的 → 可能是板子返回了 ACK/NAK/回显,说明协议接近")
|
||||||
|
print(" 2. 回应 = 发送内容 → 回显模式,说明板子期望特定格式")
|
||||||
|
print(" 3. 回应包含 'OK'/'ERROR' 等 → 文本协议")
|
||||||
|
print(" 4. 全无回应 → 要么协议全错,要么板子是单向的(不回显)")
|
||||||
|
print("\n提示: 同时也用手机摄像头看红外LED是否发光")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 模式3: 上电启动捕获
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def capture_boot(port: str):
|
||||||
|
"""
|
||||||
|
打开串口后立即开始接收,看板子有没有上电启动信息。
|
||||||
|
也尝试切换 DTR (部分板子用 DTR 供电/复位)。
|
||||||
|
"""
|
||||||
|
print("上电诊断 — 捕获板子启动信息\n")
|
||||||
|
print("提示: 拔掉 USB-to-TTL 再插入,然后按 Enter 继续...")
|
||||||
|
input()
|
||||||
|
|
||||||
|
# 先用 DTR 低电平"复位"
|
||||||
|
try:
|
||||||
|
ser = serial.Serial(port, baudrate=BAUDRATE, timeout=0.1)
|
||||||
|
ser.dtr = False
|
||||||
|
time.sleep(0.5)
|
||||||
|
ser.dtr = True
|
||||||
|
print(f"已打开 {port} (DTR 复位完成)")
|
||||||
|
except serial.SerialException as e:
|
||||||
|
print(f"✗ 无法打开 {port}: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("等待板子启动输出... (10s)\n")
|
||||||
|
|
||||||
|
buffer = bytearray()
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while time.time() - start < 10:
|
||||||
|
n = ser.in_waiting
|
||||||
|
if n > 0:
|
||||||
|
data = ser.read(n)
|
||||||
|
buffer.extend(data)
|
||||||
|
# 有数据就打印
|
||||||
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
print(f" [{ts}] {data.hex(' ').upper()}", end="")
|
||||||
|
try:
|
||||||
|
ascii_str = data.decode("ascii", errors="replace").strip()
|
||||||
|
if ascii_str:
|
||||||
|
print(f" [{ascii_str}]")
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
except Exception:
|
||||||
|
print()
|
||||||
|
start = time.time() # 有新数据就重置计时
|
||||||
|
else:
|
||||||
|
time.sleep(0.05)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
if buffer:
|
||||||
|
print(f"\n启动阶段共收到 {len(buffer)} 字节:")
|
||||||
|
print(f" HEX: {buffer.hex(' ').upper()}")
|
||||||
|
try:
|
||||||
|
text = buffer.decode("ascii", errors="replace")
|
||||||
|
if text.strip():
|
||||||
|
print(f" ASCII: {text.strip()}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
print("\n无启动输出 — 板子不发送启动信息")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 综合诊断: 以上全部
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def run_all(port: str):
|
||||||
|
"""运行全部诊断"""
|
||||||
|
print("=" * 65)
|
||||||
|
print(" 阶段1: 上电启动捕获")
|
||||||
|
print("=" * 65)
|
||||||
|
capture_boot(port)
|
||||||
|
|
||||||
|
print("\n" + "=" * 65)
|
||||||
|
print(" 阶段2: 详细遥控信号捕获")
|
||||||
|
print("=" * 65)
|
||||||
|
capture_remote_detail(port)
|
||||||
|
|
||||||
|
print("\n" + "=" * 65)
|
||||||
|
print(" 阶段3: 发送+监听回应")
|
||||||
|
print("=" * 65)
|
||||||
|
send_and_listen(port)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="红外板 — 收发联合诊断",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument("-p", "--port", default=None, help="串口")
|
||||||
|
parser.add_argument("--boot", action="store_true", help="捕获上电启动信息")
|
||||||
|
parser.add_argument("--echo", action="store_true", help="发送候选协议+监听回应")
|
||||||
|
parser.add_argument("--capture", action="store_true", help="详细捕获遥控信号")
|
||||||
|
parser.add_argument("--all", action="store_true", help="运行全部诊断")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
port = args.port or find_port()
|
||||||
|
if not port:
|
||||||
|
print("✗ 未找到串口。请用 -p 指定。")
|
||||||
|
return
|
||||||
|
print(f"串口: {port}")
|
||||||
|
|
||||||
|
if args.all:
|
||||||
|
run_all(port)
|
||||||
|
elif args.boot:
|
||||||
|
capture_boot(port)
|
||||||
|
elif args.echo:
|
||||||
|
send_and_listen(port)
|
||||||
|
elif args.capture:
|
||||||
|
capture_remote_detail(port)
|
||||||
|
else:
|
||||||
|
# 默认: 全部运行
|
||||||
|
run_all(port)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
290
ir_receive.py
Normal file
290
ir_receive.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
红外收发板 — 接收测试
|
||||||
|
=====================
|
||||||
|
USB转TTL 连接红外收发板,接收并显示红外信号数据。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python ir_receive.py # 自动查找串口, 9600 波特率
|
||||||
|
python ir_receive.py --ports # 仅扫描串口
|
||||||
|
python ir_receive.py -p COM5 # 指定端口
|
||||||
|
python ir_receive.py -p COM5 -b 115200 # 指定端口和波特率
|
||||||
|
python ir_receive.py -p COM5 --hex-only # 仅显示十六进制
|
||||||
|
|
||||||
|
依赖: pip install pyserial
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 常见红外板串口波特率 (按优先级排列)
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
COMMON_BAUDRATES = [9600, 115200, 57600, 19200, 38400, 4800, 2400]
|
||||||
|
|
||||||
|
# CH34x / 常见 USB-to-TTL 芯片 VID:PID
|
||||||
|
USB_TTL_CHIPS = {
|
||||||
|
(0x1A86, 0x55D3): "CH343",
|
||||||
|
(0x1A86, 0x55D4): "CH344",
|
||||||
|
(0x1A86, 0x7523): "CH340",
|
||||||
|
(0x1A86, 0x5523): "CH341",
|
||||||
|
(0x1A86, 0x55D5): "CH342",
|
||||||
|
(0x0403, 0x6001): "FT232",
|
||||||
|
(0x067B, 0x2303): "PL2303",
|
||||||
|
(0x10C4, 0xEA60): "CP2102",
|
||||||
|
}
|
||||||
|
|
||||||
|
USB_TTL_KEYWORDS = [
|
||||||
|
"ch343", "ch340", "ch341", "ch342", "ch344",
|
||||||
|
"usb-enhanced-serial", "usb serial", "usb-serial",
|
||||||
|
"ft232", "ftdi", "cp210", "pl2303", "wch",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def scan_ports():
|
||||||
|
"""扫描所有串口,标注 USB-to-TTL 设备"""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
results = []
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"{'端口':<8} {'芯片':<10} {'VID:PID':<12} {'描述'}")
|
||||||
|
print("-" * 70)
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
vid_pid = (port.vid, port.pid) if port.vid and port.pid else None
|
||||||
|
chip = USB_TTL_CHIPS.get(vid_pid, "")
|
||||||
|
if not chip:
|
||||||
|
combined = f"{port.description or ''} {port.hwid or ''}".lower()
|
||||||
|
for kw in USB_TTL_KEYWORDS:
|
||||||
|
if kw in combined:
|
||||||
|
chip = "USB-TTL"
|
||||||
|
break
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"device": port.device,
|
||||||
|
"description": port.description or "",
|
||||||
|
"vid": port.vid,
|
||||||
|
"pid": port.pid,
|
||||||
|
"chip": chip or None,
|
||||||
|
}
|
||||||
|
results.append(info)
|
||||||
|
|
||||||
|
vid_str = f"{port.vid:04X}:{port.pid:04X}" if vid_pid else "N/A"
|
||||||
|
chip_str = chip or "-"
|
||||||
|
print(f"{port.device:<8} {chip_str:<10} {vid_str:<12} {port.description or ''}")
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def find_usb_ttl_port():
|
||||||
|
"""自动查找 USB-to-TTL 串口"""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
combined = f"{port.description or ''} {port.hwid or ''}".lower()
|
||||||
|
if any(kw in combined for kw in USB_TTL_KEYWORDS):
|
||||||
|
candidates.append(port)
|
||||||
|
|
||||||
|
if candidates:
|
||||||
|
p = candidates[0]
|
||||||
|
print(f"→ 自动选择: {p.device} — {p.description}")
|
||||||
|
return p.device
|
||||||
|
|
||||||
|
# 回退: 返回第一个可用端口
|
||||||
|
all_ports = list(ports)
|
||||||
|
if all_ports:
|
||||||
|
p = all_ports[0]
|
||||||
|
print(f"→ 回退使用: {p.device} — {p.description}")
|
||||||
|
return p.device
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def try_connect(port: str, baudrate: int, timeout: float = 1.0) -> serial.Serial | None:
|
||||||
|
"""尝试以指定波特率打开串口"""
|
||||||
|
try:
|
||||||
|
ser = serial.Serial(
|
||||||
|
port=port,
|
||||||
|
baudrate=baudrate,
|
||||||
|
bytesize=serial.EIGHTBITS,
|
||||||
|
parity=serial.PARITY_NONE,
|
||||||
|
stopbits=serial.STOPBITS_ONE,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
return ser
|
||||||
|
except serial.SerialException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def format_hex(data: bytes) -> str:
|
||||||
|
"""格式化十六进制显示"""
|
||||||
|
return data.hex(" ").upper()
|
||||||
|
|
||||||
|
|
||||||
|
def format_binary(data: bytes) -> str:
|
||||||
|
"""格式化二进制显示"""
|
||||||
|
return " ".join(f"{b:08b}" for b in data)
|
||||||
|
|
||||||
|
|
||||||
|
def run_receiver(port: str, baudrate: int, hex_only: bool = False):
|
||||||
|
"""
|
||||||
|
主接收循环 — 持续读取串口数据并显示。
|
||||||
|
按 Ctrl+C 退出。
|
||||||
|
"""
|
||||||
|
print(f"\n打开串口 {port} @ {baudrate} baud...")
|
||||||
|
ser = try_connect(port, baudrate, timeout=0.5)
|
||||||
|
if ser is None:
|
||||||
|
print(f"✗ 无法打开 {port}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"✓ 已连接。等待红外信号... (按 Ctrl+C 退出)\n")
|
||||||
|
print("-" * 70)
|
||||||
|
|
||||||
|
buffer = bytearray()
|
||||||
|
last_print_time = time.time()
|
||||||
|
IDLE_PRINT_THRESHOLD = 0.15 # 空闲超过此秒数则打印之前累积的数据
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
waiting = ser.in_waiting
|
||||||
|
except (serial.SerialException, OSError) as e:
|
||||||
|
print(f"\n✗ 串口断开: {e}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if waiting > 0:
|
||||||
|
try:
|
||||||
|
chunk = ser.read(waiting)
|
||||||
|
except serial.SerialException:
|
||||||
|
break
|
||||||
|
buffer.extend(chunk)
|
||||||
|
last_print_time = time.time()
|
||||||
|
else:
|
||||||
|
# 空闲时: 如果缓冲区有数据且空闲足够久,打印
|
||||||
|
if buffer and (time.time() - last_print_time) > IDLE_PRINT_THRESHOLD:
|
||||||
|
data = bytes(buffer)
|
||||||
|
timestamp = time.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
if hex_only:
|
||||||
|
print(f"[{timestamp}] ({len(data)}B) {format_hex(data)}")
|
||||||
|
else:
|
||||||
|
print(f"[{timestamp}] 收到 {len(data)} 字节")
|
||||||
|
print(f" HEX: {format_hex(data)}")
|
||||||
|
print(f" BIN: {format_binary(data)}")
|
||||||
|
# 尝试 ASCII 解码
|
||||||
|
try:
|
||||||
|
ascii_str = data.decode("ascii")
|
||||||
|
if ascii_str.isprintable():
|
||||||
|
print(f" ASCII: {ascii_str.strip()}")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
print()
|
||||||
|
|
||||||
|
buffer.clear()
|
||||||
|
|
||||||
|
time.sleep(0.02)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n用户中断")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
print(f"✓ 已关闭 {port}")
|
||||||
|
|
||||||
|
|
||||||
|
def auto_baudrate_test(port: str):
|
||||||
|
"""
|
||||||
|
波特率自动探测 — 提示用户按红外遥控器,
|
||||||
|
在不同波特率下监听,看哪个能收到有效数据。
|
||||||
|
"""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print(" 波特率自动探测")
|
||||||
|
print("=" * 70)
|
||||||
|
print("请持续按红外遥控器任意键...\n")
|
||||||
|
|
||||||
|
for baud in COMMON_BAUDRATES:
|
||||||
|
print(f" 测试 {baud:>6} baud ... ", end="", flush=True)
|
||||||
|
ser = try_connect(port, baud, timeout=0.3)
|
||||||
|
if ser is None:
|
||||||
|
print("无法打开")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 等待一小段时间收集数据
|
||||||
|
received = bytearray()
|
||||||
|
deadline = time.time() + 2.0
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
n = ser.in_waiting
|
||||||
|
if n > 0:
|
||||||
|
received.extend(ser.read(n))
|
||||||
|
except serial.SerialException:
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
if received:
|
||||||
|
print(f"收到 {len(received)} 字节! → {format_hex(bytes(received))}")
|
||||||
|
else:
|
||||||
|
print("无数据")
|
||||||
|
|
||||||
|
print("\n提示: 有数据的波特率即为正确波特率。")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="红外收发板 — 接收测试",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
示例:
|
||||||
|
%(prog)s 自动查找串口, 9600bps, 持续接收
|
||||||
|
%(prog)s --ports 仅扫描串口
|
||||||
|
%(prog)s -p COM5 指定端口接收
|
||||||
|
%(prog)s -p COM5 -b 115200 指定端口和波特率
|
||||||
|
%(prog)s -p COM5 --auto-baud 自动探测波特率
|
||||||
|
%(prog)s -p COM5 --hex-only 仅显示十六进制
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument("-p", "--port", default=None, help="串口 (如 COM5)")
|
||||||
|
parser.add_argument("-b", "--baudrate", type=int, default=9600, help="波特率 (默认 9600)")
|
||||||
|
parser.add_argument("--ports", action="store_true", help="仅扫描串口")
|
||||||
|
parser.add_argument("--auto-baud", action="store_true", help="自动探测波特率")
|
||||||
|
parser.add_argument("--hex-only", action="store_true", help="仅显示十六进制")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 仅扫描
|
||||||
|
if args.ports:
|
||||||
|
scan_ports()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 确定端口
|
||||||
|
port = args.port
|
||||||
|
if port is None:
|
||||||
|
print("未指定端口,自动扫描 USB-to-TTL 设备...\n")
|
||||||
|
scan_ports()
|
||||||
|
port = find_usb_ttl_port()
|
||||||
|
if port is None:
|
||||||
|
print("\n✗ 未找到任何串口。请检查:")
|
||||||
|
print(" 1. USB-to-TTL 是否已插入")
|
||||||
|
print(" 2. 设备管理器中是否出现 COM 口")
|
||||||
|
print(" 3. 驱动是否已安装")
|
||||||
|
return
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 自动探测波特率
|
||||||
|
if args.auto_baud:
|
||||||
|
auto_baudrate_test(port)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 持续接收
|
||||||
|
run_receiver(port, args.baudrate, hex_only=args.hex_only)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
529
ir_send.py
Normal file
529
ir_send.py
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
红外收发板 — 发送脚本 (投影仪遥控)
|
||||||
|
===================================
|
||||||
|
通过 USB-TTL 连接红外收发板,发送红外信号控制投影仪。
|
||||||
|
|
||||||
|
协议说明 (来自板子文档):
|
||||||
|
发送红外信号: {A1, F1, <红外码3字节>}
|
||||||
|
修改通信地址: {A1, F2, <新地址>, 00, 00}
|
||||||
|
修改波特率: {A1, F3, <速率编号>, 00, 00}
|
||||||
|
|
||||||
|
其中:
|
||||||
|
A1 = 帧头
|
||||||
|
F1 = 发射红外指令
|
||||||
|
F2 = 修改通信地址
|
||||||
|
F3 = 修改波特率
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python ir_send.py -p COM8 up # 上
|
||||||
|
python ir_send.py -p COM8 down # 下
|
||||||
|
python ir_send.py -p COM8 left # 左
|
||||||
|
python ir_send.py -p COM8 right # 右
|
||||||
|
python ir_send.py -p COM8 --raw 3BC40D # 发送原始红外码
|
||||||
|
python ir_send.py -p COM8 -i # 交互模式 (w/s/a/d 方向键控制)
|
||||||
|
|
||||||
|
依赖: pip install pyserial
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 红外板协议常量
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
FRAME_HEADER = 0xA1 # 帧头
|
||||||
|
CMD_SEND_IR = 0xF1 # 发射红外信号
|
||||||
|
CMD_SET_ADDR = 0xF2 # 修改通信地址
|
||||||
|
CMD_SET_BAUD = 0xF3 # 修改波特率
|
||||||
|
|
||||||
|
DEFAULT_BAUDRATE = 9600
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 遥控器按键码 (从接收中采集到的红外原始码, 3字节)
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
IR_CODES = {
|
||||||
|
# 方向键
|
||||||
|
"up": bytes([0x3B, 0xC4, 0x0D]),
|
||||||
|
"left": bytes([0x3B, 0xC4, 0x10]),
|
||||||
|
"down": bytes([0x3B, 0xC4, 0x15]),
|
||||||
|
"right": bytes([0x3B, 0xC4, 0x12]),
|
||||||
|
# 继续通过接收测试添加...
|
||||||
|
# "ok": bytes([0x3B, 0xC4, 0x??]),
|
||||||
|
# "menu": bytes([0x3B, 0xC4, 0x??]),
|
||||||
|
# "power": bytes([0x3B, 0xC4, 0x??]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 帧构建
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def build_send_frame(ir_code: bytes) -> bytes:
|
||||||
|
"""
|
||||||
|
构建红外发射帧: {A1, F1, <3字节红外码>}
|
||||||
|
|
||||||
|
参数:
|
||||||
|
ir_code: 3 字节红外原始码, 如 b'\x3B\xC4\x0D'
|
||||||
|
返回:
|
||||||
|
5 字节完整发送帧
|
||||||
|
"""
|
||||||
|
if len(ir_code) != 3:
|
||||||
|
raise ValueError(f"红外码必须是 3 字节, 实际 {len(ir_code)} 字节")
|
||||||
|
|
||||||
|
return bytes([FRAME_HEADER, CMD_SEND_IR]) + ir_code
|
||||||
|
|
||||||
|
|
||||||
|
def build_set_addr_frame(new_addr: int) -> bytes:
|
||||||
|
"""构建修改地址帧: {A1, F2, <新地址>, 00, 00}"""
|
||||||
|
return bytes([FRAME_HEADER, CMD_SET_ADDR, new_addr & 0xFF, 0x00, 0x00])
|
||||||
|
|
||||||
|
|
||||||
|
def build_set_baud_frame(baud_code: int) -> bytes:
|
||||||
|
"""
|
||||||
|
构建修改波特率帧: {A1, F3, <速率编号>, 00, 00}
|
||||||
|
|
||||||
|
已知波特率编号 (根据文档):
|
||||||
|
01 = 4800bps
|
||||||
|
(其他待补充, 默认 9600 可能对应 00 或其他值)
|
||||||
|
"""
|
||||||
|
return bytes([FRAME_HEADER, CMD_SET_BAUD, baud_code & 0xFF, 0x00, 0x00])
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 串口扫描
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
USB_TTL_KEYWORDS = [
|
||||||
|
"ch343", "ch340", "ch341", "ch342", "ch344",
|
||||||
|
"usb-enhanced-serial", "usb serial", "usb-serial",
|
||||||
|
"ft232", "ftdi", "cp210", "pl2303", "wch",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def find_port():
|
||||||
|
"""自动查找 USB-to-TTL 串口"""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
for port in ports:
|
||||||
|
combined = f"{port.description or ''} {port.hwid or ''}".lower()
|
||||||
|
if any(kw in combined for kw in USB_TTL_KEYWORDS):
|
||||||
|
print(f"→ 自动选择: {port.device} — {port.description}")
|
||||||
|
return port.device
|
||||||
|
all_ports = list(ports)
|
||||||
|
if all_ports:
|
||||||
|
print(f"→ 回退使用: {all_ports[0].device}")
|
||||||
|
return all_ports[0].device
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 发送核心
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def send_ir(ser: serial.Serial, ir_code: bytes, repeat: int = 1):
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
参数:
|
||||||
|
ser: 已打开的串口
|
||||||
|
ir_code: 3字节红外原始码
|
||||||
|
repeat: 重复发送次数
|
||||||
|
"""
|
||||||
|
frame = build_send_frame(ir_code)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for i in range(repeat):
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
hex_str = ir_code.hex(" ").upper()
|
||||||
|
if repeat > 1:
|
||||||
|
print(f" 发送 [{i+1}/{repeat}]: {hex_str}")
|
||||||
|
else:
|
||||||
|
print(f" 发送: {hex_str} → 帧 {frame.hex(' ').upper()}")
|
||||||
|
|
||||||
|
if i < repeat - 1:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def send_raw_ir(port: str, hex_str: str, repeat: int = 1):
|
||||||
|
"""发送原始 3 字节红外码 (hex 字符串)"""
|
||||||
|
hex_str = hex_str.replace(" ", "").replace("-", "")
|
||||||
|
if len(hex_str) != 6:
|
||||||
|
print(f"✗ 红外码应为 6 位 hex (3字节), 实际 {len(hex_str)} 位")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
ir_code = bytes.fromhex(hex_str)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"✗ HEX 格式错误: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
ser = serial.Serial(port, baudrate=DEFAULT_BAUDRATE, timeout=0.5)
|
||||||
|
try:
|
||||||
|
send_ir(ser, ir_code, repeat)
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
|
def send_named(port: str, name: str, repeat: int = 1):
|
||||||
|
"""发送命名按键"""
|
||||||
|
name = name.lower()
|
||||||
|
ir_code = IR_CODES.get(name)
|
||||||
|
if ir_code is None:
|
||||||
|
print(f"✗ 未知按键: {name}")
|
||||||
|
print(f" 已知按键: {', '.join(IR_CODES.keys())}")
|
||||||
|
return
|
||||||
|
|
||||||
|
ser = serial.Serial(port, baudrate=DEFAULT_BAUDRATE, timeout=0.5)
|
||||||
|
try:
|
||||||
|
send_ir(ser, ir_code, repeat)
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 交互模式 (同时收发)
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
INTERACTIVE_HELP = """
|
||||||
|
╔══════════════════════════════════════════════╗
|
||||||
|
║ 投影仪红外遥控 — 收发一体交互模式 ║
|
||||||
|
╠══════════════════════════════════════════════╣
|
||||||
|
║ w/↑=上 s/↓=下 a/←=左 d/→=右 ║
|
||||||
|
║ Enter = 重复上次按键 ║
|
||||||
|
╠══════════════════════════════════════════════╣
|
||||||
|
║ :raw <hex> 发送自定义红外码 ║
|
||||||
|
║ :learn <名> <hex> 学习新按键 ║
|
||||||
|
║ :keys 列出已存按键 ║
|
||||||
|
║ :h 帮助 ║
|
||||||
|
║ :q 退出 ║
|
||||||
|
╠══════════════════════════════════════════════╣
|
||||||
|
║ 接收到的红外信号会实时显示在屏幕上 ║
|
||||||
|
╚══════════════════════════════════════════════╝
|
||||||
|
"""
|
||||||
|
|
||||||
|
KEY_MAP = {
|
||||||
|
'w': 'up', 's': 'down', 'a': 'left', 'd': 'right',
|
||||||
|
'up': 'up', 'down': 'down', 'left': 'left', 'right': 'right',
|
||||||
|
}
|
||||||
|
|
||||||
|
# 接收到的新信号 → 显示的标签
|
||||||
|
RX_LABELS = {
|
||||||
|
0x0D: "↑ 上",
|
||||||
|
0x15: "↓ 下",
|
||||||
|
0x10: "← 左",
|
||||||
|
0x12: "→ 右",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _kbhit():
|
||||||
|
"""检查是否有按键 (非阻塞)"""
|
||||||
|
try:
|
||||||
|
import msvcrt
|
||||||
|
return msvcrt.kbhit()
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _getch():
|
||||||
|
"""读取单个按键 (阻塞, 仅在 kbhit() 返回 True 后调用)"""
|
||||||
|
try:
|
||||||
|
import msvcrt
|
||||||
|
ch = msvcrt.getch()
|
||||||
|
if ch == b'\xe0':
|
||||||
|
ch2 = msvcrt.getch()
|
||||||
|
return {
|
||||||
|
b'H': 'up', b'P': 'down', b'K': 'left', b'M': 'right',
|
||||||
|
}.get(ch2, '')
|
||||||
|
return ch.decode("utf-8", errors="replace").lower()
|
||||||
|
except ImportError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def run_interactive(port: str, repeat: int = 1):
|
||||||
|
"""收发一体交互模式 — 同时接收红外信号 + 按键发送"""
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
codes = dict(IR_CODES)
|
||||||
|
cfg = {"repeat": repeat} # 可变容器, 允许命令中修改
|
||||||
|
ser = serial.Serial(port, baudrate=DEFAULT_BAUDRATE, timeout=0)
|
||||||
|
print(f"✓ 已连接 {port} @ {DEFAULT_BAUDRATE} baud")
|
||||||
|
print(INTERACTIVE_HELP)
|
||||||
|
|
||||||
|
last_key = None # 上一个按键名 (用于 Enter 重复)
|
||||||
|
rx_buffer = bytearray()
|
||||||
|
rx_last_byte_time = 0
|
||||||
|
|
||||||
|
print("┌──────────────────────────────────────────┐")
|
||||||
|
print("│ 等待按键或红外信号... │")
|
||||||
|
print("└──────────────────────────────────────────┘")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
got_input = False
|
||||||
|
|
||||||
|
# ── 1. 检查串口接收 ──
|
||||||
|
try:
|
||||||
|
n = ser.in_waiting
|
||||||
|
except (serial.SerialException, OSError):
|
||||||
|
print("\n✗ 串口断开")
|
||||||
|
break
|
||||||
|
|
||||||
|
if n > 0:
|
||||||
|
data = ser.read(n)
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
# 判断是否为新信号 (距上次字节 > 200ms)
|
||||||
|
if rx_buffer and (now - rx_last_byte_time) > 0.2:
|
||||||
|
# 打印上一个信号
|
||||||
|
_print_rx(bytes(rx_buffer))
|
||||||
|
rx_buffer.clear()
|
||||||
|
|
||||||
|
rx_buffer.extend(data)
|
||||||
|
rx_last_byte_time = now
|
||||||
|
got_input = True
|
||||||
|
|
||||||
|
# 缓冲区有数据但已空闲 → 打印
|
||||||
|
if rx_buffer and (time.time() - rx_last_byte_time) > 0.2:
|
||||||
|
_print_rx(bytes(rx_buffer))
|
||||||
|
rx_buffer.clear()
|
||||||
|
got_input = True
|
||||||
|
|
||||||
|
# ── 2. 检查键盘输入 (非阻塞) ──
|
||||||
|
if msvcrt.kbhit():
|
||||||
|
ch = _getch()
|
||||||
|
got_input = True
|
||||||
|
|
||||||
|
# 方向键
|
||||||
|
name = KEY_MAP.get(ch)
|
||||||
|
if name and name in codes:
|
||||||
|
send_ir(ser, codes[name], cfg["repeat"])
|
||||||
|
last_key = name
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 回车 = 重复上次
|
||||||
|
if ch in ('\r', '\n'):
|
||||||
|
if last_key and last_key in codes:
|
||||||
|
send_ir(ser, codes[last_key], cfg["repeat"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 命令模式
|
||||||
|
if ch in (':', '/', '\\'):
|
||||||
|
# 读取整行命令
|
||||||
|
print(f"\nIR> :", end="", flush=True)
|
||||||
|
cmd_line = _read_line()
|
||||||
|
_handle_command(ser, codes, cmd_line, cfg, last_key)
|
||||||
|
print("┌──────────────────────────────────────────┐")
|
||||||
|
print("│ 等待按键或红外信号... │")
|
||||||
|
print("└──────────────────────────────────────────┘")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 退出
|
||||||
|
if ch in ('q', '\x1b', 'esc'):
|
||||||
|
print("\n退出")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 帮助
|
||||||
|
if ch in ('h', '?'):
|
||||||
|
print("\n" + INTERACTIVE_HELP)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ── 3. 空闲时短暂休眠 ──
|
||||||
|
if not got_input:
|
||||||
|
time.sleep(0.02)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n用户中断")
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
print(f"✓ 已关闭 {port}")
|
||||||
|
|
||||||
|
|
||||||
|
def _print_rx(data: bytes):
|
||||||
|
"""格式化打印接收到的红外信号"""
|
||||||
|
|
||||||
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
hex_str = data.hex(" ").upper()
|
||||||
|
|
||||||
|
# 尝试识别
|
||||||
|
label = ""
|
||||||
|
if len(data) == 3 and data[0] == 0x3B and data[1] == 0xC4:
|
||||||
|
label = RX_LABELS.get(data[2], f"键值 0x{data[2]:02X}")
|
||||||
|
|
||||||
|
if label:
|
||||||
|
print(f"\n ← [{ts}] 收到: {hex_str} {label}")
|
||||||
|
else:
|
||||||
|
print(f"\n ← [{ts}] 收到: {hex_str} ({len(data)}字节)")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_line() -> str:
|
||||||
|
"""读取一行输入 (在 kbhit 之后调用)"""
|
||||||
|
import msvcrt
|
||||||
|
line = ""
|
||||||
|
while True:
|
||||||
|
ch = msvcrt.getch()
|
||||||
|
if ch in (b'\r', b'\n'):
|
||||||
|
print()
|
||||||
|
return line
|
||||||
|
if ch == b'\x08': # Backspace
|
||||||
|
if line:
|
||||||
|
line = line[:-1]
|
||||||
|
print('\b \b', end='', flush=True)
|
||||||
|
continue
|
||||||
|
if ch == b'\x1b': # ESC 取消
|
||||||
|
print(" [取消]")
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
c = ch.decode("utf-8")
|
||||||
|
line += c
|
||||||
|
print(c, end='', flush=True)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_command(ser, codes, line, cfg, last_key):
|
||||||
|
"""处理命令行输入"""
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
|
||||||
|
parts = line.strip().split()
|
||||||
|
if not parts:
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = parts[0].lower()
|
||||||
|
|
||||||
|
if cmd in ('q', 'quit', 'exit'):
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
if cmd in ('h', 'help', '?'):
|
||||||
|
print(INTERACTIVE_HELP)
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'keys':
|
||||||
|
print("已存按键:")
|
||||||
|
for k, v in codes.items():
|
||||||
|
marker = " ← 上次" if k == last_key else ""
|
||||||
|
print(f" {k:<12} → {v.hex(' ').upper()}{marker}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'learn' and len(parts) >= 3:
|
||||||
|
try:
|
||||||
|
data = bytes.fromhex(parts[2].replace(" ", ""))
|
||||||
|
codes[parts[1]] = data
|
||||||
|
print(f" ✓ {parts[1]} → {data.hex(' ').upper()}")
|
||||||
|
except ValueError:
|
||||||
|
print(" ✗ HEX 格式错误")
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'raw' and len(parts) >= 2:
|
||||||
|
hex_str = parts[1].replace(" ", "")
|
||||||
|
if len(hex_str) != 6:
|
||||||
|
print(f" ✗ 红外码应为 6 位 hex (3字节)")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
data = bytes.fromhex(hex_str)
|
||||||
|
send_ir(ser, data, cfg["repeat"])
|
||||||
|
except ValueError:
|
||||||
|
print(" ✗ HEX 格式错误")
|
||||||
|
return
|
||||||
|
|
||||||
|
if cmd == 'repeat' and len(parts) >= 2:
|
||||||
|
try:
|
||||||
|
cfg["repeat"] = int(parts[1])
|
||||||
|
print(f" ✓ 重复次数 = {cfg['repeat']}")
|
||||||
|
except ValueError:
|
||||||
|
print(" 用法: repeat <次数>")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 命名按键
|
||||||
|
if cmd in codes:
|
||||||
|
send_ir(ser, codes[cmd], cfg["repeat"])
|
||||||
|
else:
|
||||||
|
print(f" ✗ 未知命令: {cmd}")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
# 命令行入口
|
||||||
|
# ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="红外收发板 — 投影仪遥控 (A1 F1 协议)",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
示例:
|
||||||
|
%(prog)s -p COM8 up 发送"上"键
|
||||||
|
%(prog)s -p COM8 down 发送"下"键
|
||||||
|
%(prog)s -p COM8 --raw 3BC40D 发送原始红外码
|
||||||
|
%(prog)s -p COM8 -i 交互模式 (w/s/a/d 方向键控制)
|
||||||
|
%(prog)s -p COM8 -i -n 3 交互模式, 每键重复3次
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument("key", nargs="?", default=None,
|
||||||
|
help="按键名 (up/down/left/right)")
|
||||||
|
parser.add_argument("-p", "--port", default=None, help="串口 (如 COM8)")
|
||||||
|
parser.add_argument("-b", "--baudrate", type=int, default=DEFAULT_BAUDRATE,
|
||||||
|
help=f"波特率 (默认 {DEFAULT_BAUDRATE})")
|
||||||
|
parser.add_argument("--raw", metavar="HEX", help="发送原始 3 字节红外码")
|
||||||
|
parser.add_argument("-n", "--repeat", type=int, default=1,
|
||||||
|
help="重复发送次数 (默认 1)")
|
||||||
|
parser.add_argument("-i", "--interactive", action="store_true",
|
||||||
|
help="交互模式 (w/s/a/d 方向键控制)")
|
||||||
|
parser.add_argument("--list", action="store_true", help="列出所有已知按键")
|
||||||
|
parser.add_argument("--set-addr", metavar="HEX", help="修改通信地址 (如 A5)")
|
||||||
|
parser.add_argument("--set-baud", metavar="CODE", help="修改波特率编号")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 确定端口
|
||||||
|
port = args.port or find_port()
|
||||||
|
if not port:
|
||||||
|
print("✗ 未找到串口。请用 -p 指定。")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 列出按键
|
||||||
|
if args.list:
|
||||||
|
print("已知红外码:")
|
||||||
|
for key, code in IR_CODES.items():
|
||||||
|
print(f" {key:<12} → {code.hex(' ').upper()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 配置命令
|
||||||
|
if args.set_addr:
|
||||||
|
addr = int(args.set_addr, 16)
|
||||||
|
frame = build_set_addr_frame(addr)
|
||||||
|
ser = serial.Serial(port, baudrate=DEFAULT_BAUDRATE, timeout=0.5)
|
||||||
|
ser.write(frame)
|
||||||
|
ser.close()
|
||||||
|
print(f"✓ 已发送修改地址: {frame.hex(' ').upper()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.set_baud:
|
||||||
|
code = int(args.set_baud)
|
||||||
|
frame = build_set_baud_frame(code)
|
||||||
|
ser = serial.Serial(port, baudrate=DEFAULT_BAUDRATE, timeout=0.5)
|
||||||
|
ser.write(frame)
|
||||||
|
ser.close()
|
||||||
|
print(f"✓ 已发送修改波特率: {frame.hex(' ').upper()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 交互模式
|
||||||
|
if args.interactive:
|
||||||
|
run_interactive(port, args.repeat)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 单次发送
|
||||||
|
if args.raw:
|
||||||
|
send_raw_ir(port, args.raw, args.repeat)
|
||||||
|
elif args.key:
|
||||||
|
send_named(port, args.key, args.repeat)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
92
ir_try.py
Normal file
92
ir_try.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
快速尝试单个协议 — 带手机摄像头观察红外LED是否发光
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import serial
|
||||||
|
|
||||||
|
PORT = sys.argv[1] if len(sys.argv) > 1 else "COM8"
|
||||||
|
KEY = 0x0D # 上
|
||||||
|
|
||||||
|
ser = serial.Serial(PORT, baudrate=9600, timeout=0.3)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
# 尝试1: 完整4字节 NEC [addr, ~addr, cmd, ~cmd]
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
frame = bytes([0x3B, 0xC4, KEY, KEY ^ 0xFF])
|
||||||
|
print(f"尝试: NEC完整4字节 → {frame.hex(' ').upper()}")
|
||||||
|
print("用手机摄像头看红外LED是否发光...")
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
time.sleep(0.15)
|
||||||
|
# 读回应
|
||||||
|
resp = ser.read(ser.in_waiting)
|
||||||
|
if resp:
|
||||||
|
print(f" 板子回应: {resp.hex(' ').upper()}")
|
||||||
|
else:
|
||||||
|
print(" (无回应)")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
# 尝试2: NEC 地址命令格式 [addr, cmd] (有些板子只要2字节)
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
frame = bytes([0x3B, KEY])
|
||||||
|
print(f"\n尝试: NEC短帧2字节 → {frame.hex(' ').upper()}")
|
||||||
|
print("用手机摄像头看红外LED是否发光...")
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
time.sleep(0.15)
|
||||||
|
resp = ser.read(ser.in_waiting)
|
||||||
|
if resp:
|
||||||
|
print(f" 板子回应: {resp.hex(' ').upper()}")
|
||||||
|
else:
|
||||||
|
print(" (无回应)")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
# 尝试3: 带命令前缀的发送 (常见 YS-IRTM 风格)
|
||||||
|
# A1 F1 + 地址 + 命令
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
frame = bytes([0xA1, 0xF1, 0x3B, KEY])
|
||||||
|
print(f"\n尝试: A1F1命令格式 → {frame.hex(' ').upper()}")
|
||||||
|
print("用手机摄像头看红外LED是否发光...")
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
time.sleep(0.15)
|
||||||
|
resp = ser.read(ser.in_waiting)
|
||||||
|
if resp:
|
||||||
|
print(f" 板子回应: {resp.hex(' ').upper()}")
|
||||||
|
else:
|
||||||
|
print(" (无回应)")
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
# 尝试4: 换一种候选 — 单字节地址
|
||||||
|
# ═══════════════════════════════════════════════════════
|
||||||
|
frame = bytes([0xC4, KEY])
|
||||||
|
print(f"\n尝试: 单地址2字节 → {frame.hex(' ').upper()}")
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(frame)
|
||||||
|
ser.flush()
|
||||||
|
time.sleep(0.15)
|
||||||
|
resp = ser.read(ser.in_waiting)
|
||||||
|
if resp:
|
||||||
|
print(f" 板子回应: {resp.hex(' ').upper()}")
|
||||||
|
else:
|
||||||
|
print(" (无回应)")
|
||||||
|
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
print("\n──────")
|
||||||
|
print("用手机摄像头观察:")
|
||||||
|
print(" 红外LED = 白色透明或深紫色的小珠子")
|
||||||
|
print(" 发送时如果发光 → 手机屏幕里能看到紫白色闪光")
|
||||||
|
print(" 完全不亮 → 发送协议不对,板子根本没发射")
|
||||||
Reference in New Issue
Block a user