anpct/marshall-acton3-ble
GitHub: anpct/marshall-acton3-ble
通过逆向 BLE 协议将 Marshall Acton III 音箱的音量与 EQ 控制接入 Home Assistant,摆脱对专属手机 App 的依赖。
Stars: 0 | Forks: 0
# Acton³ — 开源的 Marshall Acton III BLE 控制
通过 Python、命令行或 **Home Assistant** 来控制你的 **Marshall Acton III** 的音量、低音和高音 —— 基于 Bluetooth Low Energy,无需手机 App。
## 为什么开发这个工具
我希望**用一个 App 控制我所有的智能设备** —— 而这个 App 就是 Home Assistant。灯光、空调、电视盒子、冰箱……所有东西都集中在一个仪表板里。唯一的例外是 Marshall Acton III:这是一款非常棒的音箱,但它的设置隐藏在专属的 **Marshall Bluetooth** 手机 App 背后。想要通过自动化稍微调整低音或降低音量?太难了 —— 你必须拿起手机,打开它们的 App,找到滑块。
这就是这个工具要解决的痛点。音箱显然以某种方式与 App *进行通信*。如果我能弄清楚*如何*通信,我就能自己说同样的语言,并将控制直接接入 Home Assistant。所以我做到了。
这个代码库就是结果:一个轻量级的、低依赖的驱动程序,加上一个 HTTP 网桥,将音量/低音/高音转换为 Home Assistant 的滑块。
## 控制内容
| 设置 | 范围 | 方式 |
| --- | --- | --- |
| **音量 (Volume)** | 0–31 | BLE GATT 写入 |
| **低音 (Bass)** | 0–10 | BLE GATT 写入 |
| **高音 (Treble)** | 0–10 | BLE GATT 写入 |
此外,如果你需要,还可以读取当前状态和实时的正在播放元数据(标题 / 艺术家 / 专辑)。音频仍然通过蓝牙以常规方式流式传输 —— 本项目仅涉及**控制**通道。
## 工作原理(架构)
Acton III 实际上同时运行着**两**个蓝牙协议栈:
- **音频** 通过 **Classic Bluetooth A2DP** 流式传输 —— 这是任何手机或笔记本电脑向其播放音乐的常用方式。本项目不涉及该部分。
- **控制**(音量、EQ、设置)通过 **Bluetooth Low Energy (BLE)** 并使用**自定义 GATT 服务**进行传输。这是 Marshall App 使用的通道,也是我们在此使用的通道。
在 BLE 内部,音箱暴露了多个 GATT 服务。有趣的是一个带有(名字有点俏皮的)UUID 基础 `…-1337-1dea-feed-c0ffee70c0de` 的供应商控制服务。每个设置都是一个由单字节“寄存器”寻址的**特征**。你通过读取寄存器来获取当前值,通过写入来更改设置 —— 这就是协议的全部内容。
### 协议参考
控制服务:`0000fccd-0000-1000-8000-00805f9b34fb`
特征 UUIDs:`0000000N-1337-1dea-feed-c0ffee70c0de`(其中 `N` 是下方的寄存器)。
| 寄存器 | 含义 | 格式 |
| --- | --- | --- |
| `0x07` | **音量** | 单字节,`0`–`max` |
| `0x08` | **最大音量**(只读常量) | 当前固件下为 `31` |
| `0x0f` | **EQ** | 5 个字节 `[bass, 0xff, 0xff, 0xff, treble]` |
| `0x0a` | **正在播放**(通知) | `00 00 00 00 6a 00 `,idx `01`=标题 `02`=艺术家 `03`=专辑 |
EQ 寄存器是最巧妙的部分:它实际上是一个 **5 频段**结构(160 Hz / 400 Hz / 1 kHz / 2.5 kHz / 6.25 kHz),但 Acton III 在其 UI 中只暴露了**外侧的两个频段** —— 低音(`byte 0`)和高音(`byte 4`)—— 并将中间三个固定为 `0xff`(“未触碰”)。因此,要设置低音,你需要读取 EQ 寄存器,更改 byte 0,然后将所有五个字节写回去。
### 我是如何进行逆向工程的
没有进行 App 反编译,也没有进行流量嗅探 —— 是音箱自身的**物理旋钮**完成了这项工作:
1. **`scan.py`** —— 找到了音箱的 BLE 地址。
2. **`probe.py`** —— 连接并导出了所有的 GATT 服务/特征,分离出自定义控制服务并读取了每个寄存器的当前值。
3. **`probe.py --monitor`** —— 订阅了所有寄存器的通知,然后我**转动物理音量旋钮**,看着确切的一个寄存器数值跳动 `13 → 14 → 15`。这就是音量。转动**低音**旋钮 → EQ 寄存器的 byte 0 发生变化。**高音** → byte 4。每一次物理操作都准确地激活了一个值,这让我们免费获得了位置*和*编码方式(音箱报告的值就是你写回去的值)。
4. **`acton3.py`** —— 将这些发现封装成了一个干净的异步驱动程序。
5. **`demo.py`** —— 遍历了所有设置并恢复了原始设置,以确认可靠的双向控制。
因为当你触碰旋钮时,音箱会*告诉你*发生了什么变化,所以映射基本功能根本不需要任何特殊工具 —— 只需要耐心和一个 BLE 库。
## 安装
要求 Python 3.11+ 和 [bleak](https://github.com/hbldh/bleak)。
```
git clone https://github.com/anpct/marshall-acton3-ble.git
cd marshall-acton3-ble
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
```
## 使用方法
**1. 查找你的音箱**(先关闭 Marshall App —— BLE 每次只允许一个连接):
```
python scan.py
# -54 dBm ACTON III [LE] 1C6B8C90-...-FEC529C0 <-- MARSHALL?
```
在 macOS 上,地址是 CoreBluetooth UUID;在 Linux/Windows 上则是 MAC 地址。
**2. 控制它:**
```
ADDR=
python acton3.py $ADDR # show current volume/bass/treble
python acton3.py $ADDR volume 20 # 0–31
python acton3.py $ADDR bass 6 # 0–10
python acton3.py $ADDR treble 8 # 0–10
```
**3. 或者运行完整的扫描演示**(聆听它的变化,然后恢复原样):
```
python demo.py $ADDR
```
**作为库使用:**
```
from acton3 import Acton3
async with Acton3(addr) as s:
await s.set_volume(18)
await s.set_bass(7)
print(await s.get_state()) # {'volume': 18, 'max_volume': 31, 'bass': 7, 'treble': 5}
```
## Home Assistant 集成
不需要 MQTT broker,也不需要自定义组件。在一台靠近音箱(且保持开机)的机器上运行内置的 HTTP 网桥,然后添加三个仅通过 `curl` 进行交互的滑块实体。
**1. 启动网桥:**
```
python bridge.py $ADDR # listens on http://0.0.0.0:8770
```
在你的局域网内的任何地方测试它:
```
curl 'http://BRIDGE_HOST:8770/set?volume=20'
curl http://BRIDGE_HOST:8770/state
```
**2. 添加实体。** 将 [`homeassistant.yaml`](homeassistant.yaml) 复制到你的 `configuration.yaml` 中(将 `BRIDGE_HOST` 替换为网关主机的 IP)并重启 Home Assistant。你会得到三个滑块:**Acton III 音量 / 低音 / 高音**。拖动滑块 → 音箱就会响应。
```
[ Acton III Volume ] ──────●──────── 20
[ Acton III Bass ] ────●────────── 6
[ Acton III Treble ] ──────────●──── 8
```
### 网桥接口
| 接口 | 返回值 |
| --- | --- |
| `GET /state` | `{"volume":13,"max_volume":31,"bass":9,"treble":5}` |
| `GET /volume` · `/bass` · `/treble` · `/max_volume` | 纯数字 |
| `GET /set?volume=20&bass=6&treble=8` | 新值(参数的任意子集) |
## 逆向你自己的设备 / 扩展它
Acton III 暴露的寄存器比我关注的那三个(音源、播放/暂停、LED 以及其他我未映射的寄存器)要多。`probe.py --monitor` 是用来映射它们的工具:运行它,在音箱上更改设置,观察哪个寄存器发生了变化。欢迎提交 PR。
```
python probe.py $ADDR --monitor
# 然后按下 source / play / pause 并记录哪个 register 发生变化
```
## 限制与注意事项
- **BLE 同时只支持一个连接。** 当使用此程序控制音箱时,Marshall 手机 App 无法连接(反之亦然)。通过 A2DP 的音乐播放不受影响。
- **延迟** 每次更改约为 1–2 秒 —— 为了保证可靠性,网关在每次请求时都会进行连接。对于滑块来说这完全没问题。
- **范围。** 网关机器需要处于音箱的蓝牙覆盖范围内。
- **低音/高音限制** 为 0–10(在 `acton3.py` 中对应 `BASS_MAX` / `TREBLE_MAX`);如果你的固件接受更大的值,可以适当放宽。
## 灵感来源
- **Home Assistant** 社区及其“你的家,你做主,一个仪表板”的理念 —— 这就是我折腾这一切的全部原因。
- 更广泛的 **BLE 逆向工程 / 维修权**精神:你拥有的设备理应由你自己来控制自动化。互操作性是一项功能,而不是一种黑客行为。
- [**bleak**](https://github.com/hbldh/bleak),它让跨平台的 Python BLE 开发变得真正令人愉悦。
## 法律声明
这是我针对自己拥有的设备进行的独立互操作性开发。它没有复制任何固件或 App 代码,也没有绕过任何加密或访问控制 —— 音箱本身就是以明文形式广播这些 BLE 值的。出于互操作性目的的逆向工程在法律上受到广泛保护(例如欧盟软件指令第 6 条、印度版权法第 52(1)(ab) 条、美国合理使用先例)。使用风险自负;你对如何使用它负责。
## 许可证
[MIT](LICENSE) © 2026 anpct
标签:Home Assistant, Python, 云资产清单, 无后门, 智能家居, 物联网, 蓝牙低功耗, 逆向工具, 逆向工程, 音频控制