Skip to content

add MOONDROP PILL adapter - #130

Merged
silverpoetry merged 1 commit into
silverpoetry:mainfrom
kiyonoRin:add-moondrop-pill
Nov 13, 2026
Merged

silverpoetry merged 1 commit into
silverpoetry:mainfrom
kiyonoRin:add-moondrop-pill

Conversation

@kiyonoRin

@kiyonoRin kiyonoRin commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

新增水月雨 PILL 耳夹式 OWS(蓝牙名称 MOONDROP PILL)适配。PILL 是 ZZZ-ANGELS-OWS 的
基础款,GAIA 握手与电量命令逐字节同构,复用 MoondropZzzAngelsWireCodec 与电量策略,
不改动 ZZZ-ANGELS-OWS 实现。该型号无降噪,不开放任何噪声控制。

Scope and impact

  • integration:新增 MoondropPillAdapter 与 MoondropPillProtocolSession,注册于
    moondropGroup 内 ZZZ-ANGELS-OWS 之后、家族回退之前
  • docs:新增 docs/moondrop-pill-protocol.md
  • 无新控制请求、无新 @SerialName、无 EarbudState/IPC 字段变化
  • 控制 App 沿用 ControlAppCatalog.moondrop,不新增包名,不改 scope.list

改动只涉及系统设备接入与电量投影,详细配置仍留在厂商 App,符合项目范围。

Evidence

抓包:2026-11-12,固件 1.6.0,官方 App 2.16.1c-251216ai(versionCode 102017),
Xiaomi 13 / HyperOS 4.0,SPP DLCI 2,151 帧 / 3 次连接。抓包原文件未入库。

握手(三次连接回答一致):

发送:FF 01 00 00 00 0A 03 00
响应:FF 04 00 04 00 0A 83 00 00 04 03 01

响应参数 4 字节:状态 00 与版本三元组 04 03 01(协议 v4 / API 3.1)。

电量(同一会话):

FF 04 00 00 00 1D 1A 00               支持组件查询
FF 04 00 02 00 1D 1B 00 01 02         左(01)、右(02),无充电盒
FF 04 00 02 00 1D 1A 01 01 02         电量查询(左、右)
FF 04 00 04 00 1D 1B 01 01 64 02 50   左 100%、右 80%

字段解释:1D 1B 01 之后按 <id> <值> 成对,01 左耳、02 右耳、03 充电盒;左右耳
1..100 有效,00 与 FF 解码为未知;充电盒 0..100 有效(空盒 0 是真实状态)。
1 至 3 对、任意顺序均可解析,重复或未知 id、奇数长度、非法百分比拒绝整帧。

交叉验证:抓包同期官方 App 设备页显示 L 100% / R 80%,与 64/50 一致;模块实机两次
读数为 1D 1B 01 01 64 02 50(100 / 80)与 1D 1B 01 01 64 02 5A(100 / 90),随耳机
状态变化,说明是实时读数而非固定值。

负证据:151 帧内 1D 40/1D 41 噪声命令零条;1D 1B 00 是支持组件表而非电量读数,
按未知帧忽略;未见 1D 1A 81 主动推送变体,故不接受推送,仅响应 Refresh 查询。

本型号无控制命令:只有支持组件与电量两类只读查询,无写入帧,因此没有回读与失败行为
需要记录。抓包中的 1D 00 05 返回固件版本 ASCII 1.6.0,1D 0A 05、1D 1C/1D/1E、
1D 28(双设备连接,回答含已配对设备名)等命令语义未确认,不开放。

端点:标准 SPP,实测 DLCI 2。设备类 0x240404,身份 DUAL。
判型只认领规范化名称 moondroppill;未观察到 -LEA 身份与更宽别名,不认领。
地址为合成值 25:02:13:00:00:01,仅保留 OUI。

Validation

  • 单元测试:testDebugUnitTest 83 套件 / 655 用例,0 失败,其中 MoondropPillAdapterTest 22 例
  • 构建::system-module:lintRelease、:system-module:assembleRelease 均通过
  • 实机(Xiaomi 13 / HyperOS 4.0,模块 3.2.1 debug):首次连接与私有左右耳电量投影、
    MiLink 卡片、重连握手(帧与抓包逐字节一致)、厂商 App 让位与退出后通道恢复、
    “更多设置”跳转均正常;published raw ANC capabilities modes=0
  • 文档:markdownlint-cli2@0.18.1 0 error,tools/validate-docs.mjs passed
  • 日志、截图与蓝牙地址均已脱敏;抓包原文件未入库
  • 控制 App 未变更,catalog、scope.list、docs/control-apps.md 无需同步

单耳入盒:入盒期间该耳读数冻结,跨数分钟的四次刷新均返回同一帧 1D 1B 01 01 64 02 5A;
出盒后第一次普通 Refresh 即变为 1D 1B 01 01 64 02 64(右耳 80→100),未重新握手。
官方 App 显示同一读数,charging 恒为 false,与 ZZZ-ANGELS-OWS 固件 1.2.0 的限制同源。
模块保留设备实际回报,不按数值不变猜测离线或充电。

Risks and rollback

  • 仅新增适配器,回滚即删除注册与文件,无数据迁移
  • 厂商 App 占用 SPP 通道时私有通道失败,回退系统电量并按 10s/60s 退避,退出后恢复
  • 单耳与充电状态依赖设备上报:入盒期间电量冻结、出盒后恢复,充电状态始终不可读

GPL-3.0-only。

@kiyonoRin kiyonoRin changed the title feat(moondrop): add MOONDROP PILL adapter add MOONDROP PILL adapter Nov 12, 2026
@kiyonoRin
kiyonoRin force-pushed the add-moondrop-pill branch 2 times, most recently from 546edd0 to 5ac6da2 Compare November 12, 2026 14:01
PILL is the base model of ZZZ-ANGELS-OWS and speaks the same MOONDROP GAIA
subset. A 2026-11-12 btsnoop capture of the official app (firmware 1.6.0,
SPP DLCI 2) confirms the API-version handshake and battery exchange are
byte-identical to the ZZZ-ANGELS-OWS capture: the handshake answers
00 04 03 01, supported components are 01 02, and 01 64 02 50 decodes to
left 100% / right 80%, matching the vendor app display.

The model is an open-ear clip headset without noise cancellation: 151
captured frames across three sessions contain zero noise commands and the
vendor device page exposes no noise entry, so no noise mode is opened.

Reuse MoondropZzzAngelsWireCodec and MoondropZzzAngelsBatteryPolicy unchanged
and leave the revalidated ZZZ-ANGELS-OWS adapter and session untouched.
Control apps reuse the registered MOONDROP entry; no new package or scope.

Device validation: first connection and private per-ear battery projection,
MiLink card, reconnect replaying the byte-identical handshake, vendor app
yield recovery after backoff, and settings navigation all pass; ANC
capabilities report zero modes. While one earbud sits in the case its reading
stays frozen, the next refresh after removal returns the real value without a
reconnect, and the vendor app shows the same frozen reading, so that path is
documented as a limit rather than a verified case.

Verification: testDebugUnitTest, :protocol-test:assembleDebug,
:system-module:lintRelease and :system-module:assembleRelease pass
(83 suites, 655 tests, 0 failures); markdownlint-cli2 and
tools/validate-docs.mjs pass.
@silverpoetry
silverpoetry merged commit 28352e6 into silverpoetry:main Nov 13, 2026
1 check passed
@silverpoetry

Copy link
Copy Markdown
Owner

@kiyonoRin 谢谢提供完整的抓包说明、适配代码和实机记录。这个 PR 的范围与现有架构一致,CI 通过后已保留你的原提交合入,随 3.3.0 发布。

我补齐了应用内兼容性条目、文档矩阵、控制 App 型号列表和来源署名,没有修改你已实测的协议与生命周期实现。左右耳电量、入盒期间读数冻结和充电盒/充电标志的边界均按记录保留。后续若有固件差异,欢迎继续提交日志或新的 PR。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants