andresjjn/ubtech-alpha1s-distributed-ai

GitHub: andresjjn/ubtech-alpha1s-distributed-ai

将停产的UBTECH Alpha 1S人形机器人改造成基于本地LLM的语音驱动分布式AI助手,通过游戏PC运行大模型推理、树莓派执行动作控制,实现自然语言理解、手势同步和动作执行。

Stars: 0 | Forks: 0

# Alpha 1S — AI 驱动的人形机器人 一台被改造为基于分布式 AI 运行的、具备语音驱动和手势感知能力的 UBTECH Alpha 1S 教育机器人助手。该机器人能够理解自然语言,通过同步的手臂手势进行回应,并执行脚本化的动作序列——所有这些都由运行在便携游戏 PC 上的本地 LLM 提供支持。 ## 目录 - [系统架构](#system-architecture) - [硬件](#hardware) - [仓库结构](#repository-structure) - [软件栈](#software-stack) - [入门指南](#getting-started) - [服务端 (ROG Ally X)](#server-rog-ally-x) - [客户端 (Raspberry Pi)](#client-raspberry-pi) - [API 参考](#api-reference) - [JSON 契约](#json-contract) - [舵机映射](#servo-mapping) - [手势目录](#gesture-catalog) - [动作序列目录](#sequence-catalog) - [USB HID 协议](#usb-hid-protocol) - [已知问题](#known-issues) - [路线图(毕业论文之后)](#roadmap-post-thesis) - [状态](#status) ## 系统架构 ``` [User] speaks close to the ROG (handheld console) │ ▼ ┌─────────────────────────────────────────────────────┐ │ ASUS ROG Ally X — BRAIN + VOICE │ │ AMD Ryzen Z2 Extreme · 24 GB LPDDR5X · Windows 11 │ │ │ │ Built-in mic → Wake word (offline) │ │ → VAD (Silero / WebRTC) │ │ → faster-whisper large-v3-turbo (CPU) │ │ → Qwen2.5-7B (LM Studio, GPU) │ │ → Piper / Kokoro TTS │ │ → Flask :3000 │ │ /query /query_stream /transcribe │ └──────────────────────┬──────────────────────────────┘ │ TTS audio fragments (WiFi LAN) │ robot commands ▼ ┌──────────────────────────────────────────┐ │ Raspberry Pi 5 4 GB — EXECUTOR │ │ │ │ Receives TTS audio chunks │ │ → buffer (200–500 ms anti-jitter) │ │ → HAT USB Audio (C-Media, 3.5 mm jack) │ │ Receives movement commands │ │ → alpha1s_usb.py → USB HID │ └──────────────────────┬───────────────────┘ │ USB HID (0483:5750) — short cable ▼ ┌──────────────────┐ │ Alpha 1S │ │ 16 DOF │ │ 16 servos │ │ External PSU │ └──────────────────┘ ``` **算力分配:** 所有的语音处理(采集 / 唤醒 / VAD / STT / LLM / TTS)以及未来的感知 + 规划都在 **ROG** 上运行。 **Pi** 仅负责播放音频片段,并通过 USB HID 中继移动指令。 语音环路中唯一的网络段是单向的 TTS 输出:ROG → Pi。 ## 硬件 | 组件 | 型号 | 详情 | |---|---|---| | 机器人 | UBTECH Alpha 1S, 16 DOF | USB HID `0483:5750`, 硬件 `Alpha1_V2.0` | | 边缘控制器 | Raspberry Pi 5 Model B Rev 1.0, 4 GB | 执行器 — 在 `~/TDD/` 下运行 venv | | 边缘操作系统 | Raspberry Pi OS (Debian) | — | | 麦克风 | ROG Ally X 内置 | 近场,已在硬件中验证 | | 音频输出 | HAT USB Audio C-Media (位于 Pi 上) | 3.5 mm 插孔 + 扬声器 | | AI 服务器 | ASUS ROG Ally X RC73XA-NH011W | AMD Ryzen Z2 Extreme, Windows 11 | | GPU / VRAM | Radeon 890M iGPU, 17.8 GB 中的 12 GB 专用 | 最高可配置至 16 GB(不推荐) | | 摄像头 *(未来)* | Luxonis OAK-D Lite, 51 g | USB 直连至 ROG;头部固定支架待定 | | 电源 | RIDEN RD6006 + 24 V / 5 A AC/DC | 稳压至 ~7.4–7.5 V / 3 A — 线缆直连,无电池 | | 网络 | Wi-Fi 5, 同一局域网 | Pi ↔ ROG 平均 ping ~5.68 ms | ### Pi USB 总线分配 — 关键 | 总线 | 设备 | 注意 | |---|---|---| | Bus 001 (内部) | HAT USB Audio | **请勿触碰。** 移至 Bus 002 会导致音频中断。 | | Bus 002 / Bus 004 (蓝色) | Alpha 1S 机器人 | 必须使用短且高质量的线缆。劣质线缆会导致每 2 秒出现一次 `error -71` / 断开连接。 | ## 仓库结构 ``` Alpha1s_modernization/ ├── client/ # Raspberry Pi — audio playback + robot control │ ├── raspberry_client_gestos.py # Main client (Phase 4, active version) │ ├── alpha1s_usb.py # USB HID transport driver │ ├── stream_parser.py # SSE streaming parser (Phase 4) │ ├── metrics.py # Latency logger t0–t7 → CSV │ ├── es_MX-claude-high.onnx # Piper TTS voice model (Spanish) │ ├── sequences/ # Full-body movement files (12 files) │ │ ├── mover_adelante.txt │ │ ├── mover_atras.txt │ │ ├── mover_a_la_derecha.txt │ │ ├── mover_a_la_izquierda.txt │ │ ├── girar_a_la_derecha.txt │ │ ├── girar_a_la_izquierda.txt │ │ ├── punetazo_derecho.txt │ │ ├── punetazo_izquierdo.txt │ │ ├── flexiones_de_pecho.txt │ │ ├── posicion_inicial.txt │ │ ├── levantarse_desde_el_frente.txt │ │ └── levantarse_desde_la_espalda.txt │ └── gestures/ # Arm-only gesture files (12 files, run parallel to TTS) │ ├── saludar.txt │ ├── despedirse.txt │ ├── presentarse.txt │ ├── brazos_abiertos_bienvenida.txt │ ├── pensar.txt │ ├── afirmar.txt │ ├── enfatizar_breve.txt │ ├── senalar_adelante.txt │ ├── explicar_derecha.txt │ ├── explicar_izquierda.txt │ ├── explicar_ambos.txt │ └── hablar_relajado.txt │ ├── server/ # ROG Ally X — LLM + STT + TTS + Flask │ ├── rog_server_fase4.py # Flask :3000 — /query + /query_stream + /transcribe │ ├── alpha1s_prompt.py # System prompt + JSON schema (single source of truth) │ └── benchmark.py # Phase 6: TTFT / tok/s / valid JSON / gestures per model │ ├── simulation/ # ROS2 + Gazebo work (ABANDONED — kept as reference) │ └── alpha1s_bringup/ │ ├── urdf/alpha1s.urdf.xacro # URDF v3 — reusable for arm IK │ ├── meshes/visual/ # 18 STLs (Blender 5.0) │ ├── meshes/collision/ # 18 simplified STLs (convex hull) │ ├── launch/ │ │ ├── display.launch.py │ │ └── gazebo.launch.py │ └── config/controllers.yaml │ ├── docs/ # Reference documentation │ ├── Alpha1_Series_Bluetooth_communication_protocol.pdf │ └── arquitectura.md │ └── README.md ``` ## 软件栈 ### 服务端 (ROG Ally X) | 层级 | 技术 | 状态 | |---|---|---| | LLM runtime | LM Studio 0.4.12 + Qwen2.5-7B-Instruct | ✅ 活跃 (~20 tok/s) | | LLM 替代方案 | Qwen2.5-3B-Instruct | ✅ 就绪 (~40 tok/s) | | LLM 后端 | llama.cpp + Vulkan (Radeon 890M) | ✅ | | STT | faster-whisper large-v3-turbo, CPU int8 | ✅ | | TTS | Piper (当前) / Kokoro-82M (候选) | ✅ / 评估中 | | API 服务器 | Flask :3000 | ✅ | | JSON 强制校验 | `response_format: json_schema` | ✅ | | 流式传输 | 通过 `/query_stream` + `stream_parser.py` 进行 SSE | ✅ 代码 / ⏳ 硬件测试 | | 指标 | `metrics.py` → CSV (t0–t7) | ✅ | ### 客户端 (Raspberry Pi) | 层级 | 技术 | 状态 | |---|---|---| | 机器人传输 | `alpha1s_usb.py` — 直连 `/dev/hidrawX` | ✅ USB HID | | 音频播放 | pyaudio + HAT USB Audio | ✅ | | 手势执行 | daemon thread 中的 `.txt` 帧 | ✅ | | 心跳 | 每 8 秒发送一次 USB HID opcode `0x08` | ✅ | | 唤醒词 *(旧版)* | 在 Pi 上使用 `speech_recognition` + Google STT | 🔄 迁移至 ROG 中 | ## 入门指南 ### 前置条件 **服务端 (ROG Ally X / Windows 11)** - Python 3.10+ - [LM Studio 0.4.12](https://lmstudio.ai/) 已加载 Qwen2.5-7B-Instruct - `pip install flask faster-whisper openai` - Windows 编码修复已包含在 `rog_server_fase4.py` 中 (`sys.stdout.reconfigure(encoding='utf-8')`) **客户端 (Raspberry Pi 5 / Raspberry Pi OS)** - Python 3.10+ 并带有 venv (推荐:`~/TDD/`) - `pip install pyaudio numpy requests` - PATH 中包含 Piper TTS 可执行文件,`client/` 中包含 `es_MX-claude-high.onnx` - 用于 USB HID 访问的 udev 规则(无需 `sudo`): ``` # /etc/udev/rules.d/99-alpha1s.rules SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5750", GROUP="plugdev", MODE="0660" sudo udevadm control --reload-rules && sudo udevadm trigger sudo usermod -aG plugdev $USER ``` ### 服务端 (ROG Ally X) 1. 启动 LM Studio 并加载 `Qwen2.5-7B-Instruct`。验证服务器正在 `localhost:1234` 上运行。 2. 启动 Flask 服务器: ``` cd server/ python rog_server_fase4.py # → 正在监听 0.0.0.0:3000 ``` 3. 记下 ROG 的局域网 IP (`ipconfig`) — 客户端需要用到它。 ### 客户端 (Raspberry Pi) 1. 在 `raspberry_client_gestos.py` 中更新 `ROG_SERVER_URL`: ``` ROG_SERVER_URL = "http://:3000" ``` 2. 通过 USB 将机器人连接到 Pi 上的 **蓝色端口** (Bus 002 或 Bus 004)。 3. 通过外部直连线缆为机器人供电 (RIDEN RD6006, ~7.4–7.5 V / 3 A)。**机器人没有内部电池。** 4. 运行客户端: ``` cd client/ source ~/TDD/venv/bin/activate python raspberry_client_gestos.py ``` 5. 说出 **"alfa"** 以触发唤醒词,然后说出您的指令。 ## API 参考 所有 endpoint 均由 `rog_server_fase4.py` 在端口 `3000` 上提供。 | Method | Path | Input | Output | |---|---|---|---| | `POST` | `/query` | `{"text": "..."}` | `{"response": ""}` | | `POST` | `/query_stream` | `{"text": "..."}` | Server-Sent Events (JSON chunks) | | `POST` | `/transcribe` | WAV file (multipart) | `{"text": "..."}` | **LLM 参数(当前启用):** | Parameter | Value | |---|---| | Model | `Qwen2.5-7B-Instruct` | | Temperature | `0.4` | | Max tokens | `256` | | Top-p | `0.9` | | Frequency penalty | `0.15` | | Response format | `json_schema` | ## JSON 契约 `gesture_sequence` **始终存在且始终排在第一位**。键和动作类型为英文;`response` 文本为西班牙语。 ### Type 1 — 对话(带手势) ``` { "gesture_sequence": ["saludar", "presentarse"], "response": "Hola, soy Alpha 1S. ¿En qué puedo ayudarte?" } ``` ### Type 2 — 静态姿势 ``` { "gesture_sequence": [], "action": "execute_pose", "parameters": {"pose_name": "hands_up"}, "response": "Levantando los brazos." } ``` 可用姿势:`init`, `hands_up` ### Type 3 — 动作序列 ``` { "gesture_sequence": [], "action": "execute_sequence", "parameters": {"sequence_name": "mover_adelante"}, "response": "Caminando hacia adelante." } ``` ### Type 4 — LED 控制 ``` { "gesture_sequence": [], "action": "control_led", "parameters": {"state": true}, "response": "Encendiendo luces." } ``` ### 手势回退(客户端) 如果 `gesture_sequence` 缺失或为空,且 `response` 包含 ≥ 4 个单词,客户端将根据语音时长选择一个回退手势: | 预计时长 | 回退手势 | |---|---| | ≤ 3.5 秒 | `enfatizar_breve` | | 3.5 – 7 秒 | `explicar_derecha`, `afirmar` | | > 7 秒 | `explicar_derecha`, `hablar_relajado`, `explicar_izquierda` | ### Qwen 语言泄漏缓解措施 Qwen2.5-7B 可能会在响应中途切换为中文。`rog_server_fase4.py` 通过以下方式处理此问题:(1) 在 system prompt 中强化语言指令,(2) 生成后进行 CJK 检测(`U+4E00–U+9FFF`),(3) 自动重试,(4) 如果问题仍然存在,则在第一个 CJK 字符处截断。如果频繁发生,请切换至 3B 模型。 ## 舵机映射 舵机 ID `0–15`(机器人固件使用 `1–16` — 由 `alpha1s_usb.py` 处理偏移量)。 | ID | 关节 | 备注 | |---|---|---| | 0 | 右肩 前/后 | 0 = 前,180 = 后 | | 1 | 右肩 上/下 | 0 = 下,180 = 上 | | 2 | 右肘 | 0 = 内收,180 = 外展 | | 3 | 左肩 前/后 | **反向** — 180 = 前,0 = 后 | | 4 | 左肩 上/下 | **反向** — 180 = 下,0 = 上 | | 5 | 左肘 | **反向** — 180 = 内收,0 = 外展 | | 6 | 右髋俯仰 | — | | 7 | 左髋俯仰 | — | | 8 | 右髋滚转 | — | | 9 | 左髋滚转 | — | | 10 | 右膝 | — | | 11 | 左膝 | — | | 12 | 右踝俯仰 | — | | 13 | 左踝俯仰 | — | | 14 | 右踝滚转 | — | | 15 | 左踝滚转 | — | ### INIT 姿势(规范 — 所有手势均返回至此) ``` [90, 0, 90, 90, 177, 90, 90, 60, 76, 110, 90, 90, 120, 104, 70, 90] # A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15 ``` ### 防碰撞定律(已在硬件上验证) | 定律 | 规则 | |---|---| | 1 — 安全进入 | 使肘部靠近胸部:首先在肘部伸展状态下抬起肩部,然后弯曲肘部。 | | 2 — 退出 / 救援 | 切勿从弯曲的肘部直接回到 INIT。首先在一帧内将肘部伸展至 90°,然后放下肩部。 | | 3 — 交错 | 交叉双臂时:在弯曲肘部之前,一侧的肩部必须高于另一侧,否则双拳会相撞。 | ## 手势目录 手势仅移动手臂舵机(ID 0–5)。它们在 daemon thread 中与 TTS 播放并行运行。文件位于 `client/gestures/` 中。 | 名称 | 时长 | |---|---| | `enfatizar_breve` | 2.4 秒 | | `afirmar` | 2.4 秒 | | `senalar_adelante` | 2.9 秒 | | `presentarse` | 3.0 秒 | | `pensar` | 3.0 秒 | | `explicar_derecha` | 3.1 秒 | | `explicar_izquierda` | 3.1 秒 | | `saludar` | 3.5 秒 | | `despedirse` | 4.0 秒 | | `brazos_abiertos_bienvenida` | 4.0 秒 | | `explicar_ambos` | 5.3 秒 | | `hablar_relajado` | 5.4 秒 | ## 动作序列目录 运动序列。文件位于 `client/sequences/`。该列表必须在三个地方保持同步:`sequences/` 目录、客户端中的 `SEQUENCE_FILES`,以及服务器 system prompt 中的示例。 | 序列名称 | 描述 | |---|---| | `mover_adelante` | 向前走 | | `mover_atras` | 向后走 | | `mover_a_la_derecha` | 向右侧步 | | `mover_a_la_izquierda` | 向左侧步 | | `girar_a_la_derecha` | 右转 (偏航) | | `girar_a_la_izquierda` | 左转 (偏航) | | `punetazo_derecho` | 右出拳 | | `punetazo_izquierdo` | 左出拳 | | `flexiones_de_pecho` | 俯卧撑 | | `posicion_inicial` | 返回站立 INIT | | `levantarse_desde_el_frente` | 面朝下起身 | | `levantarse_desde_la_espalda` | 面朝上起身 | ### 序列文件格式 `.txt` 文件中的每一行: ``` [a0, a1, ..., a15] speed_units time_ms ``` - 16 个舵机角度 (`0–180°`) - `time_ms` — 在下一帧之前保持的持续时间(以毫秒为单位) - `speed_units` — 被客户端忽略(仅使用 `time_ms`) - 重复的帧 = 保持该姿势 ## USB HID 协议 协议版本 `V20151215`。帧格式:`FB BF [length] [cmd] [params...] [CHECK] ED` 校验和:`CHECK = (length + cmd + sum(params)) & 0xFF` 响应直接以 `FB BF` 开头 — 没有 HID report ID 前缀。 | Opcode | 名称 | 备注 | |---|---|---| | `0x08` | 心跳 | `FB BF 04 08 00 0C ED` — Pi 每 8 秒发送一次 | | `0x0D` | LED 控制 | param: `0x00` = 关, `0x01` = 开 | | `0x18` | 电池 | `[volt_hi][volt_lo][charge][level 0–100]` — ⚠️ 使用外部 PSU(无电池)时行为会发生变化;需重新验证 | | `0x20` | 硬件版本 | 响应:直到 `0xED` 的 ASCII 字符串 | | `0x22` | 单个舵机 | `[id][angle][speed][interval_lo][interval_hi]` | | `0x23` | 多个舵机 | `[a0..a15][speed][interval]` ← 主要运动指令 | | `0x25` | 读取所有角度 | 响应:从位置 4 开始的 16 个角度字节 | ## 已知问题 | # | 问题 | 位置 | 优先级 | |---|---|---|---| | 1 | 唤醒词和 VAD 仍在 Pi 上运行(旧版流程) | `raspberry_client_gestos.py` | 🔴 待迁移至 ROG | | 2 | TTS 仍在 Pi 上合成(旧版流程) | `raspberry_client_gestos.py` | 🔴 待迁移至 ROG | | 3 | `legacy_dance` 系统 (`htsparser` + `pygame`) 尚未移除 | 客户端 | 🟡 隔离或删除 | | 4 | `ROG_SERVER_URL` 硬编码 — 无环境变量 / 配置文件 | 客户端 | 🟡 | | 5 | 流式 SSE (`/query_stream`) 代码已完成,但未进行硬件测试 | 服务端 + 客户端 | 🟡 | | 6 | 阶段 6 基准测试 (`benchmark.py`) 尚未运行 | 服务端 | 🟡 毕业论文必需 | | 7 | 外接 PSU 时电池遥测 (`0x18`) 语义未定义 | `alpha1s_usb.py` | 🟡 重新测量 | | 8 | `reverencia` 在 `SEQUENCE_FILES` 中被映射为 `sequences/`,但文件位于 `gestures/` 中 | 客户端 | 🟠 | | 9 | Qwen2.5-7B 在长响应下的 CJK 语言泄漏 | 服务端 | 🟠 已有缓解措施 | | 10 | 移除电池后 CoM 向上/向前移动 → 步态稳定性变差 | 硬件 | ⚪ 在阶段 B 中解决 | ## 路线图(毕业论文之后) 认知轴线作为最终的毕业论文交付成果已 **冻结**。RL / 仿真轴线已被 **放弃**(不是暂停)— 见下文。论文后的工作遵循一种 deliberative 架构:*度量感知 → 符号 LLM 规划 → 脚本化原语 → 视觉验证*。 ### 为什么放弃 RL 1. 工作台面是 1.5 × 0.7 米的桌子,而不是地板 — 机器人在上面几乎无法行走,需要去感知、触及并避开边缘,而不是实现动态运动。 2. 在仅有位置反馈且无扭矩反馈的舵机上实现动态步态的 Sim-to-real 几乎难以解决(参见 Hwangbo 等人,2019,*Science Robotics*)。 3. 与感知 + 操作路径相比,单位努力的学习收益较低。 URDF、STL 网格和惯性数据已保留,并将在阶段 E 中用于手臂 IK。 ### 阶段 A–F | 阶段 | 描述 | 关键成果 | |---|---|---| | **A** | 真实舵机映射 + 上下文包 | 所有的 16 个舵机的语义表:部位、轴、安全范围、正负号/反转。整合防碰撞定律。 | | **B** | 稳定步态(实现最小可行性,非完美) | 在外接线缆直连后实现 10 步不跌倒。静态稳定性概念 — 支撑多边形。 | | **C** | 摄像头:标定 + 姿态估计 | OAK-D 固定在头部。完成内参标定。在箱子和桌子角落使用 ArUco / AprilTag。基础坐标系中的箱子姿态,误差 < ~2 cm。 | | **D** | 闭合回路(修复侧向偏移) | 在 ROG 上运行 FSM:感知航向 → 如果偏离中心则旋转,如果居中则迈步,循环往复。离散视觉伺服。在 40 cm 处到达箱子成功率 ≥ 8/10。 | | **D.5** *(可选)* | 使用摄像头作为适应度进行步态优化 | 参数化步态;适应度 = 直行距离 / 偏移(由摄像头测量);使用 CMA-ES 进行优化。如果阶段 D 足够则跳过。 | | **E** | 抓取:手臂 IK + 盲区 | 已知箱子姿态 → 3-DOF 手臂 IK → 前臂捏持抓取最后 ~20 cm(低于 OAK-D 最小范围,开环)。必须具备悬崖检测。 | | **F** | LLM 规划器 + 验证 + 重新规划 | LLM 编排符号技能序列(`search → approach → grasp → carry → release`)。使用摄像头进行前/后验证;失败时重新规划。LLM 绝不计算几何 — 数字来自感知。 | ## 状态 | 轴线 | 状态 | |---|---| | 认知(语音 + LLM + 手势) | ✅ **已冻结 — 毕业论文最终交付成果** | | 仿真 / RL | ❌ **已放弃**(保留 URDF/STL 资产) | | 高级机器人技术 (阶段 A–F) | 🔭 **论文之后 — 阶段 A 是下一步** | **提交毕业论文前唯一待办事项:** 运行阶段 6 基准测试 (`benchmark.py`),对比 Qwen2.5-7B 与 3B — TTFT、tok/s、JSON 有效性比率、手势生成率。 ## 硬件工程笔记 - **重量减轻 ≠ 稳定性增加。** 移除电池(原本位于较低且居中的位置)会导致 CoM 升高并前移。没有它,步态会恶化。悬挂式提线结构是功能性的,不是装饰性的。 - **侧向偏移是通过用摄像头闭合回路来修复的**,而不是通过优化开环步态。 - **UBTECH 仿真器是一个姿势/动画编辑器,而不是物理仿真器。** 它无法验证平衡或接触。只有物理机器人才能验证平衡。 - **AI 无法仅凭舵机数值评估平衡或碰撞。** 它对于序列的结构性操作很有用(镜像、插值、重新计时、拼接防碰撞规则、检查限制、生成变体、解释描述的故障)。它绝对不能断言一个新帧是稳定的 — 必须提出并在硬件上进行验证。 ## 参考文献 - Hwangbo, J. et al. (2019). Learning agile and dynamic motor skills for legged robots. *Science Robotics*, 4(26). - Ahn, M. et al. (2022). Do as I Can, Not as I Say: Grounding Language in Robotic Affordances (SayCan). *arXiv:2204.01691*. - Liang, J. et al. (2022). Code as Policies: Language Model Programs for Embodied Control. *arXiv:2209.07753*. - Craig, J. J. (2005). *Introduction to Robotics: Mechanics and Control* (3rd ed.). Pearson. - Siciliano, B. et al. (2009). *Robotics: Modelling, Planning and Control*. Springer. - UBTECH Robotics. *Alpha 1S Bluetooth Communication Protocol* (internal, V20151215).
标签:人形机器人, 分布式系统, 响应大小分析, 本地大模型, 机器人控制系统, 语音助手, 边缘计算, 逆向工具