Skip to content

Instantly share code, notes, and snippets.

@shawnfeng0
Last active April 1, 2026 02:35
Show Gist options
  • Select an option

  • Save shawnfeng0/d7fcd9c065ca9e5d1edb7b9e0fe8b3fe to your computer and use it in GitHub Desktop.

Select an option

Save shawnfeng0/d7fcd9c065ca9e5d1edb7b9e0fe8b3fe to your computer and use it in GitHub Desktop.
USB Relay (16C0:05DF) Linux Control Script (Anti-Freeze Feature Report Edition)
#!/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)

USB Relay (16C0:05DF) Linux Control Script

A Python script to safely control cheap 2-channel USB Relays (VID 16c0, PID 05df) on Linux.

为什么需要这个脚本? (The Problem)

市面上许多便宜的此类 USB 继电器使用了非标准的 HID 实现。如果在 Linux 下直接使用普通的 os.write() 或是标准的 HID 输出报告来控制它,硬件会在执行 1 到 2 次指令后彻底假死,必须拔插 USB 线才能恢复。 另外,该固件的关闭指令并不是标准文档中的 0xFE 或是 0xFC

该脚本通过以下方式彻底解决了这些问题:

  1. 防假死:强制使用底层的 fcntl.ioctl 发送 9 字节的 Feature Report,完美绕过固件 Bug。
  2. 修正指令:使用了正确的非标准指令字(0xFF 为开,0xFD 为关)。
  3. 状态读取:支持通过 Feature Report 实时读取当前各个通道的真实状态。

用法 (Usage)

直接在终端运行脚本即可(无需安装额外的第三方库):

# 赋予执行权限
chmod +x usbrelay_control.py

# 读取当前所有通道的状态
./usbrelay_control.py status

# 打开第 1 路继电器
./usbrelay_control.py set 1 open

# 关闭第 1 路继电器
./usbrelay_control.py set 1 close

# 打开第 2 路继电器
./usbrelay_control.py set 2 open

# 关闭第 2 路继电器
./usbrelay_control.py set 2 close

权限配置 (udev rules)

如果运行提示 Permission denied,说明当前用户没有 /dev/hidraw* 的读写权限。为了避免每次都加 sudo,请配置 udev 规则:

  1. 创建规则文件:
echo 'SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", MODE="0660", GROUP="uucp"' | sudo tee /etc/udev/rules.d/99-usbrelay.rules

(注:Arch/Manjaro 等系统通常使用 uucp 组,Ubuntu/Debian 等可以使用 plugdev 组)

  1. 将当前用户加入该组(以 uucp 为例):
sudo usermod -aG uucp $USER
  1. 重新加载规则并生效(或重新插拔设备):
sudo udevadm control --reload-rules && sudo udevadm trigger
newgrp uucp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment