尧图精选

DS5Dongle 协议参考:HID Feature Report 0xF6/F7/F8 与 config_tool.py 完整解析

🕒 发布时间:2026/10/1 8:55:48 📁 来源:尧图网络
DS5Dongle 协议参考HID Feature Report 0xF6/F7/F8 与 config_tool.py 完整解析【免费下载链接】DS5DongleTurn your Pico 2 W into a DualSense 5 dongle.项目地址: https://gitcode.com/gh_mirrors/ds/DS5DongleDS5Dongle 能把 Raspberry Pi Pico2W 变成 DualSense 无线手柄的 USB 桥接接收器而它真正聪明的地方在于不改固件就能远程改配置。这套能力全部建立在 3 个自定义 HID Feature Report0xF6 写入 / 0xF7 读配置 / 0xF8 读版本之上配套脚本 config_tool.py 则把这套二进制协议封装成了三行命令。本文将完整梳理 DS5Dongle 的 HID 命令协议并逐段解析 config_tool.py 的实现思路帮助你快速理解、甚至扩展自己的手柄固件协议。一、项目背景一个无线变有线的手柄桥接器DS5Dongle 的核心链路是DualSense(蓝牙) ── Pico2W 固件 ──(USB HID)── 电脑电脑看到的是一只有线 DualSense而真正的握手、音频、HD 震动全部由 Pico2W 中转。除了游戏数据之外固件还额外暴露了一组厂商自定义的 Feature Report用于读取配置、写入配置、查询版本——这就是本文的主角。核心实现分散在 3 个文件里文件职责src/cmd.cpp固件侧协议处理分发 0xF6~0xF9 四个 Report IDsrc/config.h定义Config_body结构体即 0xF7 回传的原始字节tools/config_tool.py电脑侧的读写工具单文件 256 行二、DS5Dongle 的 4 个自定义 Report ID 速查表固件在 src/cmd.cpp 中用is_pico_cmd()声明了全部 4 个自定义 Report ID其中 0xF6/F7/F8 构成配置三件套0xF9 是附加的状态查询Report ID方向功能处理位置0xF6主机 → 设备 (SET)更新/保存配置、重连 USBpico_cmd_set0xF7设备 → 主机 (GET)读取Config_body原始字节pico_cmd_get0xF8设备 → 主机 (GET)读取固件版本字符串pico_cmd_get0xF9设备 → 主机 (GET)读取蓝牙 RSSI 信号强度附加pico_cmd_get这些 ID 在 tools/config_tool.py 中被原样镜像是理解整套协议的入口REPORT_SET 0xF6 # SET_REPORT: 写入/保存配置 REPORT_GET_CONFIG 0xF7 # GET_REPORT: 读取 Config_body REPORT_GET_VERSION 0xF8 # GET_REPORT: 读取固件版本 为什么用 Feature Report 而不是普通的 Input/Output 因为游戏手柄的 Input Report 要实时传输按键数据而配置读写是低频、带控制语义的操作。Feature Report 恰好不被操作系统当输入设备解析也不会污染 Steam 等软件的手柄数据流——这是 HID 协议扩展的经典用法。三、0xF6 写入协议一个 Report 里的 3 个子命令0xF6 不是写一次配置这么简单它用第 1 个字节的 funcid区分三种动作src/cmd.cpp[Report ID0xF6][funcid][62 字节数据...]funcid动作固件行为0x01更新 RAM 中的配置解析后面 62 字节的Config_body做范围钳制后立即生效震动、音量等马上响应0x02持久化到 Flash把当前 RAM 配置擦写进 Flash 扇区掉电不丢0x03重连 USB 设备调用tud_disconnect()→ 150ms 后tud_connect()让主机重新枚举常用于 USB 序列号等需要重新枚举的设置这个设计的妙处在于**先试后存**你可以用 0x01 先把参数写进 RAM 体验效果满意了再发 0x02 落盘不满意就什么都不做——RAM 里的值在下一次完整读取-写入循环中自然被覆盖。config_tool.py的 write_config 正是按这个顺序发两条 SET_REPORTdata bytes([FUNC_UPDATE]) body # 第一条funcid0x01 dev.send_feature_report(bytes([REPORT_SET]) data) if save: save_data bytes([FUNC_SAVE]).ljust(SET_DATA_LEN, b\x00) # 第二条funcid0x02 dev.send_feature_report(bytes([REPORT_SET]) save_data)注意--no-save参数只发第一条纯 RAM 更新。四、0xF7 读取协议Config_body 的 20 个字段逐一拆解0xF7 返回的就是 src/config.h 中__attribute__((packed))的Config_body结构体——小端序、无填充共 24 字节。config_tool.py里的FIELDS表是它的人话版config_tool.py两者一一对应字段类型取值范围作用config_versionu85配置 schema 版本只读固件管理haptics_gainfloat321.0 ~ 2.0HD 震动增益全表唯一的浮点数speaker_volumeu80 ~ 127手柄扬声器音量headset_volumeu80 ~ 1273.5mm 耳机音量speaker_gainu80 ~ 7扬声器预增益0自动inactive_timeu80 ~ 60无操作 N 分钟后断开0禁用disable_pico_ledu80/1关闭板载 WiFi LEDpolling_rate_modeu80/1/2轮询率250Hz / 500Hz / 实时audio_buffer_lengthu816 ~ 128音频缓冲长度controller_modeu80/1/2DS5 / DSE / 自动识别enable_usb_snu80/1启用自定义 USB 序列号ps_shortcut_enabledu80/1PS 键呼出 Xbox Game Barmic_selectu80~3麦克风自动/内置/耳麦/禁用speaker_selectu80~3扬声器自动/内置/耳机/禁用enable_wakeu80/1按 PS 键唤醒主机USB 远程唤醒trigger_reduceu80 ~ 10扳机马达力度削减0自动lock_volumeu80/1锁定音量忽略游戏的 SetState 调音量status_gpio_pinu80~255状态指示 GPIO255 禁用status_gpio_modeu80/1高电平保持 / 连接时 200ms 脉冲工具把字段类型自动翻译成struct格式串一行代码完成表驱动 → 二进制布局的推导config_tool.pySTRUCT_FMT .join(KIND_TO_CODE[f[1]] for f in FIELDS) BODY_SIZE struct.calcsize(STRUCT_FMT) # BfBBBBBBBBBBBBBBBBBBB 24 字节单一事实来源FIELDS表就是配置布局的唯一定义处。想加字段先改Config_body再同步这张表——顺序必须一致因为它是 packed 结构体任何重排都会破坏二进制兼容。读取时read_config工具先按描述符中 Report Count63 的完整长度取回数据再只截取前 24 字节解包data dev.get_feature_report(REPORT_GET_CONFIG, FEATURE_REPORT_LEN) # 64 字节 body bytes(data[1:1 BODY_SIZE]) # 跳过 report id只取 Config_body values struct.unpack(STRUCT_FMT, body)双端校验是协议的稳定性关键主机侧在parse_assignment里做范围检查固件侧在 config_valid() 里再做一遍钳制——越界值比如haptics_gain99会被强制拉回合法区间config_tool.py甚至会在输出中提醒你哪个值被固件偷偷修正了cmd_set。五、0xF8 版本查询一行 memcpy 的艺术0xF8 的处理只有一行核心逻辑src/cmd.cpp把编译期注入的PICO_PROGRAM_VERSION_STRING拷进 Report 缓冲区。工具侧 read_version 收到后在第一个\x00处截断、按 ASCII 解码。别小看这个设计远程知道固件版本意味着配置工具可以按版本做行为分支旧版不认识新字段时提前报错也是排查配置读回来全是垃圾值这类问题的第一步——90% 的协议不匹配都是版本错位导致的。六、config_tool.py 设备发现如何从一堆 HID 里找到游戏手柄Pico2W 插上后会出现多个 HID 接口手柄、键盘唤醒、甚至音频直接打开会抓错接口。工具的 open_device 用两级过滤精确定位按 VID/PID 过滤VID0x054C索尼PID∈{0x0CE6, 0x0DF2}DS5 / DS5 Edge 的冒牌身份按 Usage 过滤只接受usage_page0x0001Generic Desktop且usage0x0005Game Pad的接口cand [d for d in hid.enumerate(VID) if d[product_id] in PIDS] gamepads [d for d in cand if is_gamepad_hid(d)]找不到设备时的报错信息也贴心地列出了每个候选接口的 usage 详情方便你判断是不是 Steam 抢占了设备句柄——这是新手最常踩的坑先退出 Steam/DSX 再运行工具。七、实战三条命令玩转 DS5Dongle 配置装好依赖pip install hidapi后整个工具的使用体验被压缩到三个子命令1️⃣ 查看当前全部配置getpython config_tool.py get输出固件版本 带注释的字段列表例如Firmware: 1.2.3 Config: haptics_gain 1.500 # [1.0, 2.0] speaker_volume 100 # [0, 127] ...2️⃣ 批量设置字段setpython config_tool.py set speaker_volume90 enable_wake1 python config_tool.py set haptics_gain1.5 --no-save # 只写 RAM先试试效果set的完整流程是读 → 改 → 写 → 回读工具先read_config取回全部字段只更新你指定的几个其余原样带回——这就是为什么可以只传speaker_volume90而不影响其他 19 个字段。3️⃣ 查看字段清单fieldspython config_tool.py fields离线不连设备列出全部 20 个字段的类型、范围和说明写脚本前的必备参考。⚠️ 唯一不可设置的字段是config_version——工具在 parse_assignment 里直接拒绝因为版本号由固件单方面管理是强制重置配置机制的触发器config.cpp版本不匹配时整个 body 填 0xFF 重来。八、持久化机制Flash 里的那份带保险的存档0xF60x02 触发的落盘过程在 config_save() 中完成做了三层保险CRC32Config结构体头部含magic(0x66ccff00) crc32 sizeconfig.h任何一位翻转都会在下次config_valid()时暴露安全擦写flash_safe_execute会先停掉 core1 的音频任务再擦扇区——注释里写得很直白不做这步会出现保存配置时音频滋滋响的竞态config.cpp回读验证写完立即从 Flash 读回比对 CRC失败会打印verify failed并返回 false九、扩展指南想给自己的固件加一个自定义配置这套0xF6 子命令 0xF7 packed 结构体 表驱动工具的组合非常值得抄作业。动手前记住三个改动点固件在 config.h 的Config_body尾部追加字段不要插队保持二进制兼容更新CONFIG_VERSION工具在 FIELDS 表中同步追加一行STRUCT_FMT会自动重算描述符Report 长度 63 字节Report Count 0x3F见 usb_descriptors.cpp 中的 HID 报告描述符已经预留了余量24 字节的 body 远在容量之内再配合 0xF9 这类只读诊断口RSSI 音频门控状态位你就拥有了完整的远程诊断 远程调参通道——这正是 DS5Dongle 协议设计的精华所在用最少的 HID 事务换最大的运维自由度。【免费下载链接】DS5DongleTurn your Pico 2 W into a DualSense 5 dongle.项目地址: https://gitcode.com/gh_mirrors/ds/DS5Dongle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →