|
#!/usr/bin/env python3 |
|
import os |
|
import fcntl |
|
import sys |
|
import argparse |
|
import glob |
|
|
|
CMD_OPEN = 0xFF |
|
CMD_CLOSE = 0xFD |
|
HIDIOCSFEATURE_9 = 0xC0094806 # Write feature report |
|
HIDIOCGFEATURE_9 = 0xC0094807 # Read feature report |
|
|
|
def find_relay_device(): |
|
for dev_path in glob.glob('/dev/hidraw*'): |
|
try: |
|
with os.popen(f'udevadm info --query=all --name={dev_path} 2>/dev/null') as f: |
|
info = f.read() |
|
if '16c0' in info.lower() and '05df' in info.lower(): |
|
return dev_path |
|
except: |
|
pass |
|
return None |
|
|
|
def get_device_fd(): |
|
dev_path = find_relay_device() |
|
if not dev_path: |
|
print("❌ 错误: 未找到 USB 继电器设备 (16C0:05DF)") |
|
print("请检查设备是否插入。") |
|
sys.exit(1) |
|
|
|
try: |
|
return os.open(dev_path, os.O_RDWR) |
|
except PermissionError: |
|
print(f"❌ 错误: 没有权限访问 {dev_path}。") |
|
print("请运行: sudo usermod -aG uucp $USER && newgrp uucp (或直接 sudo 运行)") |
|
sys.exit(1) |
|
except Exception as e: |
|
print(f"❌ 无法打开设备: {e}") |
|
sys.exit(1) |
|
|
|
def print_status(): |
|
fd = get_device_fd() |
|
buf = bytearray([0x00] + [0]*8) |
|
try: |
|
fcntl.ioctl(fd, HIDIOCGFEATURE_9, buf) |
|
status_byte = buf[8] |
|
|
|
ch1_state = "\033[92m[OPEN]\033[0m" if (status_byte & 1) else "\033[90m[CLOSED]\033[0m" |
|
ch2_state = "\033[92m[OPEN]\033[0m" if (status_byte & 2) else "\033[90m[CLOSED]\033[0m" |
|
|
|
print("\n📊 继电器当前状态:") |
|
print("-" * 20) |
|
print(f"🔌 通道 1 : {ch1_state}") |
|
print(f"🔌 通道 2 : {ch2_state}") |
|
print("-" * 20 + "\n") |
|
except Exception as e: |
|
print(f"❌ 读取状态失败: {e}") |
|
finally: |
|
os.close(fd) |
|
|
|
def control_relay(channel, state): |
|
fd = get_device_fd() |
|
cmd_byte = CMD_OPEN if state.lower() == 'open' else CMD_CLOSE |
|
buf = bytearray([0x00, cmd_byte, channel, 0, 0, 0, 0, 0, 0]) |
|
|
|
try: |
|
fcntl.ioctl(fd, HIDIOCSFEATURE_9, bytes(buf)) |
|
action = "\033[92m打开\033[0m" if cmd_byte == CMD_OPEN else "\033[91m关闭\033[0m" |
|
print(f"✅ 成功: 通道 {channel} 已{action}") |
|
except Exception as e: |
|
print(f"❌ 发送指令失败: {e}") |
|
finally: |
|
os.close(fd) |
|
|
|
class CustomHelpFormatter(argparse.HelpFormatter): |
|
def format_help(self): |
|
help_text = """ |
|
======================================== |
|
🔌 2路 USB 继电器 (16C0:05DF) 控制工具 |
|
======================================== |
|
|
|
使用示例: |
|
读取当前所有通道的状态: |
|
./usbrelay_control.py status |
|
|
|
打开通道 1: |
|
./usbrelay_control.py set 1 open |
|
|
|
关闭通道 2: |
|
./usbrelay_control.py set 2 close |
|
|
|
可用命令: |
|
status 查看继电器的当前开关状态 |
|
set 控制继电器开关 (格式: set <通道号: 1|2> <状态: open|close>) |
|
""" |
|
return help_text |
|
|
|
if __name__ == '__main__': |
|
parser = argparse.ArgumentParser(formatter_class=CustomHelpFormatter) |
|
subparsers = parser.add_subparsers(dest='action') |
|
|
|
# 绕过argparse默认生成的难看的子命令帮助,我们自己写了 CustomHelpFormatter |
|
status_parser = subparsers.add_parser('status') |
|
set_parser = subparsers.add_parser('set') |
|
set_parser.add_argument('channel', type=int, choices=[1, 2]) |
|
set_parser.add_argument('state', choices=['open', 'close']) |
|
|
|
# 如果没有带参数直接运行,显示自定义帮助信息 |
|
if len(sys.argv) == 1: |
|
parser.print_help() |
|
sys.exit(1) |
|
|
|
args = parser.parse_args() |
|
|
|
if args.action == 'status': |
|
print_status() |
|
elif args.action == 'set': |
|
control_relay(args.channel, args.state) |