jrichter24/moldqueen

GitHub: jrichter24/moldqueen

通过逆向工程的 BLE 协议和 WebSocket API,从树莓派可编程控制 Mould King 积木玩具的多 hub 电机系统。

Stars: 0 | Forks: 0

moldqueen

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![Python 3.13](https://img.shields.io/badge/python-3.13-3776AB?logo=python&logoColor=white) ![Raspberry Pi](https://img.shields.io/badge/Raspberry%20Pi-3B-C51A4A?logo=raspberrypi&logoColor=white) [![status: two-hub control working](https://img.shields.io/badge/status-two--hub%20control%20working-brightgreen)](#highlights) **通过逆向工程的 BLE 协议和干净的 WebSocket API,从 Raspberry Pi 驱动 [Mould King 13112](https://www.mouldking.com/) RC 挖掘机。**

moldqueen — Mould King 13112 RC excavator control

`moldqueen` 将一台兼容 Lego 的积木挖掘机(两个原装电池/Bluetooth hub,约 6 个电机功能)变成了一台可编程机器。这些 hub 并非通过 GATT 连接——它们是通过**广播精心构造的 BLE 广播“电报”**来进行控制的。我们捕获并解码了官方 App 的协议,重建了其加密算法,并将其封装在一个小型的双进程控制服务中,提供 Web GUI 和完善的 API 文档——这是后续添加摄像头、TOF 传感器以及通过同一 API 驱动机器的本地 AI 大脑的基础。 **多用途——不仅限于挖掘机。** 协议和控制核心是*与具体玩具无关的*:它们驱动的是 **Mould King BLE hub**,而不是某个特定模型。因此,任何使用基于这些 hub 的 Mould King 套件的人都可以使用 moldqueen——**13112 挖掘机**只是第一个经过硬件验证的**参考布局**,而**可插拔布局系统**允许你为**自己的玩具**添加布局(包含其专属的功能和仪表盘),而无需改动控制核心。诚实的范围说明:挖掘机是在真实硬件上验证过的;不同的玩具将需要自己的布局——系统现在已经支持这一点。从此处开始: [`docs/ADDING_A_LAYOUT.md`](docs/ADDING_A_LAYOUT.md)。 ## 免责声明 🚀 **赶时间?[`docs/QUICKSTART.md`](docs/QUICKSTART.md)** —— 从拆箱到开动最快的方式。 📖 **权威详尽的参考资料:[`docs/PROJECT.md`](docs/PROJECT.md)。** 本 README 只是导览;PROJECT.md 才是事实来源。 ## 目录 - [免责声明](#disclaimer) - [你将获得什么](#what-you-get) - [亮点](#highlights) - [截图](#screenshots) - [工作原理](#how-it-works) - [协议 (MK4 12-channel nibble)](#the-protocol-mk4-12-channel-nibble) - [架构](#architecture) - [API 服务器 (必需) + 客户端 UI (可选)](#two-pieces-api-server-required--client-ui-optional) - [硬件](#hardware) - [快速开始](#quick-start) - [使用 AI 助手进行设置](#set-it-up-with-an-ai-assistant) - [WebSocket API](#the-websocket-api) - [通道映射](#channel-map) - [协议逆向工程过程](#how-the-protocol-was-reverse-engineered) - [安全模型](#safety-model) - [故障排除](#troubleshooting) - [仓库布局](#repository-layout) - [路线图与待解决问题](#roadmap--open-problems) - [开发](#development) - [致谢与许可](#credits--license) ## 你将获得什么 - **多 hub 同步控制** —— 一个 MK4 BLE 电报即可同时移动多个 hub, 无需进行单设备配对。 - **干净的 WebSocket API 优先** —— 控制核心就是一个 API;你可以根据完善的 **AsyncAPI 3.0** 规范构建自己的客户端 (控制台、App、AI agent)。 - **多语言 Web 客户端** —— 精美的仪表盘,带有 **6 种语言切换器** (EN/DE/ZH/KO/ES/FR),让孩子们可以用母语驾驶;可在 Pi 或任何地方作为容器运行。 - **RAW 调试模式** —— 一个低级工作台,用于直接构造和读取 BLE 电报: 这是逆向工程协议的方法,现在是内置工具。 - **面向未来的无线电技术** —— 通过 **raw HCI sockets** 与适配器通信 (不依赖 已弃用的 `hcitool`),后端可替换。 - **可移植核心** —— API 和控制逻辑是纯 Python 代码,**架构设计使其能在其他 Linux SBC 上运行** (任何有 BlueZ + BLE 适配器的地方);今天已在 Raspberry Pi 上得到验证。 - **可插拔布局** —— 挖掘机是参考布局;通过 manifest 为*你的* Mould King 玩具添加布局,无需修改核心 ([`docs/ADDING_A_LAYOUT.md`](docs/ADDING_A_LAYOUT.md))。 - **设计即支持多设备** —— 协议可寻址 **最多 3 个 hub slot**,因此单个电报可以同时驱动多个玩具/hub。 - **默认安全** —— 断开连接、零客户端、STOP 或离开 READY 状态时,电机立即回弹至 **neutral (中位)**;dry-run (试运行) 模式会记录每个电报但不发送任何内容。 - **AI 辅助设置** —— 将仓库交给 AI agent,让它引导你完成安装;文档 (QUICKSTART · PROJECT · CLAUDE.md) 编写得对 agent 非常友好。 ## 亮点 - **对真实控制协议进行了逆向工程。** 这些 hub 使用 **MK4 12-channel nibble** 协议 (而不是我们——以及公开参考资料——最初假设的 MK6.0 “per-device” 模型)。一个电报包含 **12 个 nibbles = 3 个 slots × 4 个通道**,并可**同时驱动所有 hub**。 - **恢复并验证了密码。** `mouldking_crypt.py` (`encode`/`decode`) **完全一致地**重现了官方 App 的传输字节 (13/13 自测通过) —— 这使我们能够解码捕获的广播并发现该协议。 - **使用单无线电实现双 hub 同步控制。** 设置了两个通道块的单个广播可同时移动两个设备。无需单设备寻址,也无需第二个 dongle。 - **是真正的服务,而非脚本。** 一个 `broadcaster` (拥有无线电、保持状态、自动回中安全机制) + 一个 `WebSocket API` (产品本身) + 一份 **AsyncAPI 3.0 规范**。 - **横屏仪表盘 GUI** (即着陆页)。控制项通过**可配置的通道映射** (持久化默认值 + 实时覆盖) 绑定至**功能**;履带/机械臂采用拖动摇杆,冷启动配有**连接向导**,并内置 **channel-assignment** (通道分配) 工具 (拖动控制项 → 查看哪个电机移动 → 进行分配,支持单功能最大速度限制、反向微调、反转以及 EN/DE 标签)。响应式设计: 桌面端为顶栏,移动端为左侧栏。 - **多用途平台。** 控制核心是**与玩具无关的** —— 它驱动的是 hub,而非某个模型。13112 挖掘机是已实现的**参考布局**; **可插拔布局**系统 (manifest + 每个布局的功能映射 + 可复制模板) 让任何人都可以为*不同*的 Mould King 玩具添加布局 —— [`docs/ADDING_A_LAYOUT.md`](docs/ADDING_A_LAYOUT.md)。 - **安全第一。** 断开连接 / 无客户端 / API 崩溃 → 电机回至 **neutral (中位)**。 dry-run (试运行) 模式会记录每个电报但不发送任何内容。 ## 截图 挖掘机仪表盘 —— **[`docs/SCREENSHOTS.md`](docs/SCREENSHOTS.md)** 中的完整视觉导览 (启动页 · 仪表盘 · 向导 · 通道设置)。

moldqueen excavator dashboard

## 工作原理 挖掘机的两个 hub 是**广播接收器**:它们监听 manufacturer data (制造商数据,公司 ID **`0xFFF0`**) 为精心构造、混淆过的**电报**的 BLE 广播包。要控制它们,你需要: 1. 停止/掩盖 `bluetoothd` 并接管 Bluetooth 适配器的原始控制权。 2. 广播一个 **connect (连接)** 电报,使 hub 进入监听模式。 3. 持续广播一个其 12 个 nibbles 编码了每个通道速度/方向的 **motion (移动)** 电报。重新广播一个 neutral (中位) 电报以保持/停止。 就这么简单 —— 无连接、一对多。一个适配器即可驱动所有 hub。 ``` phone app ──(BLE adverts, 0xFFF0)──► ┌─────────┐ we replaced the phone with: │ hubs │ Raspberry Pi ──(BLE adverts, 0xFFF0)──►└─────────┘ moldqueen on a USB dongle ``` ## 协议 (MK4 12-channel nibble) 电报是 **原始字节 → `MouldKingCrypt` → 24 字节 manufacturer data (制造商数据)** (公司 ID `0xFFF0`)。原始字节才是承载实际意义的部分: | 电报 | 原始字节 | 含义 | |----------|-----------|---------| | **Connect** | `ad ae 18 80 80 80 f3 52` | 使 hub 进入监听模式 | | **Motion** | `7d ae 18 ⟨6 个通道字节⟩ 82` | 驱动通道 | | **Neutral** | `7d ae 18 88 88 88 88 88 88 82` | 全部停止 | **6 个通道字节包含 12 个 nibbles = 3 个 slots × 4 个通道** (偶数通道 = 高 nibble,奇数 = 低;字节偏移量 `3 + ch//2`): ``` byte3 byte4 byte5 byte6 byte7 byte8 nibbles: [c0 c1] [c2 c3] [c4 c5] [c6 c7] [c8 c9] [c10 c11] slots: └── slot 0 ──┘ └── slot 1 ──┘ └──── slot 2 ────┘ ``` - **`0x8` = neutral (中位)/停止**;`>0x8` = 一个方向,`<0x8` = 另一个方向。 - **一个电报即可同时驱动所有 hub** —— hub 是通过移动的 *nibble 块*来寻址的。hub 的 slot 由其**物理按钮** (闪烁 1/2/3 次 = slot 0/1/2) 选择,并在重新通电时重置为 slot 0。 - **value ↔ nibble** (API 使用):`nibble = 0x8 + value`,value `-7..+7` → nibble `0x1..0xF` (`0`→`0x8`, `+7`→`0xF`, `-7`→`0x1`)。 **`MouldKingCrypt`** 混淆算法 (从 App 中恢复并已验证):固定 前导码 `C1..C5`、逐字节位反转、CRC-16/CCITT (多项式 `0x1021`),以及两次 7 位 LFSR 白化处理 (种子 63 / 37)。详见 [`bt-core/reference/mouldking_crypt.py`](bt-core/reference/mouldking_crypt.py)。 ## 架构 通过本地 Unix socket 通信的两个进程 —— 有意分开,以便将 **WebSocket API 作为产品**,而网页仅仅是它的第一个客户端 (控制台或 AI 大脑使用 *相同的* API): ``` ┌────────────────────────────────────────────────────────────────────┐ │ bt-core/mk4web/ │ │ │ │ web page / AI brain / CLI │ │ │ WebSocket (JSON, :8765) │ │ ▼ │ │ ┌──────────┐ Unix socket ┌──────────────┐ BLE adverts │ │ │ api │ ───────────────► │ broadcaster │ ──(raw HCI socket)─► hubs │ │ :8080 │ 12-nibble │ owns radio │ company 0xFFF0 │ │ │ :8765 │ state + setup │ + state + │ │ │ └──────────┘ │ lifecycle │ │ │ serves page + └──────────────┘ │ │ the WS API IDLE→CONNECTING→READY, auto-neutral │ └────────────────────────────────────────────────────────────────────┘ ``` - **`broadcaster.py`** —— 拥有无线电和权威的 12-nibble 状态;运行生命周期 **IDLE → CONNECTING → READY**;持续广播 (~5次/秒) 一个反映状态的 MK4 电报。如果 API 消失,则回退至 neutral (中位)/IDLE。无线电位于后端抽象之后:**`rawhci`** (原始 `AF_BLUETOOTH`/`BTPROTO_HCI` socket,不依赖 hcitool) 是默认选项;`hcitool` 是传统的备选方案 (`MK4_RADIO_BACKEND`)。通过 **MAC 地址**解析 dongle —— 因为 `hciN` 索引会变化。 - **`api.py`** —— WebSocket 服务器 (产品本身),也提供静态页面服务;拥有/驱动生命周期,映射 `value→nibble`,强制仅在 READY 状态下才能发出移动指令, 并将状态推送给客户端。断开连接/无客户端 → NEUTRAL (回中)。 ## 硬件 | 部件 | 详情 | |------|--------| | 控制盒 | Raspberry Pi 3B (aarch64, 1 GB RAM) | | **无线电 (使用此设备)** | **Realtek RTL8761B USB dongle** `00:A6:44:02:21:25` (通过 MAC 解析;重新插拔/重启后 `hciN` 索引会发生变化) | | 备用无线电 | hci2 = TP-Link USB dongle `6C:4C:BC:87:D0:83` (一个无线电就足够了) | | 避免 | 板载 Broadcom UART BT (hci0) —— *在连接转换时*会损坏数据帧;计划通过 `dtoverlay=disable-bt` 禁用 | | 电源 | **可靠的 5 V / 3 A** —— 电压不足会导致真实的故障;劣质电源是头号捣蛋鬼 | bluetoothd` 必须被**停止并掩盖** (它是通过 dbus/socket 激活的,会 重新抢夺适配器);raw HCI 需要 root 或 `cap_net_raw,cap_net_admin` 权限。 ## 两部分:API 服务器 (必需) + 客户端 UI (可选) moldqueen 包含两个可清晰分离的功能: - **API 服务器 —— 必需。** broadcaster (拥有无线电) + **WebSocket API** (产品本身)。WebSocket *始终*开启;这才是真正控制 挖掘机的部分。在 Pi 上运行。 - **客户端 Web UI —— 可选。** chooser/excavator/RAW 页面是为了方便 *驱动* API 而提供的便利工具。它们并非控制机器的必需品。 三种使用方式: 1. **API 也提供页面服务 (最简单)。** `scripts/start.sh` → 打开 `http://:8080/`。 2. **单独运行客户端。** 在其他地方提供 UI 服务 (例如桌面上的 Docker),然后 通过应用内的 endpoint 设置将其指向 Pi 的 WS —— 详见 [单独运行客户端](#running-the-client-separately-docker) 和 [`docs/REMOTE_CLIENT.md`](docs/REMOTE_CLIENT.md)。 3. **自带客户端。** 完全跳过页面,从你自己的代码/控制台/AI 连接到 WebSocket —— 契约见 [`asyncapi.yaml`](bt-core/mk4web/asyncapi.yaml)。 **服务器标志** (API):`--ws-only` (或 `--no-client`,环境变量 `MK4_SERVE_CLIENT=0`) 仅运行 **WebSocket —— 没有 HTTP 服务器** (无头模式 / 自带客户端); `--http-port N` 在端口 `N` 上提供页面服务 (默认为 `8080`;CLI 优先级高于 `MK4_HTTP_PORT`)。 WebSocket 端口有其专属设置 (`MK4_WS_PORT`,默认为 `8765`)。 ``` python -m mk4web.api # WS :8765 + client page :8080 (default) python -m mk4web.api --http-port 9000 # WS :8765 + client page :9000 python -m mk4web.api --ws-only # WS :8765 only (no web server) scripts/start.sh --ws-only # launcher, websocket-only ``` ## 快速开始 **一次性设置** (Python venv + 唯一依赖项): ``` cd bt-core python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt # websockets (+ pytest) ``` **最简单 —— 启动器。** [`scripts/start.sh`](scripts/start.sh) 会对 无线电进行预检 (bluetoothd;通过 MAC 识别 dongle,因此重新分配索引无关紧要;venv) 并按正确顺序启动 broadcaster + API。它**不会对系统进行任何持久化更改** —— 它可能会为了当前会话掩盖 bluetoothd 并启动 dongle,这些操作都是 可逆的: ``` scripts/start.sh # preflight + launch (live) → http://:8080/ scripts/start.sh --dry-run # logs telegrams, transmits NOTHING scripts/start.sh --check # audit only — report state, change nothing (= scripts/check.sh) ``` ### 手动替代方案 ``` # launcher 的作用:将 adapter 从 bluetoothd 释放,启动 dongle sudo systemctl mask --now bluetooth sudo hciconfig hci1 up cd bt-core && source .venv/bin/activate ``` **先进行 Dry-run (试运行)** (记录电报,*不发送任何内容*): ``` python -m mk4web.broadcaster --dry-run # terminal 1 python -m mk4web.api # terminal 2 → http://:8080/ ``` **Live (实况运行)** (驱动 dongle): ``` sudo python -m mk4web.broadcaster # starts IDLE — no transmit until "Connect" python -m mk4web.api ``` **冷启动流程** —— 打开 `http://:8080/` 处的仪表盘并按下 **Connect** 以启动向导: 1. 打开两个 hub 的电源 (每个显示一次长闪烁) → **Next**。 2. *Connecting…* → hub 快速闪烁。 3. 按下**其中一个** hub 的按钮使其快速闪烁**两次** (→ slot 1);让另一个保持 闪烁一次 (→ slot 0)。*(独立的控制需要分配不同的 slot。)* 4. **Ready** → 控制解锁。拖动摇杆进行驾驶 (松开会回弹至停止); **STOP** (或 Space/Esc) = 全部回中。 所有端口/HCI 均可通过环境变量覆盖 (`MK4_HCI`、`MK4_HTTP_PORT`、`MK4_WS_PORT`,…… —— 见 [`bt-core/mk4web/config.py`](bt-core/mk4web/config.py))。 ### 布局:chooser、dashboard、RAW **`/`** 提供一个**布局选择器** —— 选择 **Excavator** (`/excavator`) 或 **RAW** (`/raw`);它会记住你的选择 (通过“Layouts”按钮或 `/?choose` 返回此页面)。 一个 **About** 浮层包含免责声明、致谢、许可、AI 注明和作者信息。 **`/excavator`** 是**横屏挖掘机仪表盘**,布局在 HMI 背景之上 (见 [`docs/mould_king_13112_hmi_layout_spec.md`](docs/mould_king_13112_hmi_layout_spec.md))。 **`/raw`** 是一个 **RAW 调试**布局 —— 一个基于低级 `set`/`stop` 路径的协议级测试台:选择 1-3 个 slots,直接设置每个通道,构建并发送 电报,控制台会记录确切的字节 (原始字节 + 空中传输的 AD)。 - **控制项通过可配置的通道映射绑定到功能** (左/右履带、动臂提升、前臂、旋转、 铲斗),而不是原始通道。履带 + 机械臂功能是**比例拖动摇杆** (拖动 = 速度,松开回弹至中位);旋转和铲斗是长按按钮。 - **连接向导** —— 居中的模态框引导完成冷启动 (打开电源 → 连接 → 分配 slots → 就绪),带有媒体 slots 和 EN/DE 文本。 - **Settings** (居中浮层) = **通道分配工具**:拖动/测试控制项,查看哪个电机移动,设置其 slot/channel + 最大速度 + 反向微调 + 反转;单独的 **Labels** 页面 (EN/DE);**Save** (仅本次会话) 或 **Promote** (作为布局的 `config/channel_map..json` 中的新默认值保存)。此外还有仅限本次会话的 **device-0/1 hub swap** (0/1 号 hub 互换)。 - **响应式外壳** —— 适配视口 (无页面滚动);菜单在桌面端是**顶栏**,在移动端是**左侧栏** (竖屏 *和* 横屏);**EN/DE** 切换、全屏和 **STOP** 始终可触达。 channel map (通道映射) 具有持久化的**服务器默认值**和**客户端活动**映射 (默认值 + 覆盖值);**服务器**解析 `function → (slot, channel, value)`, 从而使 broadcaster 保持简单。*(**RAW** 布局 (`/raw`) 是基于原始 `set`/`stop` 的 slot/通道 测试台。)* ### 作为服务运行 (可选) 启动器是**不对系统做任何更改的默认选项** —— 随时运行它以获取 服务。如果你希望它**开机自启**,这是一个可选的便利功能,你可以通过一个小型的 **systemd** 单元自行添加 (掩盖 `bluetooth`,执行 `hciconfig up`,然后启动 broadcaster + API)。本项目有意**不提供** systemd 单元,因此克隆它不会对你的系统造成任何持久化更改。 ### 单独运行客户端 (Docker) Web UI 可以在**任何地方**运行,并通过局域网与 Pi 的 WS API 通信 —— broadcaster + 无线电保留在 Pi 上。一个 **仅限客户端** 的镜像 ([`Dockerfile.client`](Dockerfile.client)) 在 nginx 后面仅提供静态 UI (chooser + dashboard + RAW) 服务: ``` docker build -f Dockerfile.client -t moldqueen-client . # from the repo root docker run --rm -p 8080:80 moldqueen-client # → http://localhost:8080/ ``` 然后打开 **API endpoint** 设置 (Dashboard **⚙ Settings**,或 RAW 的 **API connection** 面板),输入 `ws://:8765`,然后按 **Connect** —— 它会被保存在浏览器中,WebSocket 将重新连接。endpoint 默认指向页面自身的主机 (因此由 Pi 提供的 UI 无需设置)。Pi 的 API 设计为**宽松的 CORS / 允许任何 WS origin** (局域网业余爱好工具)。完整指南: **[`docs/REMOTE_CLIENT.md`](docs/REMOTE_CLIENT.md)**。 ## 使用 AI 助手进行设置 习惯使用 AI 工具?你可以将安装和设置工作交给 agent。将 AI 助手 (例如 Claude Code) 指向 仓库 —— **[`github.com/jrichter24/moldqueen`](https://github.com/jrichter24/moldqueen)** —— 或者直接粘贴此 README,让它引导你在 Pi 上运行 moldqueen。本项目编写得对 **agent 非常友好**:[`docs/QUICKSTART.md`](docs/QUICKSTART.md) 是快速通道,[`docs/PROJECT.md`](docs/PROJECT.md) 是详尽的参考 资料,而每个文件夹下的 [`CLAUDE.md`](CLAUDE.md) 文件向 agent 交代了必须了解的信息和操作上的注意事项。你仍需提供硬件 (Pi、USB BLE dongle、你的 hub) —— AI 负责软件和配置步骤。 ## WebSocket API 核心产品。连接至 `ws://:8765`;消息格式为 JSON。完整的机器可读契约:**[`bt-core/mk4web/asyncapi.yaml`](bt-core/mk4web/asyncapi.yaml)** (AsyncAPI 3.0),也可通过 `GET /asyncapi.yaml` 获取。 **客户端 → 服务器** ``` { "cmd": "setup", "action": "connect" } // IDLE → CONNECTING { "cmd": "setup", "action": "ready" } // CONNECTING → READY { "cmd": "setup", "action": "reset" } // → IDLE (all neutral) { "cmd": "drive", "function": "left_track", "value": 6 } // motion BY FUNCTION (READY only) { "cmd": "set", "slot": 1, "channel": 0, "value": 5 } // raw motion by slot/channel (READY only) { "cmd": "stop" } // all neutral (any state) { "cmd": "map", "action": "get" } // get the channel map { "cmd": "map", "action": "set", "map": { … } } // set the session ACTIVE map { "cmd": "map", "action": "swap", "value": true } // session device-0/1 (slot 0↔1) swap { "cmd": "map", "action": "promote", "map": { … } } // persist a map as the DEFAULT { "cmd": "state" } // re-send current state ``` **服务器 → 客户端 (推送)** ``` { "type": "lifecycle", "state": "READY" } // on connect + every transition { "type": "state", "slots": [[0,0,0,0],[6,0,0,0],[0,0,0,0]] } // 3 slots × 4 signed values { "type": "map", "default": { … }, "active": { … }, "device_swap": false } { "type": "mapresult", "action": "set", "ok": true, "errors": [] } ``` `drive` 调用一个**功能**;**服务器**根据当前映射将其解析为 `(slot, channel, value)` (应用反转、device-swap 以及针对单功能的 `reverse_scale` 微调),从而使 broadcaster 保持简单。`value` 的范围是 `-7..+7`;`slot` `0..2`,`channel` `0..3`。完整契约:[`asyncapi.yaml`](bt-core/mk4web/asyncapi.yaml)。 ## 通道映射 **function → (slot, channel)** 的映射是**数据**,而非硬编码,且**按布局划分**: 每个映射了功能的布局都会声明其功能集合 (在布局 manifest 中) + `config/channel_map..json` 中的持久化默认值 (挖掘机的是 [`config/channel_map.excavator.json`](config/channel_map.excavator.json)),可在 GUI 中实时编辑。挖掘机的六个功能,每个为 `{slot, channel, invert, max, reverse_scale, label_en, label_de}`。服务器根据**活动**映射 (默认值 + 客户端的覆盖值;`promote` 会将映射保存为新的默认值) 解析 `drive`。当前默认值 —— `bucket`、`left_track`、`arm_lift`、`front_arm` 已确认可发送;`rotation` 和 `right_track` 是待排查的占位符: | 功能 | Slot | Ch | 全局 nibble | 状态 | |----------|------|----|--------------:|--------| | **bucket** (铲斗) | 0 | 0 | ch0 | ✅ 已确认 | | **arm_lift** (动臂提升) | 0 | 3 | ch3 | ✅ 已确认 (硬件测试) | | rotation (旋转) | 0 | 2 | ch2 | 占位符 | | **front_arm** (前臂) | 0 | 1 | ch1 | ✅ 已确认 (硬件测试) | | **left_track** (左履带) | 1 | 0 | ch4 | ✅ 已确认 (反向) | | right_track (右履带) | 1 | 2 | ch6 | 占位符 | **双 hub 同步,已确认:** 包含 `ch0` *和* `ch4` 设置的一个电报同时移动了两个设备。在 **Settings → Test** 中分配/确认其余通道 (拖动控制项,观察哪个电机移动,设置其 slot/通道)。`reverse_scale` (默认 1.0) 用于微调反向速度以匹配正向;`max` 用于限制功能的最高速度。 ## 协议逆向工程过程 一个简明的案例研究 (详情见 [`docs/PROJECT.md`](docs/PROJECT.md) 和 [`bt-core/reference/MKtech_reverse_engineering_report.md`](bt-core/reference/MKtech_reverse_engineering_report.md)): 1. **嗅探** hub,使用 `btmon` + `hcitool lescan` —— 它们不广播; 它们是纯接收器。这对被动发现是个死胡同。 2. **遵循公开参考资料** ([mkconnect-python](https://github.com/J0EK3R/mkconnect-python)) 并使用 MK6.0 模型实现了*单 hub* 移动 —— 但双 hub 寻址从未奏效。 3. **反编译** 官方 `MK+tech` Android App (`jadx`),从 Java BLE 插件中恢复了 `MouldKingCrypt` 密码,并在 Python 中重新实现了它 (`encode`/`decode`,经字节级完全一致性验证)。 4. **捕获** App 驱动真实 hub 的行为,并使用我们的 `decode()` **解码**广播 —— 揭示了真正的 **MK4 12-channel nibble** 协议 (单个电报,通过 nibble 块进行 slot 寻址)。之前走的 MK6.0 弯路一直都是错误的模型。 5. **重新发送**从我们自己的 dongle 解码出的电报 → 两个 hub 都通过一个广播同时移动了。 ## 安全模型 - **Dry-run** (`--broadcaster --dry-run`) 不发送任何内容;它会记录每一个电报。 - **移动受控于** `READY` 生命周期状态;仅进行/连接绝不会移动电机。 - **自动回中 (neutral)** 触发条件:客户端断开连接、零客户端、`stop` 或生命周期离开 READY。 - 如果 API 进程死亡,则**自动 IDLE** (关闭广播) (broadcaster 检测到 socket 断开)。 - GUI 中有醒目的 **STOP** 按钮;摇杆上每个通道的**松开即停止**功能依然有效。 ## 故障排除 首先,运行健康审计 —— 它会报告无线电/服务状态,并且**不会做任何更改**: ``` scripts/check.sh # = scripts/start.sh --check ``` | 症状 | 可能原因 | 解决方法 | |---------|--------------|-----| | **页面加载成功,电机不转** | **broadcaster 未运行** (只有 API 在运行) —— API 日志显示 `IPC: broadcaster not reachable` | 启动 broadcaster;最简单的方式是 `scripts/start.sh` (按顺序启动两者)。 | | **重启后什么都不动** | broadcaster 没有自动启动,dongle 恢复为 **DOWN** 状态,和/或你还没有重新执行 **Connect→Ready** | 运行 `scripts/start.sh`,然后在 GUI 中进行冷启动 (Connect → 按下一个 hub 按钮直到闪烁两次 → Ready)。 | | **以前能用,现在不稳定 / 电压不足** | **电源 (PSU) 太弱** (Pi 3 + 两个 dongle 非常耗电;`vcgencmd get_throttled` ≠ `0x0`) | 使用可靠的 **5 V / 3 A** 电源。 | | **只有一个 hub 移动,或者一起移动** | 这两个 hub 处于**相同的 slot** | 冷启动并按下**其中一个** hub 的按钮直到**快速闪烁两次** (slot 1),另一个保持闪烁一次 (slot 0)。 | | **bluetoothd 不断自动重启** | 它是 **socket/dbus 激活**的 —— 只要任何 BT 工具触碰适配器,普通的 `stop` 就会立即重新激活它 | **掩盖**它:`sudo systemctl mask --now bluetooth` (启动器会做这个)。可通过 `sudo systemctl unmask bluetooth` 恢复。 | | **找不到 Dongle / 错误的 `hciN`** | USB 重新枚举更改了索引,或者未插入 dongle | 检查 `lsusb` / `hciconfig -a`。启动器**通过 MAC** (`MK4_DONGLE_MAC`) 绑定,因此索引无关紧要 —— 如果仍然找不到,说明 dongle 不存在。 | 安全网始终适用:关闭页面 (客户端断开连接) 或点击 **STOP** 按钮会强制回中,并且如果 API 进程死亡,broadcaster 会降至 **IDLE** 状态。 ## 仓库布局 ``` moldqueen/ ├── docs/PROJECT.md # canonical project reference (read this) │ └── mould_king_13112_hmi_layout_spec.md # dashboard layout coordinates ├── config/channel_map..json # per-layout default channel map (e.g. channel_map.excavator.json) ├── assets/ # moldqueen_banner.png, moldqueen_icon.png, excavator_icon.png, moldqueen_dashboard_v2.png, wizard/ ├── scripts/ # start.sh / check.sh — preflight + launch (no system changes) ├── bt-core/ # Python — the radios + the control service │ ├── mk4web/ # broadcaster · api · telegram · channelmap · mouldking_crypt · config │ │ ├── asyncapi.yaml # WS API contract (served at /asyncapi.yaml) │ │ └── web/{chooser.html, shell.css, clientconfig.js, layouts.json, dashboard.*, raw.*, template.*} # / · /excavator · /raw (template inactive) │ └── reference/ # verified protocol snapshots, the codec, the APK report ├── java-core/ # empty Java scaffold — future API client OR retire ├── web-gui/ # original Node scaffold — superseded by mk4web's dashboard └── CLAUDE.md # terse agent/dev notes (per folder too) ``` `java-core/` 和 `web-gui/` 是项目早期遗留的脚手架;正在使用的代码栈完全位于 `bt-core/mk4web/` 中。 ## 路线图与待解决问题 - **RAW 页面 + 可配置的 API endpoint** —— 一个专门的原始 slot/通道页面,用于替代已废弃的简单页面。 - **完善 channel map (通道映射)** —— 通过 Settings → Test 排查/确认占位符通道。 - **反向速度校准** —— 为每个履带设置 `reverse_scale`,使反向速度与正向一致。 - **slot 自动检测 —— 未解决。** slot 由物理按钮设置,并在重新通电时重置;目前由**向导**手动引导。 - **设备身份用户体验 —— 未解决。** 哪个物理设备在哪个 slot 上属于操作员知识范畴;映射标签按功能划分 (EN/DE)。 - WebSocket API 的 **控制台 / AI 客户端** (API 已就绪;客户端尚未完成)。 - **硬件:** 禁用板载 BT;保持使用 5 V/3 A PSU。 - **未来阶段:** 摄像头、TOF 传感器、本地 AI 大脑 —— 均通过 WS API 驱动。 ## 开发 - **添加你自己的布局/玩具:** [`docs/ADDING_A_LAYOUT.md`](docs/ADDING_A_LAYOUT.md) (干净的路径 = 通用的 slot/通道 布局,无需修改服务器)。 - **在其他板子/容器中运行:** [`docs/PORTING.md`](docs/PORTING.md) (无线电核心受硬件绑定 —— 内部有诚实的警告)。 - **最小化依赖** (1 GB Pi):该服务仅需要 `websockets`。 - **测试:** `cd bt-core && source .venv/bin/activate && pytest`。 - **约定:** 小而清晰的常规提交 (`feat:`/`fix:`/`docs:`/`chore:`); 绝不提交密钥。 - Agent/工作笔记位于 `CLAUDE.md` 文件中 (根目录 + 每个文件夹)。 ## 致谢与许可 - **作者:** 由 Jens Richter 构建。具有物理和电气工程背景;我白天在 [DNA Evolutions](https://www.dna-evolutions.com/) 使用遗传算法和 AI 算法进行巡演优化。在 [LinkedIn](https://www.linkedin.com/in/li-jens-richter) 上找到我。*为我热爱挖掘机和直升机的儿子 Jonas 而构建。* - 协议基础:[`J0EK3R/mkconnect-python`](https://github.com/J0EK3R/mkconnect-python) (MK4/MK6 参考资料和 `MouldKingCrypt`)。我们的 `mouldking_crypt.py` 是 他们 `MouldKingCrypt` 的**移植/衍生作品**,**在 MIT License 下使用** (© 2024 J0EK3R) —— 见 [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md) —— 并且 经字节级验证与 MK+tech App 完全一致。我们的 hub 是 MK4 nibble 变体。附加协议参考:[BrickController2](https://github.com/imurvai/brickcontroller2)。 - **在 AI 辅助下构建** —— 见启动页的 *About* 浮层。 - **独立且非官方** —— 与 Mould King / Shenzhen Yuxing 无关; 商标的使用仅作描述性质。**按“原样”提供,无任何保证,使用风险自负** —— 见完整的[免责声明](#disclaimer)。 **许可:** [MIT](LICENSE) © 2026 Jens Richter。捆绑的第三方代码保留其各自的许可 —— 见 [`THIRD-PARTY-NOTICES.md`](THIRD-PARTY-NOTICES.md)。
标签:Python, WebSocket, 云资产清单, 依赖分析, 无后门, 机器人控制, 物联网, 蓝牙低功耗(BLE), 请求拦截, 逆向工程