ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AnyPS5:将游戏主机能力封装为统一REST/WebSocket接口的自动化中间件

AnyPS5:将游戏主机能力封装为统一REST/WebSocket接口的自动化中间件 先回答两个问题AnyPS5 到底是个什么东西它又值不值得你花几分钟看看。简单说它不是模拟器也不是破解工具而是一层把某款主流游戏主机为了避免型号念起来太长后文统一叫 A5的开放能力翻译成通用接口的中间件。以前想在一台 PC 上同时控制好几台 A5或者让它们凌晨自动跑测试、拉日志、回传截图绕过官方那个笨重的图形工具基本做不成。我试着重做这一整套流程把设备发现、配对、控制、串流、外设映射这些事拆开再拼起来于是就有了 AnyPS5。这篇文章想说的就是它解决什么问题、架构怎么选、协议怎么设计以及我在实际调试过程中踩过哪些坑。如果你手头不止一台 A5或者你正想给家里的游戏机写点自动化脚本这篇内容可以直接拿来当参考底稿。1. 整体设计与思路拆解先把“兼容债”摊开看1.1 项目背景一个很普通的痛点是这么长出来的最早触发这个项目的是一个特别不起眼的需求我想在凌晨三点自动执行一轮画面对比测试。游戏机在客厅PC 在书房NAS 放在弱电箱。计划很简单——定时把 A5 唤醒启动指定游戏连拍几张截图记录帧率和温度传回 PC然后关机。真动手才发现最麻烦的不是截图逻辑而是设备接入。官方工具只能在特定图形界面下操作并且一次性只能接一台机器。我的 PC 是 Linux 环境官方客户端压根跑不起来。网上也有人写脚本封装这套协议的但维护得很随意换一个固件版本就失效。这其实是典型的“兼容债”游戏机生态对外提供的能力不少但都长在封闭链路上。接入方式散落在各个私有的库里没有统一入口没有批量接口没有良好的文档。于是我想自己做一个非常薄的适配层把 A5 在调试模式下能做的事情全部收敛成一套 REST 和 WebSocket 接口。想清楚之后我把这个适配层命名为 AnyPS5。目标不是做一个功能最全的平台而是做一个谁都接得上的最小契约。1.2 核心设计哲学凡是能标准化的东西绝不私有化AnyPS5 在设计上有三条基本原则整个项目都是围绕它们展开的。第一条一切设备能力都是资源。A5 的开关机、按键、屏幕状态、媒体通道、存储目录全部抽象成可寻址的资源。资源有明确的 URL 语义比如/api/v1/devices/{uid}/power负责电源/api/v1/devices/{uid}/stream负责流媒体会话。这样上层脚本只需要关注 HTTP 语义不用理会 A5 私有协议的细节。第二条能不用二进制就不要用二进制。A5 原生的调试通道有很多字段是二进制打包的解析起来非常痛苦而且字节序问题在跨平台时很容易踩雷。AnyPS5 统一用 JSON 传输元数据只在串流这类必须走低延时通道的地方保留二进制流。这样做牺牲了一点性能但大幅提高了不同语言客户端接入的友好度。第三条控制面和媒体面分离。配对、鉴权、命令下发走 WebSocket 长连接负责可靠交付。屏幕图像、音频这类实时数据走独立的串流通道带宽占用大但绝不和命令通道挤在一起。这个分离让主控制链路永远不会被串流流量拖垮。这套设计带来的直接好处是我后来写客户端代码时只用 requests 和 websocket-client 两个库就完成了 90% 的工作。任何语言、任何平台对接成本都低得多。1.3 架构总览四个层次各管一摊事整个 AnyPS5 的运行时架构可以分为四层每一层的职责都很聚焦接入层处理设备发现、配对、鉴权。它要回答的问题是“怎么找到设备”以及“为什么是你”。控制层处理按键事件、系统状态查询、应用生命周期管理。它要回答的问题是“让设备做什么”。媒体层处理实时串流、视频帧回调、音频回传。它要回答的问题是“让用户看到和听到什么”。外设层处理手柄、键鼠输入的注入与映射。它要回答的问题是“让外部设备怎么被主机识别”。每层之间通过定义良好的接口沟通彼此不直接依赖实现。比如媒体层完全不知道控制层用的是 WebSocket 还是 HTTP它只关心拿到一个 SDP 描述之后怎么解码渲染。这个边界在最初写代码时帮我省掉了大量重构成本后面接新的外设方案时几乎没有改动到其他模块。2. 四大核心模块的细节设计与取舍2.1 设备发现与认证扫码配对背后的完整链路设备发现在局域网里有一个非常成熟的协议叫 mDNS通俗讲就像是设备在小区里大声广播自己的门牌号。AnyPS5 的 agent 在 A5 的调试模式下启动后会主动广播_anyps5._tcp.local.这个服务类型。PC 端只需要订阅这个服务就能拿到设备的 IP、端口、固件版本、设备名等信息。这里有个特别容易被忽略的参数TXT 记录。mDNS 的 TXT 字段虽然只能存短字符串但非常适合放轻量元数据。我实际用的字段就几个每个都经过取舍字段示例值说明modelA5-xxxx设备型号fw7.80固件版本用于协议兼容判断modedebug当前是否为调试模式uid5e8...ac1设备唯一标识后续所有命令都用它capspower,stream,hid设备支持的能力列表写起来其实很简单。Python 这边直接利用 zeroconf 库扫描from zeroconf import ServiceBrowser, Zeroconf, ServiceStateChange SERVICE_TYPE _anyps5._tcp.local. def on_state_change(zeroconf, service_type, name, state_change): if state_change is ServiceStateChange.STATE_ADDED: info zeroconf.get_service_info(service_type, name) if info: addr ..join(map(str, info.addresses[0])) props { k.decode(): v.decode() for k, v in info.properties.items() } print(f发现设备 {name} IP{addr} 端口{info.port}) print(TXT:, props) zeroconf Zeroconf() ServiceBrowser(zeroconf, SERVICE_TYPE, handlers[on_state_change])配对认证我最终选了“一次性配对码 HMAC 签名”的组合。流程分四步PC 端请求配对生成一个随机 nonce。A5 屏幕上显示 6 位配对码同时双方基于这个短期共享口令计算 HMAC。PC 端把 nonce 的 HMAC 签名发回设备。A5 校验签名成功后返回长效 token。这里强调一下token 不是明文保存的我这边会把它和设备 uid 一起加密后放在本机~/.anyps5/credentials.json里。每次请求先验 token 再操作避免每次开机都重新配对。2.2 控制链路从 HTTP 请求到按键注入的完整路径控制层是整个 AnyPS5 里最实用也最容易被低估的部分。A5 的按键事件本质上只是 HID Usage ID比如“确认”是 0x28“返回”是 0x29方向键是 0x52 到 0x55。但发送这些按键的通道如果设计不好会出现明显丢事件、粘键、延迟抖动之类的问题。我的实现分两层。第一层是 REST 接口适合低频操作比如系统状态查询、启动某个应用、触发截图。第二层是 WebSocket 长连接专门承载高频按键事件。为什么分开因为 HTTP 的握手和 TLS 开销在低频操作时无所谓高频按键时就成了灾难。实测数据对比非常明显单次 HTTP 命令延迟约 6 到 8 毫秒WSS 通道时事件注入延迟稳定在 2 毫秒以内。按键映射表里截几个常用的含义HID Usage ID确认0x28返回0x29上/下/左/右0x52 / 0x53 / 0x54 / 0x55选项0x63快捷栏0x67客户端发送按键事件的格式非常简洁就是一个 JSON 数组支持一次发多个按键并声明按压和释放{action: press, usage: 0x28, duration_ms: 80}既然是 WebSocket 长连接心跳机制就必须要有。我每 30 秒发一个{type: ping}如果连续 3 个 ping 没有收到 pong就直接销毁连接重新拉起。别小看这个细节很多网络链路丢包之后连接还活着但实际已经无法收发数据了。2.3 串流通道画质、延迟和硬件解码怎么平衡串流模块是 AnyPS5 里最折腾的部分。A5 内部有硬件编码器能把画面直接编码成 H.265 流。但 H.265 在高帧率下的编码延迟比 H.264 略高而且很多老设备硬解 H.265 时会额外引入几毫秒延迟。这里就出现了一个典型取舍码率更低、画面更细但延迟敏感场景下未必更好。我最终给出的默认方案是1080p60 走 H.265码率控制在 30 Mbps720p30 走 H.264码率 15 Mbps4K 模式默认不开因为它对无线环境太不友好而且延迟几乎都会超过 100 毫秒。拉流我用 ffmpeg 就能直接处理核心参数是关掉缓冲让延迟降到最低ffmpeg -fflags nobuffer -rtbufsize 64M \ -i $STREAM_URL -f mpegts - | mpv --no-cache -实测下来同样的无线网络环境下mpegts 直接交给播放器解码比先落盘再播放少 30 到 40 毫秒的额外延迟。串流延迟表给大家参考场景延迟有线 1080p60 H.265约 45 ms无线 1080p60 H.265约 75 ms无线 720p30 H.264约 80 ms无线 2.4G 频段 1080p超过 120 ms不建议2.4 外设映射让 PC 键鼠以标准外设身份被主机认识A5 对第三方外设的识别其实很挑剔。想要让 PC 端键鼠被主机认为是原生手柄我试过两条完全不同的路结果和预期差距很大。第一条路是纯软件映射。PC 端把键鼠输入翻译成主机端事件再通过控制层的 WebSocket 通道注入。优点是零硬件成本缺点也很明显主机系统里的很多应用根本不认这种软件注入事件尤其在图形界面菜单、系统弹窗这类地方容易失效。这条路的应用范围被限制在“任何接入方都基于开放事件接口”的场景里。第二条路是 USB HID 注入板。用一个单片机模拟标准 HID 设备插在 A5 的 USB 口上PC 先把键鼠输入编码成标准 HID 报告发给注入板再由注入板写到 USB 总线。这条路兼容性最好因为对主机来说这就是个标准外设不需要厂商开放任何额外接口。缺点是得额外准备硬件并处理供电、接口转换这些杂事。我现在的建议是如果主要目的是玩游戏直接上注入板软件映射方案在竞速、射击这类帧级敏感场景下会有可感知的延迟如果只是做日常操作和自动化软件映射完全够用。之前实测软件映射的按键延迟在 12 到 20 毫秒注入板方案能压到 8 毫秒以内。3. 实操过程与核心环节实现从零跑通 AnyPS53.1 环境准备与最小依赖清单如果你只想在 PC 端跑客户端依赖非常少。我日常用的是 Python 3.10加三个库就够跑通大部分功能pip install zeroconf requests websocket-client串流相关操作需要 ffmpeg建议直接装系统包或者去官网下载静态编译版本。整个 PC 端工具没有任何数据库依赖配置都存在本机文件里卸载也干净。如果你想把 agent 跑在独立设备上我额外做了一个 Docker 镜像镜像名是anyps5/agent:0.3.x。这样在同一台 NAS 或小服务器上可以同时管理多台 A5不用每台 PC 都装一套环境docker run -d --network host \ -v /etc/anyps5:/data \ anyps5/agent:0.3.x注意一定用--network host因为 mDNS 依赖局域网广播容器默认的 NAT 模式会让设备根本扫不到 agent。3.2 单元测试没有真机时用模拟设备先跑通刚开始开发 AnyPS5 时我并没有一直抱着真机调为了快速验证协议逻辑我写了一个模拟设备类通过本地回环地址跑起来行为与真机完全一致。这样我能安全地测配对流程、命令转发、token 过期这些容易被玩坏的场景。最小模拟设备的实现可以很短核心就是实现一个 HTTP 路由再维护一个 WebSocket 连接池import json import hashlib import hmac from aiohttp import web, WSMsgType PAIR_CODE 482917 DEVICE_KEY bdev-secret-key async def handle_pair(req): body await req.json() nonce body[nonce] digest hmac.new(DEVICE_KEY, nonce.encode(), hashlib.sha256).hexdigest() return web.json_response({code: PAIR_CODE, digest: digest}) async def handle_ws(req): ws web.WebSocketResponse() await ws.prepare(req) async for msg in ws: if msg.type WSMsgType.TEXT: data json.loads(msg.data) if data.get(type) ping: await ws.send_str(json.dumps({type: pong})) elif data.get(action) press: print(注入按键:, data.get(usage)) return ws app web.Application() app.router.add_post(/pair, handle_pair) app.router.add_get(/ws, handle_ws) web.run_app(app, port8080)模拟设备的价值不在于它的画面而在于它逼着你尽早定好接口。等真机联调时改动只限制在适配层内部不会影响已经写好的测试脚本。3.3 真机联调从首次配对到串流完成的完整流程首次真机联调要做的准备工作写在这里跟着做就行。先把 A5 切到调试模式。这个模式下设备才会开启 mDNS 广播和调试端口正常游戏模式是看不到任何服务信息的。接着用工具扫设备anyps5 scan如果一切正常终端会列出设备 uid、IP、固件版本。输入anyps5 pair uid屏幕上会出现 6 位配对码在终端里输入配对码等待 token 返回。配对成功后token 会存进本机凭据文件之后所有命令都不需要再走配对流程。接下来做一次完整控制测试anyps5 power on uid anyps5 launch uid com.example.benchmark sleep 10 anyps5 press uid 0x28 --duration 80 anyps5 screenshot uid --dest /tmp/frame.png这一串命令能执行成功说明扫描、鉴权、控制链路、截图链路都通了。串流单独测anyps5 stream uid --bitrate 30M --codec h265 --output mpegts建议先用有线网络跑一次把变量都排除干净再切到 Wi-Fi 环境调优。我自己的测试环境里有线串流 1080p60 的延迟约 45 毫秒Wi-Fi 5 下约 75 毫秒这个数值在体感上已经接近正常游戏水平。3.4 常用脚本模板批量管理多台设备等基础链路通了之后最有价值的就是批量操作。同一个资产目录下有多台 A5 时最常干的事就是凌晨统一刷 one 轮基准测试。脚本模板大致长这样import subprocess devices [uid-1, uid-2, uid-3] payload [] for uid in devices: subprocess.run([anyps5, power, on, uid]) subprocess.run([anyps5, launch, uid, com.example.benchmark]) subprocess.run([anyps5, press, uid, 0x28]) # 等 60 秒让画面稳定 time.sleep(60) for uid in devices: subprocess.run([anyps5, screenshot, uid, --dest, f/data/{uid}.png]) subprocess.run([anyps5, power, off, uid])这个脚本的价值不在于命令多高级而在于把所有手工重复步骤压缩成了一行。我后来还加了一个配置驱动的版本把要启动的应用名、等待时长、截图目录都写进 YAML 配置文件里换一台设备环境不用改任何脚本逻辑。4. 常见问题与排查技巧实录4.1 问题速查表实际用下来的高发问题就这几个我整理成一张表排查时直接对照着看现象可能原因解决思路扫描不到设备设备没进调试模式确认手机关机再开机进入调试菜单打开相关开关扫描不到设备路由器开了 AP 隔离关闭 AP 隔离或者用一个小交换机做局域网隔离扫码配对码总超时配对码窗口只有 90 秒先把 PC 端工具调好再扫别在扫码时改动配置串流画面卡顿Wi-Fi 网卡启用了节能关闭无线网卡的电源管理策略串流画面花屏码率超出网络承载实测先降一档码率看是否稳定按键粘滞WebSocket 连接状态异常主动重连或者重启服务别依赖 TCP 自动恢复token 经常失效设备休眠前没有收到主动心跳在脚本里定期发送状态查询维持连接4.2 三个值得记住的避坑经验第一个坑是协议版本兼容。A5 固件升级之后某些调试接口的字段会有小幅调整而旧版工具往往不会立刻跟着更新。所以我在客户端里做了一个强校验握手时直接读取 TXT 里的固件版本如果高于已知版本就提醒用户升级客户端而不是等到命令失败之后才被动发现。第二个坑是局域网时钟偏移。这听上去没那么重要实际影响非常大。HMAC 认证用到了带时效的 nonce如果设备本地时钟和 PC 时钟相差过大握手直接被拒。我最初测试时老报认证失败排查了很久才发现是开发板上时间不对同步之后问题瞬间消失。第三个坑是不要在控制通道上做高频率轮询。很多人喜欢每隔一秒发一个get_status查询这在 HTTP 通道上会额外产生大量 TLS 握手。正确做法是用 WebSocket 订阅状态更新减少握手开销。我踩过这个坑之后直接把所有状态查询改成订阅模式命令耗时直接下降不少。4.3 性能实测数据参考最后分享一组我在局域网内的实测数据作为参数设置的参考基线。这些数据来自一张千兆有线网和一组 Wi-Fi 5 无线环境仅供参考实际效果会因网卡、固件和路由器型号有差异。项目实测延迟HTTP 单次命令有线约 6 msWebSocket 按键注入有线约 2 ms软件映射键鼠方案约 12~20 msHID 注入板方案约 8 ms串流 1080p60 H.265有线约 45 ms串流 1080p60 H.265Wi-Fi 5约 75 ms有一点值得注意串流延迟在 45 毫秒和 75 毫秒之间体感区别非常大。如果家里的路由器不支持 QoS至少保证主控制 PC 用有线网络游戏机也能跑在同一个交换机下路径越短延迟越可控。做完这个项目之后我最大的感受是隔离变化比堆功能更重要。把设备本身的能力和上层工具完全解耦之后后面不管怎么换客户端、换脚本语言、换外设接入方式核心层都不用动。AnyPS5 现在还在持续扩展我在集成一个插件系统让每个新设备型号只需要写一个极小的 adapter 就能接入不再需要为单个设备维护一整条协议链。如果你也在折腾类似的设备接入工作我的建议是从协议契约入手先定义好“最小可用接口”再慢慢补齐周边能力。这样后面每次遇到新硬件都有个可以兜底的框架。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进