jrichter24/moldqueen
GitHub: jrichter24/moldqueen
通过逆向工程的 BLE 协议和 WebSocket API,从树莓派可编程控制 Mould King 积木玩具的多 hub 电机系统。
Stars: 0 | Forks: 0
moldqueen
[](LICENSE)


[](#highlights)
**通过逆向工程的 BLE 协议和干净的 WebSocket API,从 Raspberry Pi 驱动 [Mould King 13112](https://www.mouldking.com/) RC 挖掘机。**
`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)** 中的完整视觉导览 (启动页 · 仪表盘 · 向导 ·
通道设置)。
## 工作原理
挖掘机的两个 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), 请求拦截, 逆向工程