Formal, evidence-based specification of the Pixel Buds Pro 2 communication
protocol (libmaestro / libgfps over Bluetooth Classic RFCOMM, plus relevant
BLE/GATT and Fast Pair mechanisms), as reconstructed from captures, official
Fast Pair specifications, and APK analysis. This is the source of truth for
the app's implementation — see ARCHITECTURE.md §5 for how the app's code
consumes it.
This document intentionally contains only protocol facts, hypotheses, and
assumptions — not implementation details (those live in ARCHITECTURE.md).
There is no separate working-notes buffer: new protocol knowledge is recorded
directly in the relevant capture's CAP-NNN-FINDINGS.md and promoted straight
into this document once confirmed (PROJECT_RULES.md §2) — PROTOCOL_NOTES.md
has been retired; its content was consolidated into this document (see §6,
§8's changelog).
Rule: every entry below carries a status. Nothing is implemented on the
basis of an ASSUMPTION without explicitly accepting that as a risk, recorded in
DECISIONS.md.
Status legend (this is the project-wide canonical legend — every other document's
legend, e.g. PROJECT_RULES.md §1 rule 1, REVERSE_ENGINEERING.md,
DESKRESEARCH_FINDINGS.md, and every CAP-NNN-FINDINGS.md, must be consistent
with this one, not the reverse):
- 🟢 FACT — observed and repeatedly confirmed (multiple captures/experiments, or directly stated in Google's official Fast Pair specification as the documented mechanism and confirmed as what the Buds Pro 2 use).
- 🟡 HYPOTHESIS — observed or plausible, not yet independently confirmed against our own capture.
- ⚪ ASSUMPTION — not yet tested, assumed based on comparable/official protocols or an older Pixel Buds generation.
- 🔴 OPEN QUESTION — genuinely unresolved: no specific hypothesis or working
assumption exists yet, only an identified gap. Formally documented
2026-08-20 — this tier was already in heavy, load-bearing use throughout
this document's body (16 occurrences) and in every
CAP-NNN-FINDINGS.mdfile's own legend before it was added here; this entry reconciles the written rule with the practice that already existed, perPROJECT_RULES.md§1 rule 1's matching update.
| Field | Value |
|---|---|
| Last verified against firmware | release_5.203 |
| Primary source | qzed/pbpctrl (Linux/Rust) — protocol knowledge only, no code reused (see AGENTS.md §12) |
| Secondary sources | Official app/web screenshots (SCREENSHOTS_PIXEL_BUDS_APP.md, SCREENSHOTS_PIXEL_BUDS_WEB_APP.md), Google Fast Pair Service (GFPS) specification (developers.google.com/nearby/fast-pair), pbtk-extracted schemas |
| Verification method | Android CAP-nnn-btsnoop_hci.log capture and analysis (see CAPTURE_BLUETOOTH_HCI_SNOOP.md) |
| Promoted from | capture CAP-NNN-FINDINGS.md files directly (formerly via PROTOCOL_NOTES.md, retired 2026-08-15) |
Extend this table as additional firmware versions are tested — per AGENTS.md §4 and
ARCHITECTURE.md §8, protocol behavior is not assumed stable across firmware updates,
so each row should be added on its own evidence, not by assuming continuity with the row
above it.
| Firmware version | Known protocol-relevant differences | Source |
|---|---|---|
release_5.203 |
ADAPTIVE ANC mode present; 5-band EQ; L/R/Case independent battery reporting (now understood to likely be the Fast Pair Battery Notification, §4.3 Option A) |
[VERIFIED-LOCAL] (Screenshot UI analysis, 2026-07-30) |
Note (2026-08-14) — four different version-like strings are now documented across captures; not yet reconciled into a single confirmed firmware version. Listed here explicitly, each with its source channel, pending a capture that also records the app's own "More settings" firmware display (never captured to date) to resolve which (if any) is the actual, user-visible firmware version:
"release_5.203"— this table's current baseline,[VERIFIED-LOCAL]2026-07-30 (screenshot UI). Also found on-the-wire, independently, in three sessions on DLCI 0x08's private envelope (Group0x03Code0x02,CAP-004-FINDINGS.md§5a Task 2; byte-for-byte identical toCAP-002frame 49028) and in DLCI 0x08's Group0x02Code0x04value (CAP-002-FINDINGS.md§2a, a third independent sighting on the same DLCI)."Revision 6"— found on DLCI 0x04, the official Fast Pair Message Stream's Device Information group, Code0x09(spec-confirmed field identity = "Firmware version" string,CAP-002-FINDINGS.md§3), and independently corroborated via a direct GATT characteristic read (handle0x0f2a,CAP-002-FINDINGS.md§4). GMS-and/or-app-dependent (unresolved which) — absent inCAP-004(CAP-004-FINDINGS.md§4a, where GMS was disabled and the official app was uninstalled together — a confound not yet isolated), unlike the DLCI-0x08 strings above."cape2_sm"(hardware/board codename) and"500m"–"500p"(config-variant identifiers) — found alongside a third"release_5.203"sighting on DLCI 0x08's Group0x02Code0x04(CAP-002-FINDINGS.md§2a) — plausibly per-preset/per-profile identifiers, not mapped to any user-visible feature.These are not competing readings of one field — they are on-the-wire values from at least two structurally independent channels/mechanisms (§2.3's table), and
"Revision 6"specifically looks more like a protocol/schema revision number than a firmware build string (CAP-002-FINDINGS.md§3). Left as 🔴 OPEN QUESTION which (if any) is what the app itself would call "the firmware version" until a capture also records that app screen.Documentation-gap fix (2026-09-04,
CAP-036-FINDINGS.md§12.3) — mechanical, no new sign-off needed: DLCI 0x04's own Device Information group (Group0x03, distinct from DLCI 0x08's private numbering above) has two further codes that reached 🟢 FACT inCAP-002-FINDINGS.md§3 back on 2026-08-10 (direct fetch of Google's officialdeviceinformationspec page, worked-example byte match) but were never copied into this document — fixed now: Code0x01= "Model ID", valueda 2d b1, constant across every session checked to date (CAP-001,CAP-002,CAP-010,CAP-036). Code0x02= "BLE address updated", a 6-byte value that rotates every session (77:96:2c:96:68:1c,53:0c:b4:c8:06:3d,75:51:27:4f:ae:59,51:70:22:b8:72:2f,5e:6a:14:ce:17:9f,44:d6:94:50:f0:4e— six distinct sessions, six distinct values) — consistent with a rotating private/resolvable BLE address, exactly as its name states.CAP-036additionally cross-checked its own session's value against live BLE advertising traffic in the same log and found it broadcasting a Fast Pair (0xFE2C) advertisement 407 times — see §4.3 Option A's device-attribution note below; this is 🟡 HYPOTHESIS (one session), not itself part of this documentation-gap fix.Update (2026-08-21),
CAP-023— the capture this note asked for now exists. Device details → More settings → Firmware update shows "Device firmware version": Left earbudrelease_5.203, Right earbudrelease_5.203, Caserelease_5.203— video-confirmed on-screen, same session, at 08:24:17. The DLCI 0x08 private-envelope string documented above (Group0x03Code0x02) was independently present in this same session's connection-time handshake (frame 849, 08:23:46.038, before the screen was even opened) with the byte-identical value"release_5.203". This is the first same-session, on-screen-confirmed match this project has recorded — 🟢 FACT, promoted 2026-08-23 (maintainer sign-off obtained perAGENTS.md§6; seeDECISIONS.mdADR-012):"release_5.203"is what the app calls "the firmware version," and"Revision 6"is not surfaced anywhere in the app's own UI. Still open: what"Revision 6"itself represents, if not the user-facing firmware version. Also established as a clean negative finding: tapping the manual "Up to date" check and opening this screen produce zero RFCOMM traffic — the display is read from already-cached connection-time data, not queried live. SeeCAP-023-FINDINGS.md§3–§4.
Note (2026-08-28), 2026-08-28 project-wide audit finding
XC-03— an unrelated version identifier has never been reconciled against the baseline above.TESTPLAN_BLUETOOTH_HCI_SNOOP.md§6 states thatANC-003(Adaptive)/HEAD-*/LOUD-001were "added in firmware 4.467," using a different-looking version scheme from this project's own confirmed"release_5.203"baseline above. ⚪ ASSUMPTION, not yet checked either way: whetherrelease_5.203numerically satisfies a4.467threshold, or whether the two are even comparable version schemes at all — no capture or external source consulted by this project states the relationship. Not currently blocking anything: all three gated features are confirmed present and working on this project's test device regardless (ANC-003/Adaptive sinceCAP-001;HEAD-*sinceCAP-020) — this is a latent documentation gap, flagged so a future firmware-compatibility check (ARCHITECTURE.md§8) doesn't silently assume a relationship that was never verified.
| Transport | Used for | Status |
|---|---|---|
Bluetooth Classic RFCOMM (BluetoothSocket, SPP-style) |
Three coexisting, independently-framed DLCIs (see §2.3's table): the official Fast Pair Message Stream (DLCI 0x04, 🟢 FACT, carries the confirmed ANC command), libmaestro's candidate Pigweed pw_hdlc channel (DLCI 0x02, 🟡 HYPOTHESIS that this is specifically libmaestro), and a third, still-unidentified private envelope (DLCI 0x08, 🔴 open identity) |
🟢 FACT that these are three separate channels, not one shared channel — see §2.3 |
| BLE advertisement | Fast Pair "Battery Notification" extension — passive battery status broadcast, no active connection required | 🟢 FACT (mechanism, official spec); 🟡 HYPOTHESIS (confirmed as what the Buds Pro 2 send) |
| BLE GATT | Possible standard Battery Service (0x180F) for the case; otherwise not confirmed to be used for control |
⚪ ASSUMPTION |
All application data over BluetoothSocket must be parsed into discrete frames
before being handed to the protobuf deserializer, since RFCOMM is a byte stream
and is not message-delimited at the socket level.
🟢 FACT (mechanism exists, officially documented) / 🟡 HYPOTHESIS (that
libmaestro control traffic rides on it):
| Field | Size | Notes |
|---|---|---|
| Message Group | 1 byte | Selects the category of message (e.g. 0x04 = Action group; 0xFF = ACK) |
| Message Code | 1 byte | Selects the specific message within the group (e.g. 0x01 = Ring, within the Action group) |
| Additional Data Length | 2 bytes, big-endian | Length of the Additional Data field |
| Additional Data | variable | Message-specific payload |
No magic byte, no checksum — integrity relies on RFCOMM's own reliable, ordered
delivery. Confirmed worked example from the spec: an ACK for a "ring" action
(group 0x04, code 0x01) is encoded as 0xFF 0x01 0x00 0x02 0x04 0x01.
Correction (2026-08-23): the byte sequence above does not match the official spec's own worked example — caught during an external audit pass that fetched
developers.google.com/nearby/fast-pair/specifications/extensions/acknowledgementdirectly rather than relying on this document's prior (miscited) restatement. The spec's actual worked example for acknowledging a received ring action (0x04010002013C) is an ACK of0xFF 0x01 0x00 0x04 0x04 0x01 0x01 0x3C— length0x0004, four data bytes (echoed group0x04, echoed code0x01, plus two additional bytes0x01 0x3C), not the six-byte, two-data-byte value stated above. The framing structure itself (Group/Code/Length/Data) is unaffected — only this specific worked-example citation was wrong. See §4.4 below andCHANGELOG.md's 2026-08-22/23 entry for the full correction and its effect on the Find My Buds ACK-variant comparison.
Google's Fast Pair specification explicitly allows partners to extend the
Message Stream with vendor-specific message groups — so it is architecturally
plausible that libmaestro is "Message Stream, with Google's own private
message group(s)" rather than a fully separate protocol.
🟡 HYPOTHESIS:
| Field | Size | Notes | Confidence |
|---|---|---|---|
| Magic bytes | TBD (commonly cited as 1 byte, e.g. 0x5A) |
Start-of-frame marker, needed to resync a buffered stream after a partial/corrupt read | 🟡 Medium |
| Payload length | 2 bytes (16-bit) | Size of the protobuf payload; whether it includes the Channel/Msg ID byte(s) is unconfirmed | 🟡 Medium |
| Channel / Message ID | TBD size | Selects which .proto message handler decodes the payload |
🟡 Medium |
| Protobuf payload | variable | Serialized libmaestro protobuf message |
🟢 High (protobuf itself is self-describing once the correct .proto is known) |
| Checksum/CRC | optional, TBD | Algorithm not confirmed (CRC16? XOR? absent on some channels?) | 🔴 Low |
A point against this hypothesis: the officially documented Message Stream format (§2.1) has no checksum field at all, which is inconsistent with this hypothesis if the two channels turn out to be the same one.
🟢 FACT (2026-08-12, deskresearch task, evidence below) that the RFCOMM
channel documented as "channel 1/DLCI 0x02" in CAP-001-FINDINGS.md §2 (and
its CAP-002/CAP-003 counterparts) is not an unidentified proprietary
envelope, but a byte-for-byte match to Pigweed's pw_hdlc wire format
(https://pigweed.dev/pw_hdlc/), the exact transport qzed/pbpctrl's own
project notes describe Maestro as using — see
https://raw.githubusercontent.com/qzed/pbpctrl/main/docs/Notes.md,
consulted per AGENTS.md §12/DECISIONS.md ADR-003 (protocol knowledge
only, no code reused): "The protocol is implemented using the pigweed RPC
library... the RPC messages are wrapped in High-Level Data Link Control
(HDLC) U-frames."
Confirmed field-by-field, replacing §2.2's placeholder row for this channel:
| §2.2 placeholder field | Confirmed value on DLCI 0x02 | Confidence |
|---|---|---|
| Magic bytes | Not a magic byte — standard HDLC flag 0x7E delimits every frame (start and end), with 0x7D-prefixed byte-stuffing (escaped byte X transmitted as 0x7D (X XOR 0x20)) for any literal 0x7E/0x7D in the frame body — this is why naive byte-splitting on 0x7E alone previously looked structurally messy |
🟢 High — verified below |
| Payload length | No explicit length field — flag-delimited framing makes one unnecessary (length = distance to the next unescaped 0x7E) |
🟢 High |
| Channel / Message ID | An HDLC Address field, LEB128-varint-encoded (1–3+ bytes) immediately after the opening flag, followed by a single Control byte. In CAP-001–CAP-003/CAP-006/the 11:42 CAP-010 session, exactly two distinct address values are observed: 0x00 (both directions) and 0xD180/53632 (Buds→phone only). Not a fixed/exhaustive set, per a 2026-08-17 deskresearch pass (§6, DESKRESEARCH_FINDINGS.md): CAP-005/CAP-007 additionally show 0x1e80/0x2680 (Sent) answered by 0xe980 (Rcvd), appearing specifically around connection-(re)open events and carrying the same content as the 0x00/0xD180 pair — HYPOTHESIS that this field is a per-connection-negotiated pw_rpc handle, not a small fixed set |
🟢 High for the field's existence/position; 🟡 Medium for what any specific value means, and now also for whether the address set itself is fixed |
| Checksum/CRC | Confirmed as CRC-32 (IEEE 802.3 / zlib polynomial, little-endian byte order) over the unescaped Address+Control+Data — exactly matching Pigweed's documented use of pw_checksum's CRC-32 frame check sequence |
🟢 High — see verification below |
Verification method: exported every RFCOMM payload on this DLCI across CAP-001, CAP-002,
CAP-003 (CAP-004 never opens this channel) via
tshark -r CAP-NNN-btsnoop_hci.log -Y "btrfcomm.dlci==0x02 and btrfcomm.len > 0" -T fields -e frame.number -e frame.time_epoch -e data.data,
split each RFCOMM payload on the 0x7E flag byte, HDLC-unescaped each resulting
sub-frame (0x7D <X> → X XOR 0x20), then computed zlib.crc32() over
everything except the trailing 4 bytes and compared:
def unescape_hdlc(data):
out = bytearray(); i = 0
while i < len(data):
b = data[i]
if b == 0x7d:
i += 1
out.append(data[i] ^ 0x20)
else:
out.append(b)
i += 1
return bytes(out)
body, trailer = unescape_hdlc(subframe)[:-4], unescape_hdlc(subframe)[-4:]
assert struct.pack('<I', binascii.crc32(body) & 0xffffffff) == trailerResult: 640/640 sub-frames matched (100%), across all three independent captures, zero
exceptions. Example (CAP-001 frame 1348, 00 4b 03 10 15 1d ea 71 de 7d 5e 25 e3 a5 ec 28 f9 67 61 b5): unescaping the 7d 5e sequence yields the true byte 0x7e (5e XOR 20), after
which crc32(00 4b 03 10 15 1d ea 71 de 7e 25 e3 a5 ec 28) = f9 67 61 b5 (little-endian) —
matches the trailing 4 bytes exactly. Before applying the unescape step, only sub-frames that
happened to contain no literal 0x7D/0x7E bytes matched (a subset); after unescaping, the
match rate is unconditional. This is a reproducible, standard-algorithm match, not a coincidental
byte pattern — per PROJECT_RULES.md §1's promotion rule (byte-for-byte match to a documented
mechanism, replicated across ≥2 independent captures), the framing mechanism itself (flag +
escape + LEB128 address + control + CRC-32) is promoted to 🟢 FACT.
Channel ownership — promoted to 🟢 FACT (2026-08-30, maintainer sign-off, DECISIONS.md
ADR-018, Option 2 — a narrow promotion, see that ADR's "What this new evidence is, precisely — and
what it is not"): this channel is the Pixel Buds companion app's own internal RFCOMM socket,
not some other/generic Pigweed-RPC-based Google service. Evidence, from an AI-run APK
keyword-search pass (DECISIONS.md ADR-017's mechanical-assistance boundary;
REVERSE_ENGINEERING.md's fzd/gbm/gau/gbd/fxm/fsz/fut/fux/ghd/goq entries,
v1.0.955078536-10253511) cross-checked against captures already in captures/: the app's own
decompiled code (gbm.java:35-43) selects between two internal RFCOMM sockets by checking which of
two 128-bit SDP UUIDs (fzd.java:9) is present in the discovered service set, logging "Provide pigweed internal rfcomm socket" for UUID 25e97ff7-24ce-4c4c-8951-f764a708f7b5. That exact UUID's
SDP Service Search Attribute Response resolves to RFCOMM server channel 1 in three independent
captures (CAP-001 frame 1327 @ 42.534s, CAP-002 frame 1327 @ 42.534s, CAP-032 frame 1632 @
104.943s), and tshark's own btrfcomm.dlci field reads 0x02 for every frame once that
channel opens in each of them (CAP-001 frame 1334 @ 42.545s; CAP-032 frame 1645 @ 105.173s) — a
direct wire reading, not the 2×channel arithmetic applied blind. Independently corroborated a
fourth time (2026-08-30 audit finding, CAP-033-FINDINGS.md §3): CAP-033's own SDP browse (frame
1279, within the 15:18:09.417–15:18:18.822 window) returns a full named-service table naming this
same UUID's service "MAESTRO APP" on RFCOMM channel 1 — the first time this correlation is
confirmed from a human-readable SDP service-name string rather than only the raw UUID/channel-number
match above. The same code path is where the
app's own maestro_pw.* pw_rpc services are dispatched (Maestro, HeadGesture, EartipFitTest,
Dosimeter, JitterBuffer, Multipoint, DynamicServerConfigService), including a WriteSetting
call (fsz.java:75) confirmed via a surviving Kotlin function-reference metadata string
(fsz.java:223) to route through dev.pigweed.pw_rpc.MethodClient against the app's own
com.google.android.apps.wearables.maestro.companion.pw.hdlc.RouteProto$Route.
What remains HYPOTHESIS, not promoted by ADR-018: that this channel's opaque payload content
specifically carries libmaestro's ANC/EQ/settings-write commands (as opposed to, say, only
diagnostic/telemetry RPCs riding the same socket) — no Maestro-specific content (an
ANC-mode-change command, an EQ write, or any decoded pw_rpc service/method name applied to a real
value) has been decoded from this channel's payload bytes yet — the "Rcvd"-direction payloads
decode as protobuf (device serial "1779298694" + firmware "release_5.203", per
CAP-001-FINDINGS.md §2) and the "Sent"-direction payloads remain opaque 16-byte-ish blocks.
🟡 HYPOTHESIS (strong): DLCI 0x02's Sent-direction payloads specifically carry libmaestro's
settings-write commands. Still requires either (a) a pw_rpc/protobuf schema to decode the opaque
"Sent" payloads and recognize an actual ANC/EQ method call, or (b) a properly isolated capture
(Group B, single ANC/EQ action per window) correlating a specific "Sent" write here with a specific
user action, before this can move to 🟢 FACT.
Per AGENTS.md §6 / ARCHITECTURE.md §2.1's implementation gate: the FACT-level framing
confirmation above, together with the channel-ownership FACT confirmed by ADR-018, still does not
cover the full §2.3 question below (what protobuf schema libmaestro's actual settings commands
use, and which Sent-direction bytes correspond to which command). FrameEncoder/FrameDecoder
implementation for the settings-command content still requires its own DECISIONS.md ADR
recording that determination before any code is written against it — ADR-018 only settles channel
ownership, not payload semantics, per that ADR's own explicit scope note.
Update (2026-08-30) — the protobuf schema question above is now answered, for the specific fields
tested: it is libmaestro's own WriteSetting request type (Java class qhr), recovered by APK
static analysis and byte-confirmed against 2 sampled wire fields. 🟢 FACT, promoted 2026-08-30
(maintainer sign-off, DECISIONS.md ADR-019): re-decoding CAP-020 frames 1741/1935 (already
identified as field5{field4{...}} per ADR-013) one level deeper than originally notated shows the
"..." is exactly qhr's own protobuf oneof, addressed via standard wire-format tags — confirmed for
qhr field 4 (frame 1741, value 1) and field 29 (frame 1935, value 2), both matching the
independently-recovered app schema with no discrepancy. See §4.5.3 below for two further fields
(7 and 12) confirmed the same way. Scope, precisely: this closes path (a) above (a pw_rpc/protobuf
schema to decode the opaque payloads) for the specific fields sampled — it does not itself claim every
one of qhr's 38 fields has been wire-verified, and does not by itself promote the broader "Sent"
HYPOTHESIS above to FACT for fields not yet sampled; it substantially strengthens that HYPOTHESIS
without fully closing it. See DECISIONS.md ADR-019 for the complete scope note.
DLCI 0x08, by contrast, does not match this framing at all (checked and ruled out, not
assumed): no 0x7E flag bytes delimit its frames, no escaping, and its own
[Group:1][Code:1][Length:2B-BE][Value] envelope (CAP-001-FINDINGS.md §2, CAP-004-FINDINGS.md
§5a) has an explicit length field HDLC framing doesn't need. It is architecturally a third,
independent private protocol — neither the official Fast Pair Message Stream (DLCI 0x04, §2.1,
spec-verified) nor Maestro's Pigweed-HDLC channel (DLCI 0x02, above) — still 🔴 unidentified. See
§2.3 below for how this reframes the central open question.
Original framing (superseded by the finding above, kept for the record per PROJECT_RULES.md
§3's non-destructive-correction convention): is libmaestro's ANC/EQ control channel the same
RFCOMM channel as the Fast Pair Message Stream (§2.1), using a custom/vendor Message Group ID — or
a separate RFCOMM channel/PSM with its own proprietary envelope (§2.2)?
Update (2026-08-12): this is no longer a clean binary choice — three structurally distinct RFCOMM sub-protocols are now evidenced across the four captures to date, all coexisting within the same RFCOMM multiplexer session:
| DLCI | Framing | Status | Content |
|---|---|---|---|
| 0x04 | Official Fast Pair Message Stream (§2.1) | 🟢 FACT (spec-verified, CAP-002-FINDINGS.md §3) |
Device Information (Group 0x03), SASS (Group 0x07), and the officially-documented Hearable Controls extension (Group 0x08) — Get/Set/Notify ANC state, see §4.1 below; GMS-and/or-app-dependent, unresolved which (absent in CAP-004, §4a there — GMS disabled and the app uninstalled together, a confound) |
| 0x02 | Pigweed pw_hdlc (§2.2a) |
🟢 FACT for the framing and for channel ownership (this is the companion app's own internal RFCOMM socket, ADR-018); 🟡 HYPOTHESIS (strong) that its Sent-direction payload content specifically carries libmaestro's settings-write commands |
Opaque ~16-byte "Sent" blocks (phone→Buds, unresolved content) and protobuf-decodable "Rcvd" blocks (device serial + firmware) — not GMS-dependent (present regardless in every capture that opens it) |
| 0x08 | Private, undocumented [Group][Code][Length][Value] envelope, structurally resembling §2.1's shape but with its own Group/Code numbering (CAP-004-FINDINGS.md §5a) |
🟢 FACT that it's a real, decodable envelope; 🔴 OPEN QUESTION what protocol it belongs to | One-time capability/setup handshake + a low-rate periodic status ping; not GMS-dependent (CAP-004-FINDINGS.md §4b) |
Resolved for ANC specifically (2026-08-12) — DLCI 0x04's Group 0x08 carries the actual ANC
set/get/notify commands, on the official Fast Pair Message Stream, not a proprietary envelope.
See §4.1 below for the full byte-level evidence: Google's own "Hearable Controls" extension page
documents Group 0x08 Codes 0x11/0x12/0x13 (Get/Set/Notify ANC state) with an exact,
byte-for-byte match to frames already present in CAP-001, including a clean content-and-timing
correlation to 4 of that capture's 6 recorded ANC taps. This resolves this section's original
question for the ANC feature specifically — it does not ride on libmaestro (DLCI 0x02) or
the DLCI-0x08-private-envelope at all. What remains open: whether EQ, touch/head-gesture
config, and other non-ANC settings follow the same pattern (an as-yet-undiscovered official Fast
Pair extension) or genuinely require libmaestro's separate channel (DLCI 0x02) — libmaestro's
own opaque "Sent" payloads there remain undecoded, so this is not yet answered either way for
those features.
Resolution path (updated): for EQ/touch/other settings, first check whether any further
official Fast Pair extension pages (beyond Hearable Controls, Device Action, SASS, Device
Information) document a matching Group ID before assuming libmaestro/DLCI 0x02 involvement — the
ANC case shows the "proprietary envelope" assumption was wrong for at least one whole feature
area. Where no official extension covers it, decode DLCI 0x02's opaque "Sent" payloads (most
plausibly via a pw_rpc/protobuf service definition, once extracted per §3) or correlate one
against an isolated action (CAPTURE_BLUETOOTH_HCI_SNOOP.md Group B, properly isolated this time).
Addendum (2026-08-14), deskresearch task — DLCI 0x08 and DLCI 0x02 evaluated explicitly, side
by side, as libmaestro candidates against pbpctrl's own transport description (not the
pbpctrl code — only its published protocol knowledge, per AGENTS.md §12/DECISIONS.md
ADR-003). pbpctrl's own project notes
(https://raw.githubusercontent.com/qzed/pbpctrl/main/docs/Notes.md, re-fetched and checked
specifically for transport/framing detail) state, verbatim: "The protocol is implemented using
the pigweed RPC library... the RPC messages are wrapped in High-Level Data Link Control (HDLC)
U-frames." The document gives no magic-byte, length-field, or channel-ID detail beyond that
(confirmed by a targeted re-read — those fields are simply not documented upstream), and the
project's own README gives no RFCOMM channel/UUID specifics either. This means the only concrete
transport signature pbpctrl actually publishes for Maestro is "HDLC U-frames wrapping pw_rpc
protobuf messages" — nothing else in PROTOCOL.md §2.2's placeholder field list (magic byte,
explicit length field, checksum) is independently confirmed by the upstream source; §2.2a already
derived the length/checksum mechanics empirically from the capture bytes themselves, not from
pbpctrl's docs.
Re-checked again 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 2) — clean
negative, no further published detail exists. A fresh fetch of the same document found nothing
beyond the one paragraph already quoted above: it restates "the pigweed RPC library"/"HDLC U-frames"
and, at a purely feature-list level, that the protocol "allows for changing settings" including
"noise-cancelling, equalizer, balance" and "getting hardware/firmware information" — no opcode,
field number, or byte-layout detail for any of these settings exists anywhere in the document. This
project's own independently-derived qhr/EQ/ANC findings (§4.1/§4.2/§4.5, REVERSE_ENGINEERING.md)
are neither strengthened nor contradicted by anything further here — pbpctrl's published notes
simply do not go deeper than the transport-framing paragraph already cited.
Checked against that one confirmed signature, the two candidates diverge sharply:
Field (per §2.2's Hypothesis B placeholder / pbpctrl's stated mechanism) |
DLCI 0x02 (§2.2a) | DLCI 0x08 (CAP-001-FINDINGS.md §2, CAP-004-FINDINGS.md §5a) |
|---|---|---|
Framing mechanism vs. pbpctrl's stated "HDLC U-frames" |
Match — 0x7E-flag-delimited, 0x7D-escaped, exactly HDLC framing |
No match — no 0x7E flag bytes anywhere, no escaping; framed instead by an explicit [Group:1][Code:1][Length:2B-BE][Value] header, structurally the Message Stream shape (§2.1), not HDLC |
| "Magic bytes" (Hypothesis B placeholder) | No fixed magic value — the HDLC flag 0x7E itself is shared start/end framing, not a distinguishing sync byte |
No magic byte either — but for a different reason: framing is length-delimited (TLV), which doesn't need one, same as the official Message Stream |
| "Payload length" field | Not explicit — implicit via flag-delimiting (HDLC's own mechanism, matching pbpctrl's description) |
Explicit 2-byte big-endian length — this is not what pbpctrl describes for Maestro at all; it is exactly Message Stream §2.1's "Additional Data Length" field shape |
| "Channel / Message ID" | HDLC Address (LEB128 varint) + Control byte — consistent with pw_rpc's own channel/service addressing scheme | Group (1B) + Code (1B) — consistent with Message Stream's Group/Code addressing, not pw_rpc's |
| Checksum | CRC-32 (IEEE 802.3/zlib), confirmed 640/640 — matches Pigweed's own documented pw_checksum FCS convention exactly |
None found (CAP-004-FINDINGS.md §5a's reassembling parser closes cleanly on every session with 0 leftover bytes and no checksum-shaped trailer ever isolated) |
Conclusion, stated per PROJECT_RULES.md §1's promotion rules: DLCI 0x02 is now a
direct, mechanism-level match to the one concrete thing pbpctrl actually documents about
Maestro's transport (HDLC U-frames) — this was already known (§2.2a) but had not previously been
stated as a comparison against a rejected alternative. DLCI 0x08, by contrast, structurally
matches the official Fast Pair Message Stream's TLV shape (§2.1) applied to a private Group
namespace — it does not use HDLC framing at all, and therefore does not match pbpctrl's own
description of Maestro's transport mechanism. This is a genuine negative result for DLCI 0x08
as a libmaestro candidate specifically, not merely "still unresolved" — recorded here as such
rather than left ambiguous. Status, per the evidence rules (updated 2026-08-30, DECISIONS.md
ADR-018): DLCI 0x02 as the companion app's own internal RFCOMM channel is now 🟢 FACT (see
§2.2a's "Channel ownership" finding — an SDP-record/APK-code correlation, not framing-mechanism
match alone). What remains 🟡 HYPOTHESIS (strong), unchanged in strength, is narrower than the
original "DLCI 0x02 = libmaestro" framing above: whether this channel's Sent-direction payload
content specifically carries libmaestro's settings-write commands, since no Maestro-specific
content (an ANC/EQ method call) is decoded yet. DLCI 0x08's identity remains 🔴 OPEN QUESTION as before, but is now narrowed by a
checked negative: not libmaestro (mechanism mismatch against the one concrete signature
pbpctrl publishes), leaving "a lower-level Nearby/CDM companion-device negotiation independent
of both Fast Pair and Maestro" (CAP-004-FINDINGS.md §5a's existing framing) as the leading
remaining candidate for it. §2.3's three-channel table above is not restructured into a binary
choice — it already correctly shows three coexisting channels; this addendum only sharpens which
one the libmaestro hypothesis should now concentrate on.
Update (2026-08-30, audit finding, CAP-033-FINDINGS.md §3) — a new lead, not a resolution.
CAP-033's SDP browse names DLCI 0x08's service "GSND CONTROL" (UUID
f8d1fbe4-7966-4334-8024-ff96c9330e15, RFCOMM channel 4) on the wire — the first time this channel
has had anything beyond a raw DLCI number to refer to it by. This is a concrete new search target for
a future APK keyword pass (a grep -ri "gsnd" sweep of jadx-output/ for v1.0.955078536-10253511
found no match as of this update), not a resolution of the channel's identity — knowing it is named
"GSND CONTROL" does not by itself reveal its Group/Code semantics or confirm/deny the Nearby/CDM
candidate above. 🟡 HYPOTHESIS awaiting maintainer review, per AGENTS.md §6 — not committed as a
promotion. The same browse also named DLCI 0x0a "GSND AUDIO" (CAP-021-FINDINGS.md §4a's
still-unattributed 1123-frame burst channel), DLCI 0x06 "DEBUG APP", and DLCI 0x12 "BTIS" — none
previously documented anywhere in this project.
Handling rule (unchanged regardless of which hypothesis is confirmed, per
AGENTS.md §6 and ARCHITECTURE.md §5): any checksum mismatch, or any frame
that fails to parse against the relevant invariants, is dropped silently and
surfaced internally as BudsError.MalformedFrame — never a crash, never a
best-effort guess at the payload.
The libmaestro control channel communicates using serialized Protocol
Buffers. Schemas are extracted from the official companion app APK using tools
such as pbtk — an AI session may run pbtk itself and search/explain its
output mechanically, but never hand-reconstructs or guesses a schema, and
never decides which extracted finding is relevant (see AGENTS.md §4/§6,
DECISIONS.md ADR-017, superseding ADR-003).
| File | Purpose | Status |
|---|---|---|
maestro_pw.proto |
Core control messages, routing, generic request/response envelope | 🟡 HYPOTHESIS |
anc_settings.proto |
ANC / Transparency / Adaptive mode enum | 🟡 HYPOTHESIS |
eq_settings.proto |
5-band equalizer definitions, presets | 🟡 HYPOTHESIS |
hardware_status.proto |
Battery / hardware telemetry query-response | 🟡 HYPOTHESIS — may turn out to be Fast Pair's generic Message Stream "Device Information" messages rather than a Buds-specific schema (see §4.3) |
File names above are best-guess placeholders pending real extraction — see §6 open questions.
-
Feature states confirmed present in the UI (firmware
release_5.203): Off, Active (Noise Cancellation), Aware (Transparency), Adaptive. Status: 🟢 FACT (UI presence, via screenshots). -
Opcode/payload structure — 🟢 FACT, resolved 2026-08-12. Google's official Fast Pair "Hearable Controls" extension (
[OFFICIAL-SPEC]) documents Message Group0x08with three codes:Code Name Direction MAC ACK 0x11Get ANC state Seeker → Provider N N 0x12Set ANC state Seeker → Provider Y Y 0x13Notify ANC state Provider → Seeker N N "Set ANC state" (
0x12) layout ([Group:1][Code:1][Len:2BE][Seeker version:1][ANC settable modes:1][ANC enabled modes:1][New ANC mode index:1][Reserved:16, present iff Len=0x14]), and the mode-index byte uses a one-hot bitmask. Bit-mapping table (rewritten 2026-08-14 for clarity — same underlying mapping as before, no conclusion changed):Spec's own bit name ( hearablecontrolspage, MSB-first numbering)Standard bit position Observed hex value ANC mode Bit 0bit 7 (MSB) 0x80Transparent / Aware Bit 1bit 6 0x40Adaptive Bit 2bit 5 0x20Off Bit 3bit 4 — (not observed) Reserved Bit 4bit 3 0x08ANC / Active Noise Cancelling -
Byte-level match against
CAP-001, exact, no discrepancy: four08 12 00 14 01 e8 e8 XX <16 reserved bytes>frames exist inCAP-001-btsnoop_hci.log(frames 2039, 2132, 2159, 2193; DLCI 0x04). Decoded:ver=0x01, settable=0xe8, enabled=0xe8, new_mode=:Frame Time new_modebyteDecoded ANC mode Nearest video-observed tap ( CAP-001-EVENT-NOTES.md)2039 08:51:41.77 0x40(bit6)Adaptive "Tap Adaptive" @ 08:51:43 2132 08:51:48.14 0x80(bit7)Transparent/Aware "Tap Transparency (2nd)" @ 08:51:49 2159 08:51:53.39 0x08(bit3 — note: spec'sBit 4)ANC/Active Noise Cancelling "Tap Noise Cancellation" @ 08:51:54 2193 08:51:59.20 0x20(bit5)Off "Tap Off (2nd)" @ 08:52:00 All four decoded modes match their nearest tap by content, in the correct sequence, each within ~1–1.5s of the video-observed tap (well inside the ±1s 1fps-video-sampling uncertainty already documented for this capture) — this is not a spec-shape resemblance, it is a confirmed identification with an internal cross-check the spec page itself does not provide. Each
0x12frame is immediately followed (~60–110ms later) byff 01 00 06 08 12 01 e8 e8 XX— the documented ACK shape (§2.1), consistent with the spec'sACK: Ycolumn. This satisfiesPROJECT_RULES.md§1's promotion bar on two independent grounds at once (official spec byte-match and internal content/timing cross-check within one capture). -
Open sub-question, not resolved:
CAP-001's first two ANC taps (Transparency @ 08:51:32, Off @ 08:51:39) have no corresponding0x12frame anywhere in the log — possibly UI-state realization rather than genuine user-initiated sets (the ANC row was still greyed out until shortly before, perCAP-001-EVENT-NOTES.md), not yet confirmed. -
"Notify ANC state" (
0x13):Provider → Seeker, layout[Group:1][Code:1][Len:2BE=0004] [Version:1][UI toggles:1][Settable toggles:1][Current state:1], same one-hot bit layout for the "Current state" byte. Matches the08 13 00 04 01 e8 e8 XXframes independently documented inCAP-001-FINDINGS.md§5 (26 occurrences across the day this capture's log spans) — a periodic/on-change status report, not itself a command. -
"Get ANC state" (
0x11) — opcode identity 🟢 FACT, promoted 2026-09-04 (maintainer sign-off,DECISIONS.mdADR-021): documented above by spec but never previously observed on the wire untilCAP-036(2026-09-04), frame 1169 (08 11 00 00, Sent, DLCI 0x04) —Group=0x08, Code=0x11, Len=0x0000, exact structural match to the spec'sGet ANC state(Seeker→Provider, no payload). Fired 34ms after DLCI 0x04's channel opens during a reconnect — ~9s before the app's own "Active" UI confirmation renders — and answered ~10.7ms later by frame 1182 (08 13 00 04 01 e8 00 20, Rcvd — Notify, current state=0x20=Off), matching the on-screen ANC state confirmed later in the same session (an internal content cross-check, on the same pattern as0x12's own promotion above). -
Trigger-reliability 🟢 FACT, promoted 2026-09-04 (maintainer sign-off,
DECISIONS.mdADR-022): the08 11/08 13query/response pair, initially a singleCAP-036sample, has since been found in 17 occurrences across 10 independent capture files (CAP-006×3,CAP-010×2,CAP-016×1,CAP-019–CAP-024×1 each,CAP-025×5,CAP-036×1), with zero misses against a precisely-narrowed trigger condition: it fires every time DLCI 0x04 (re)establishes and proceeds to carry real Message Stream payload — including channel-level bounces that don't involve the underlying classic ACL link reconnecting at all (CAP-006,CAP-025each show this within a single continuous ACL connection). BareSABM→UA→DISCchannel bounces carrying zero payload do not trigger it (CAP-025, 3 such bounces, confirmed via full DLCI 0x04 frame-type listing). This replication base exceeds the sample size behind several of this project's own existing FACT promotions (e.g.ADR-009's 4-sample ANC-Set opcode,ADR-014's 4-independent-session Option E). Precisely scoped claim, promoted: "DLCI 0x04'sGet ANC state(0x11) fires whenever the channel (re)establishes and carries real Message Stream payload, independent of whether the underlying classic link itself reconnects." -
Settable-toggles byte = whether the Buds are physically docked, 🟢 FACT, promoted 2026-09-05 (maintainer sign-off,
DECISIONS.mdADR-024): of the 17Notifysamples referenced above, 12 showSettable=0xe8and 5 showSettable=0x00— not a connect-time-vs-settled split (both values appear at channel-(re)open moments) but a Buds-in-the-case-vs-not-docked split, video-confirmed at 7 of 7 checked samples with zero counter-examples (DESKRESEARCH_FINDINGS.md2026-09-05 entry, updated same day onceCAP-006-recording.mp4was re-pulled from the phone to replace a corrupted copy):Settable=0x00when both earbuds sit seated in the case's charging slots (CAP-016frame 1521,CAP-036's entire session,CAP-010's fresh-pairing frame — LED lit, dock confirmed on screen),Settable=0xe8when the case is open and empty (CAP-021,CAP-025, and nowCAP-006's first two samples — all slots visually empty, buds off-frame or resting loose beside the case, confirmed via cropped/zoomed frame extraction).CAP-006's own third sample (Settable=0x00, 17:26:55) sits ~1m47s past both the original and replacement video's coverage, so the one within-session0xe8→0x00transition on record is still not itself video-confirmed. Supersedes this section's earlier, narrower "connect-time" framing of the same observation — the distinguishing factor is dock state, not timing. Directly relevant toARCHITECTURE.md§3.1 (State Reconciliation) — confirms the official app performs a comparable read-on-reconnect for ANC state specifically. The sameCAP-036session found no query of any kind (this opcode or otherwise) when a settings screen is opened with nothing touched, across five clean windows (EQ, Controls and gestures, Touch controls, More settings, Multipoint) — that negative result stays 🟡 HYPOTHESIS (one session), not affected by either promotion above (a different sub-question — screen-open vs. channel-(re)establishment). SeeCAP-036-FINDINGS.md§3–§7 for the full decode. -
Sent to: RFCOMM Fast Pair Message Stream, DLCI 0x04 (§2.1/§2.3) — not
libmaestro's Pigweed-HDLC channel (DLCI 0x02, §2.2a) and not the private DLCI-0x08 envelope; both were live candidates before this resolution. -
Expected response: ACK (
0xFF 0x01 0x00 0x06 <echoed group/code/data>), 🟢 FACT, see above. -
Status: 🟢 FACT (opcode, payload layout, and the "set" direction's semantics are all confirmed against official documentation and cross-validated within
CAP-001); recorded inDECISIONS.mdADR-009.FrameEncoderimplementation for this command is blocked pendingCAP-006(ADR-009) — the FACT status above does not by itself establish that every ANC tap reliably produces a command frame; see the open sub-question below andCAP-001-FINDINGS.md§5's risk flag.0x11's opcode identity independently 🟢 FACT as of 2026-09-04 (CAP-036,DECISIONS.mdADR-021) — its trigger-reliability ("fires on every reconnect") remains 🟡 HYPOTHESIS, unaffected by ADR-009's scope. -
Evidence: UI presence (
SCREENSHOTS_PIXEL_BUDS_APP.md,TESTPLAN_BLUETOOTH_HCI_SNOOP.md§1); official spec (developers.google.com/nearby/fast-pair/specifications/extensions/hearablecontrols, consulted 2026-08-12, re-fetched 2026-08-30 with no drift — Message Group0x08, Codes0x11/0x12/0x13unchanged; the page still contains no mention of touch-control/gesture configuration, EQ, or a "GSND"-named service, corroborating §2.3's conclusion that EQ/touch settings needlibmaestro's own channel, not a further official extension);CAP-001frames 2039/2132/2159/2193 (Set) and 2041/2134/2162/2195 (ACK), cross-referenced againstCAP-001-EVENT-NOTES.md's tap timeline. -
Verified with experiment: none formally logged in a
CAP-NNN-FINDINGS.mdyet — this is a deskresearch correlation against an existing capture, not a fresh, purpose-built experiment; recommended as a cheap confirmation step (repeat with isolated single taps, perCAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup B) before treating the mode-index bit mapping as final for implementation.
-
Feature confirmed present: 5-band custom EQ (Low Bass, Bass, Mid, Treble, Upper Treble) and presets (Standard/Default, Bass Boost/Heavy Bass, Bass Reduction/Light Bass, Balanced, Vocal Boost, Clarity, Last Saved). Status: 🟢 FACT (UI presence).
-
Opcode/payload structure: not yet extracted from an official spec, but Added 2026-08-15, refined 2026-08-18:
CAP-005-FINDINGS.md(Group T, isolatedEQP-002/EQS-004capture) identified the wire format on DLCI 0x02 (libmaestro's Pigweedpw_hdlcchannel, §2.2a) — an HDLC frame whose payload nests down to a 5×float32band-gain quintet, oneSentframe per changed value. 🟢 FACT for the envelope/quintet shape itself (byte-for-byte reproducible, cross-capture replicated — see below), and 🟢 FACT that this channel is the companion app's own internal RFCOMM socket (§2.2a's "Channel ownership" finding,DECISIONS.mdADR-018); 🟡 HYPOTHESIS (strong) that this specific EQ quintet write islibmaestro's own settings-write content (as opposed to, e.g., a coincidentally-similar payload from another RPC on the same socket) — unchanged from §2.2a's narrower remaining caveat. -
Field-to-band mapping — 🟢 FACT, promoted 2026-08-18, maintainer sign-off obtained 2026-08-28 (
DECISIONS.mdADR-016) (was 🟡 HYPOTHESIS as of 2026-08-15, inferred from only one slider ever having moved in that first capture). The 2026-08-18 session (captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md§5) drags all five sliders individually, three passes each, and directly video-confirms 4 of the 5 fields via finger-on-slider position (plus an on-screen-6.0tooltip for one); the 5th is confirmed by elimination against a perfectly repeating field-change order across all three passes. The mapping matches the earlier (2026-08-15) capture's single-band inference exactly, five days apart, independently:Quintet field (wire order) 1 (first) 2 3 4 5 (last) On-screen band Low bass Bass Mid Treble Upper treble Note: wire field order is the reverse of the on-screen top-to-bottom slider order (UI shows Upper treble first/top; the wire quintet puts it last/field 5) —
FrameEncoder/FrameDecodermust not assume the two orders match without an explicit re-index. -
Band-gain range — 🟢 FACT (2026-08-18, maintainer sign-off obtained 2026-08-28,
DECISIONS.mdADR-016): every slider, dragged to its physical UI extreme, clamps at ±6.0 (CAP-015-FINDINGS.md[2026-08-18] §4 — 8 of 10 extreme-drag samples land at exactly±6.0, the remaining 2 at5.8/5.9, consistent with the drag gesture not quite reaching the slider's physical edge before release, not a different clamp value). 🔴 units not independently confirmed (plausibly dB, not tested against any external reference). -
Confirmed preset quintets — 🟢 FACT (2026-08-18, maintainer sign-off obtained 2026-08-28,
DECISIONS.mdADR-016),[Low bass, Bass, Mid, Treble, Upper treble]:Preset Quintet Last saved(this session's baseline)[0.0, 0.0, 0.0, 0.0, 0.0]Heavy bass [5.0, 3.0, 0.0, 0.0, 0.0]— byte-for-byte identical to the independent 2026-08-15 capture's decodeLight bass [-5.0, -1.5, 0.0, 0.0, 0.0]Balanced [-3.5, 0.5, 1.0, -1.0, 2.5]Vocal boost [-1.0, 0.0, 4.0, 2.0, 0.0]Clarity [-2.0, 0.0, 2.0, 3.0, 5.0]Last saved's quintet is per-account/session state, not a fixed constant — the value above is this specific session's starting point, not a universal default. -
Outer field 16 vs. 18 ("preview" vs. "save") — still 🟡 HYPOTHESIS, reading revised 2026-08-18. The 2026-08-15 capture guessed field 18 = an explicit
Save-button tap, ~5s after the matching field-16 write. The 2026-08-18 capture's 15 field-18 frames each fire only 0.05–1.9s after the preceding field-16 write, with no video-visibleSave-button tap in between for any of them — revised hypothesis: field 18 fires on slider-release (finger lift), not on a separateSave-button tap; the visibleSavebutton may instead persist the whole profile as the account'sLast savedpreset, a separate/higher-level action. Neither reading is confirmed; would need a capture that drags-and-releases without ever tappingSave. Code-level trace, 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 4) — contradicts the "slider-release" revision above, does not confirm it.fyd.d/fyd.e's own call sites in the EQ UI fragment are now traced: field 16 (fyd.e) fires reactively from the slider-drag/preset-selection path, as already understood — but field 18 (fyd.d) is reachable only through a dedicated, self-describing"On click save EQ button"handler wired to a realkey_eq_save_button/title_eq_save_buttonbutton — no code path from slider-release (finger lift) to field 18 was found anywhere in this app version's decompiled source, and no other caller of the save-trigger method exists in the tree. This directly contradictsCAP-015-FINDINGS.md§6's own "field 18 fires on slider-release, no video-visible Save tap" reading for all 15 of that capture's drag-cycles — a genuine, unreconciled tension, not resolved here (plausible explanations — a missed fast/off-screen Save tap, an app-version difference — are unconfirmed). SeeREVERSE_ENGINEERING.md'sqjwentry for the full trace. Re-verification, 2026-09-13 (ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.mdPhase 1) — the 2026-09-08 trace's own "sole caller" claim was incomplete, not wrong about the Save button itself: a genuine second call path to field 18 exists. 🟢 FACT (code existence): a second lambda (hod.java:36) also reaches the same field-18 write, gated on an unsaved-changes-shaped condition and self-describingly logged"Navigate away, save EQ"— registered as an event-bus listener inUserEqFragment, not confirmed as a literal "user left the EQ screen" trigger (what specific event fires it was not traced this pass). This adds a third candidate trigger (Save button / navigate-away-with-unsaved-changes / the still-unconfirmed slider-release reading) — seeREVERSE_ENGINEERING.md'sqjwentry for the full correction. The tension between wire timing and static analysis remains open; a capture isolating all three conditions from each other (no Save tap, no navigation away, genuine slider-release only) is designed asCAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup AO (PROPOSAL — new capture, plannedCAP-053). -
Sent to / expected response: same open questions as §4.1.
-
Status: 🟢 FACT for the wire envelope, the field-to-band mapping, and the ±6.0 range; 🟡 HYPOTHESIS (strong) that DLCI 0x02 is specifically
libmaestro; 🟡 HYPOTHESIS for the preview/save field semantics; 🔴 unconfirmed for the gain units, the Control byte, and the ~13-byte correlation-ID region (§6).FrameEncoder/FrameDecoderimplementation for EQ is explicitly unblocked, 2026-09-03 (DECISIONS.mdADR-020) — the FACT-level elements above (envelope, field-to-band mapping, ±6.0 clamp, preset quintets) are sufficient on their own; the field-16-vs-18 and gain-unit open items above are unaffected and should be resolved before a "Save as preset" UI affordance ships, per that ADR's own scope note. -
Evidence:
SCREENSHOTS_PIXEL_BUDS_APP.md,TESTPLAN_BLUETOOTH_HCI_SNOOP.md§1,captures/CAP-005-2026-08-15_15-02-31_15-03-45-Group_T/CAP-005-FINDINGS.md(first candidate format, single-band sample),captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md(all-5-bands confirmation, range, preset table). -
Verified with experiment: Group T, two independent sessions —
CAP-005(2026-08-15, single Bass slider) andCAP-015(2026-08-18, 5 presets + all 5 sliders, 3 passes each) — see both FINDINGS.md files above.
Five candidate mechanisms, in priority order for implementation. These do
not all share one update model — Options A/B (the Fast Pair mechanisms, on
the official Message Stream, DLCI 0x04) are event-driven (sent on connect or
on value change, per the official spec); Option C (HFP, on a session-local RFCOMM channel —
most commonly observed at DLCI 0x0c, see below) instead
pushes periodically regardless of whether the value changed —
CAP-001-FINDINGS.md §3 observed AT+BIEV=2,100 repeating on a roughly
6–7 second cadence throughout the session, not just on change. An
implementation that treats all four mechanisms as equally event-driven would
either miss HFP's periodic updates (if it only listens for state transitions)
or busy-poll unnecessarily on the Fast Pair mechanisms (if it treats their
event-driven pushes as periodic) — see ARCHITECTURE.md §6 on
event-observation coroutines.
- Status: ⚪ ASSUMPTION — mechanism exists on Android (API 31+), not confirmed whether it actually fires for this device.
BroadcastReceiveronBluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED(API 31+) or the legacyandroid.bluetooth.device.action.BATTERY_LEVEL_CHANGEDintent — Android's own generic, profile-agnostic battery signal for a connected Bluetooth device, independent of which underlying mechanism (Options A–D below) the OS derived it from.- Advantage: costs nothing to implement — no scan permission, no RFCOMM connection, just a receiver registration. If it reliably fires for the Buds Pro 2, it can short-circuit needing to implement any of Options A–D directly.
- Caveat: whether the OS actually populates this for a Fast-Pair-based device like the Buds Pro 2 is unconfirmed — treat as a supplement to, never a replacement for, confirming at least one of Options A–D.
- Evidence: none yet — cheap to test early in a capture/implementation session precisely because confirming or ruling it out costs almost nothing.
-
Status: 🟢 FACT (mechanism, official spec) / 🟡 HYPOTHESIS (confirmed as used by the Buds Pro 2 specifically).
-
Payload structure (
[OFFICIAL-SPEC],developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification):Octet Field Encoding 0 Flags 0x00(reserved)1..s Account Key Data — s+1 Battery level length & type 0bLLLLTTTT(L = 3 values; T =0b0011show /0b0100hide)s+2 Left bud battery 0bSVVVVVVV(S = charging bit, V = 0–100%,0x7F= unknown)s+3 Right bud battery 0bSVVVVVVVs+4 Case battery 0bSVVVVVVV -
Trigger: sent when RFCOMM connects, or when a battery value changes — event-driven, not periodic polling (this corrects an earlier, unstated assumption of fixed-interval polling).
-
Shown ≥8 seconds when using the "show" type; auto-hidden after 20s or via an explicit "hide" type frame. Optional when a single bud is inserted/removed. Re-check flagged 2026-09-03: two direct re-fetches of the official
batterynotificationextension page found no sentence anywhere on it stating this 8s/20s timing — the byte-layout table above was re-confirmed exactly, but this specific timing sub-claim's citation could not be re-verified against the currently-live page text. Not retracted (the fetch tool's page-to-text conversion is not a guaranteed-complete read, and this detail may live on a different Fast Pair spec page, e.g. the base Message Stream spec, not checked this pass) — downgraded from unqualified[OFFICIAL-SPEC]to 🟡 HYPOTHESIS pending a maintainer or future session reading the actual page directly. Follow-up re-check, 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 2): the specifically-proposed alternate location (the base Message Stream extension spec page,.../specifications/extensions/messagestream) was fetched directly and also contains no sentence about notification display duration or auto-hide timing anywhere on it — it only describes the message format itself (Message Group/Code/Length/Data) and the two supported underlying transports (RFCOMM/L2CAP). Combined with thebatterynotificationpage's own already-confirmed absence, this closes both candidate locations named so far with a clean negative: neither of the two most plausible official spec pages currently states this 8s/20s timing anywhere. Still not retracted outright (a different, not-yet-identified page could still carry it, and the fetch tool's page-to-text conversion remains an imperfect read), but this claim now has no locatable official-spec citation at all — treat it as an internally-observed-only detail (its original source was never itself re-traced to a specific capture either) rather than a spec-backed one until a citation is found. -
Advantage: visible on a passive BLE scan — no active connection required, useful for the battery fallback logic in
ARCHITECTURE.md§4. -
Attempted 2026-08-21,
CAP-011— inconclusive, not[VERIFIED-LOCAL]. A dedicated capture found the Fast Pair Service (0xFE2C) BLE advertisement 🟢 FACT present (634 frames, 5 rotating addresses, strong/close-range RSSI), but the sampled service-data payloads do not structurally match this section's documented layout — no sampled byte equals the expected Length&Type marker (0x33show /0x34hide) at any offset. Two un-isolated confounds, not resolved either way: (1) the capture's own procedure deviated from a clean passive scan — an active classic RFCOMM connection was present throughout (the official app was left open on "Device details"), a confound this section's guidance doesn't yet cover; (2) the observed payloads structurally resemble a plain Account Key Filter/rotating-salt advertisement rather than the Battery Notification extension specifically. SeeCAP-011-FINDINGS.md§4 for the full byte-level check. Not force-fit, perCAPTURE_BLUETOOTH_HCI_SNOOP.md§5's explicit instruction — recorded as inconclusive rather than a false negative or false positive. A clean repeat (Bluetooth scanning only, no app open, no active connection) is still needed. -
Timing correlation, 🟡 HYPOTHESIS (
CAP-009, 2026-08-23, maintainer-approved for recording here perAGENTS.md§6) — a candidate trigger for on-screen updates when HFP/Option E are both closed. AfterCAP-009's case+USB reconnect (20:02:27.21), the on-screen Right-earbud percentage updates (20:02:28.68–29.01) with no HFP or DLCI-0x08 traffic present at all (both channels stay closed post-reconnect,CAP-009-FINDINGS.md§4) — so neither Option C nor Option E can explain it. Immediately after the reconnect, aLE Set Extended Scan Enablecommand (20:02:27.683) is followed by a stream ofLE Extended Advertising Reportevents (starting 20:02:27.742, continuing through and past the on-screen update), one of which (frame 29174) decodes to a Fast Pair Service (0xFE2C) advertisement — matching the foreground/reconnect-triggered, time-boxed scan patternAGENTS.md§7's bounded exception describes. Not confirmed: the advertisingBD_ADDR(17:6e:d1:d9:e3:dd, a random/rotating address) was not traced back to this specific Buds unit (no Account Key Filter decode attempted), so this is a timing correlation, not a payload-level confirmation — it neither proves nor disproves whether the payload itself matches this section's documented layout (still open per theCAP-011result above). Proposed verifying experiment: capture the BLE side at full detail and decode the Account Key Filter to confirm device attribution. -
Device-attribution advance, 🟡 HYPOTHESIS (
CAP-036-FINDINGS.md§12.4, 2026-09-04) — a different sub-question than the payload-layout one above, not a resolution of it. DLCI 0x04's "BLE address updated" field (Device Information Group0x03Code0x02, §0.1) gave this session's own value,44:d6:94:50:f0:4e— cross-checked directly against the same log's BLE advertising traffic, this address broadcasts a Fast Pair (0xFE2C) +0x1853advertisement 407 times, stable payload, throughout the ~7-minute session. This is the first time a live-observed advertising address has been tied back to a classic-channel field describing the Buds themselves (rather than only a timing correlation, as the entry below records) — but the payload's first byte is0x10, not the0x00"Flags" byte this section's table requires, structurally still an Account Key Filter-shaped frame, not a Battery Notification match — the payload-layout question above remains exactly as open asCAP-011left it. -
Evidence: official Fast Pair spec;
CAP-011-FINDINGS.md(2026-08-21, inconclusive payload-layout result);CAP-009-FINDINGS.md§4 (2026-08-23, timing-only correlation);CAP-036-FINDINGS.md§12.4 (2026-09-04, device-attribution advance).
- Status: 🟢 FACT (mechanism exists) / 🟡 HYPOTHESIS (candidate battery
message code identified,
CAP-009, 2026-08-23 — not yet a confirmed match). - The Message Stream (§2.1) has a documented "Device Information" message
group. Firmware version is confirmed at code
0x09(per the Find Hub Network extension doc), sent once per Message Stream establishment. Battery is expected to have its own code in the same group, following the same event-driven pattern; PROPOSAL, added 2026-08-28 (2026-08-28 project-wide audit findingEXT-01), pending maintainer review — the specific code value now has external spec support: Google's official Fast Pair Device Information extension spec (developers.google.com/nearby/fast-pair/specifications/extensions/deviceinformation, fetched 2026-08-28) documents Message Group0x03Code0x03= "Battery updated" — an exact match to the candidate below, independently derived fromCAP-009's own wire behavior. This strengthens, but does not by itself promote, the HYPOTHESIS below (AGENTS.md§6 still requires explicit maintainer sign-off, and ideally the fresh independent-session reproduction already proposed there, before promotion). - This is presumed to be the same underlying channel as the
hardware_status.protohypothesis in §3 — i.e. likely not a Buds-specific protobuf schema at all, but generic Fast Pair Message Stream traffic. - Candidate battery code, 🟡 HYPOTHESIS (
CAP-009-FINDINGS.md§7, maintainer-approved 2026-08-2x for recording here perAGENTS.md§6): DLCI0x04'sGroup 0x03 Code 0x03message (03 03 00 03 <b1> <b2> ff) is a strong structural and behavioral candidate. Across 208 occurrences in a 101-minute natural-discharge session,b2matched the Right earbud's percentage at all 7 of its transitions, andb1matched Left's percentage at both of its transitions while not charging — both fields updating within single-digit milliseconds of the already-establishedAT+BIEV(Option C) and DLCI-0x08 Option E pushes for the same underlying change. Once the Left earbud starts charging,b1stops behaving like a percentage (jumps to 221 and climbs ~1/sample instead of following L's known 93→100 charging curve) — read as a regime change (the field switches to reporting something else while charging, not yet identified) rather than a counter-example against the mapping while discharging. Not yet confirmed: whether thisGroup/Codenumbering is stable across sessions the way DLCI0x08's Option E numbering has proven to be, or is itself session-dynamic; the charging-state field switch is unexplained. Proposed verifying experiment: reproduce in an independent session and check theGroup 0x03 Code 0x03numbering holds. - Cross-channel timing synchronization extended to DLCI 0x02, 🟡 HYPOTHESIS (
CAP-036-FINDINGS.md§12.5, 2026-09-04): the near-lockstep pattern above (Option B/C/E firing within single-digit milliseconds of each other) is joined, in this session, by a periodic DLCI 0x02 (libmaestro) push firing within 7–18ms of Option E's DLCI 0x08 push at all 7 checked occurrences — suggesting a single shared underlying trigger across at least 4 mechanisms, not four independently-timed loops. The DLCI 0x02 push's own payload does not clearly match Option E's[value, flag, index]battery-triple shape, though (§6's new open question below) — this extends the timing observation only, not a claim that DLCI 0x02 carries confirmed battery content. - Evidence: official Fast Pair Message Stream / Find Hub Network extension
docs (mechanism).
CAP-009-FINDINGS.md§7,[VERIFIED-LOCAL]2026-08-23 (candidate code).
- Status: 🟢 FACT — confirmed active for this device (
CAP-001-FINDINGS.md§3). DLCI is session-local, not fixed — corrected 2026-09-07 (AUDIT_REPORT_2026-09-07.md§2.1). Originally documented as a fixed "DLCI 0x09" based onCAP-001alone; a cross-capture check found HFP's AT-command traffic (AT+CIND/AT+BIEV) landing on DLCI 0x0c in every one of four further independent sessions checked (CAP-004,CAP-007,CAP-033,CAP-042) —CAP-001's0x09was that session's own local value, never independently reconfirmed since, consistent with this project's already-established finding that RFCOMM DLCI numbers are session-local (CAP-001-FINDINGS.md§2), the same treatment already given to DLCI 0x02/0x04's own renumbering. Note (2026-08-23), onCAP-001's own session specifically: that session's HFP channel ("channel 4") was a different logical channel from DLCI 0x08's "channel 4" (the private envelope, §2.3) — both DLCIs share one RFCOMM multiplexer session (same ACL handle/L2CAP CID, independently confirmed viatshark), but each side of the connection independently assigned its own service to server channel 4, disambiguated by RFCOMM's direction bit (phone-initiated → DLCI 0x08, Buds-initiated → DLCI 0x09 in that session). This is standard, spec-correct RFCOMM behavior, not a numbering conflict — seeCAP-001-FINDINGS.md§2 for the full phone-init/Buds-init disambiguation; it explains whyCAP-001's own value differs from later sessions', not a contradiction of the session-local correction above. Two simultaneously-active HFP mechanisms observed:AT+BIEV=2,<0-100>(HF Indicator #2, Bluetooth-spec-assigned as Battery Level) and the olderAT+CIND?battchgindicator (0–5 scale) — these disagreed inCAP-001(battchg=3≈60% vs.AT+BIEV=2,100= 100%, at the same moment). BATT-006resolved 2026-08-23 (CAP-009,ADR-015, maintainer sign-off obtained):AT+CIND?'sbattchgis 🟢 FACT a single, non-repeating snapshot queried once at HFP Service Level Connection setup and never refreshed again — confirmed across a 101-minute, natural-discharge session (CAP-009-FINDINGS.md§1) where the peer's Right earbud genuinely changed by ~13 percentage points whilebattchgstayed silent the whole time.AT+BIEV=2does track real changes — but per-earbud, not as a single aggregate (this project's earlier working assumption — see the next bullet).- Update model — periodic, but not a fixed cadence.
AT+BIEV=2,100repeated on a roughly 6–7 second cadence immediately after SLC setup in bothCAP-001(frames 1236–2269, 08:51:14.106–08:51:52.148) andCAP-009— butCAP-009's much longer session (101 minutes vs.CAP-001's ~80 seconds) shows this tight spacing is a settling-burst behavior right after connection, not a sustained fixed cadence: gaps widen to a median of 20.5s and as much as ~14.6 minutes during idle stretches later in the same session (69 pushes over 86 minutes,CAP-009-FINDINGS.md§2, 🟡 HYPOTHESIS —CAP-009cannot distinguish "less frequent push-on-change" from "polling that only slows down while idle"). Revises the previous "regardless of whether the value changed... expect a steady stream" guidance below, which held forCAP-001's short, connection-adjacent window but does not describe extended idle behavior. An app relying on this mechanism should expect a push shortly after connecting, plus further pushes whenever the value changes — but not a steady drip throughout an idle session. - Per-earbud tracking, not a single aggregate (revises this project's earlier working
assumption that both HFP indicators report one aggregate value) — 🟢 FACT for
CAP-009, 🟡 HYPOTHESIS as a general rule.CAP-009's 101-minute session showsAT+BIEV=2tracking the Right earbud specifically: all 5 of its distinct values (93→92→90→89→88) match R's on-screen percentage and only R's, at every transition, while Left (96→95→94→100) and Case (72→71→68→75) never appear in theAT+BIEVsequence at all (CAP-009-FINDINGS.md§3). WhetherAT+BIEValways reports physical-Right, or whichever earbud is currently HFP-primary (R happened to be primary this session), is not yet distinguished — a session with a confirmed-L primary earbud would resolve this.AT+CIND?'sbattchgwas only ever observed once per session (see above), so whether it is aggregate or per-earbud remains untested either way. - Second data point, added 2026-08-26 (
CAP-008, Group V — proposal awaiting sign-off):CAP-008'sAT+BIEV=2tracked the Left earbud instead — its one value transition (98%→97%) matches only the on-screen Left percentage (cross-checked against 3 separate video frames), while Right (100%) and Case (43%) stayed constant throughout (CAP-008-FINDINGS.md§8). Two independent sessions now each track a different physical earbud (CAP-009: Right;CAP-008: Left) — evidence in favor of the "whichever earbud is HFP-primary" reading over a fixed-Right rule, still 🟡 HYPOTHESIS since neither session deliberately swapped the primary earbud mid-session to confirm the mechanism directly. - New open question, added 2026-08-26 (
CAP-008-FINDINGS.md§9):CAP-008observed a single unsolicited+CIEV: 6,<battchg>push (index 6 =BATTCHG) mid-session, not tied to any video-visible action — the first time this project has seen thebattchgindicator itself update outside the initial SLC-setup snapshot, even though theAT+CIND?query remains observed only once per session in every capture to date (consistent withADR-015, not a contradiction of it). 🔴 OPEN QUESTION: is this a general mechanism (battchg can push on change, just rarely) or a one-off artifact? A single occurrence is not enough to resolve this. - GMS/app-independence confirmed for Option C specifically, 🟢 FACT (retroactive maintainer
sign-off 2026-09-05,
DECISIONS.mdADR-023 — see that ADR's process note):AT+BIEV=2,<value>fires normally with Google Play Services disabled and the official app uninstalled (CAP-004) and, independently, with the app force-stopped for the entire session while GMS is untouched (CAP-033) — HFP battery reporting is OS/Bluetooth-stack-level, not GMS- or app-driven. ExtendsCAP-035-FINDINGS.md's existing GMS-independence result (which only checked DLCI 0x08/0x0a/0x06/0x12) to Option C. Confirmed a second OS, 🟢 FACT (retroactive maintainer sign-off 2026-09-05,DECISIONS.mdADR-023): the sameAT+BIEV=2,100behavior reproduces on Pixel 9a/GrapheneOS itself (CAP-035, GMS present butdumpsys-verified disabled, no official app, no nRF Connect) — Option C works on this project's actual target platform, not only on stock Android. - Cross-channel synchronization caveat, 🟡 HYPOTHESIS, new (
DESKRESEARCH_FINDINGS.md2026-09-04, round 2,CAP-027): the "near-lockstep" timing between Option C and Option E (documented above, extended to DLCI 0x02 inCAP-036-FINDINGS.md§12.5) is not universal — during an active-media-streaming session (AVRCP/A2DP traffic flowing throughout), DLCI 0x08's battery-triple push fired 3 times with no accompanying HFP push nearby (checked directly, zero DLCI 0x0c frames in a ±30s window around each). One session, correlation only — not confirmed as caused by active streaming specifically.
- Status: 🟡 HYPOTHESIS — the service's existence is now confirmed
(
CAP-017, 18:30 session — live GATT discovery via a fresh nRF Connect client,[VERIFIED-LOCAL]2026-08-16, seecaptures/CAP-017-2026-08-16_18-30-12_18-37-12-Group_W/CAP-017-FINDINGS.md§3, service #14 of 15 in the recovered profile). Not yet confirmed: the characteristic's actual value, whether it reports a single aggregate figure or per-component (L/R/Case), and whether it is actually read by either the official app or any capture to date — noRead/Notifytraffic against this service's handles has been observed in any capture (the handle range itself is still unresolved, same open question as the0x0c0X/0x0f2Xcluster in §2.3). Raised from 🔴 (service presence was previously unconfirmed) to 🟡 (presence confirmed, use/content still open) — not promoted further, since existence alone does not establish this is how the app or this project's own implementation should read battery. If pursued, the value itself would be read from the standard Battery Level characteristic,0x2A19(Bluetooth SIG-assigned, externally confirmed 2026-08-23) — the service UUID0x180Falone only identifies that the service exists, not which characteristic handle to read/subscribe to. The Battery Service/Battery Level pairing was independently reconfirmed a second time via nRF Connect's on-screen characteristic list (Properties: NOTIFY, READ,CAP-014-FINDINGS.md§3). Handle range resolved 2026-09-01 (CAP-034, maintainer sign-off obtained perAGENTS.md§6, 🟢 FACT): the Battery Service occupies handles0x0f30–0x0f33, with the Battery Level characteristic at value handle0x0f32(CCCD0x0f33) — resolved by the same discovery burst that closed the0x0c0X/0x0f2Xopen item below; see that item andCAP-034-FINDINGS.md§4.5 for the full command+hex evidence. This also explains the previously-unresolved0x0f32=0x64(100%) value first seen inCAP-017/CAP-014: it is an ordinary Battery Level reading, not a proprietary field — it only ever appeared via nRF Connect because the official app reads battery through the Fast Pair/HFP mechanisms above, not this BLE characteristic. Still not resolved: no capture to date has observed aRead/Notifyof this characteristic actually returning a value while the official app is in use, so whether the app (or this project's own future implementation) should read battery via this path remains open — status stays 🟡 for that narrower question.
Option E — DLCI 0x08 private envelope, per-earbud+case push (Group 0x0e Code 0x01 / Group 0x04 Code 0x03)
- Status: 🟢 FACT, promoted 2026-08-23 (maintainer sign-off obtained per
AGENTS.md§6; seeDECISIONS.mdADR-014) for the index=1/2/3 → Left/Right/Case mapping, based on the 3-independent-session cross-check below.CAP-011's own stale idx=3 reading and the burst's trigger stay open, unaffected by this promotion (see below). - Discovered while re-analyzing
CAP-011for an unrelated request (locating the exact moment a 1%-battery UI change occurred): a message on DLCI 0x08 (the still-🔴-unidentified private envelope, §2.3),Group 0x0e Code 0x01, decodes to a nested structure carrying 3 repeated entries[value, flag, index]plus 2 trailing scalars. - Cross-capture confirmation (2026-08-23): entries index=1/2/3 = Left/Right/Case, confirmed in 3
independent sessions spanning 12 days.
CAP-011(2026-08-21) alone: entries idx=1/idx=2 match the on-screen Left/Right percentages across 4 independent occurrences in one ~17.5-minute log, including a video-confirmed UI change (92/87, ~0.86s before the screen visibly updates) and two further off-camera recurrences where Right keeps declining (88→87→86) — not a coincidence. Extending the same check toCAP-001andCAP-002(2026-08-09, bothGroup 0x0e Code 0x01frames picked near an independently-recorded on-screen notification) found a clean 3-for-3 match, including idx=3=Case:CAP-001frame 1114 =[100,100,62]against on-screen "Left 100% Case 62% Right 100%";CAP-002frame 49024 =[100,100,57]against on-screen "Left 100% Case 57% Right 100%". An independent, single-value message on the same DLCI (Group 0x04 Code 0x03) cross-confirms the Right value at all 4 ofCAP-011's occurrences. - Not a new packet type — a new semantic decode of an already-known shape: this exact
field1="all"+ "3 varint-triple entries" structure was already documented, structurally only, inCAP-002-FINDINGS.md§2a (2026-08-12) — that pass did not attempt to interpret the numbers. This entry is the first to propose (and cross-check) what they mean. - One specific, unresolved anomaly — not glossed over: in
CAP-011alone, entry idx=3 (Case) reads a stale, non-matching value (92) against the on-screen Case reading (89%, constant throughout that session), unlikeCAP-001/CAP-002where it matched live.CAP-011's idx=3 also lacks theflagfield (field2) that every other confirmed-fresh entry carries (present and=1in all ofCAP-001/CAP-002's entries andCAP-011's idx=1/2) — plausibly, not confirmed, a "fresh/valid" bit, absent specifically when a value is stale. A plausible (not confirmed) explanation for the staleness itself:CAP-011's case sat open and empty for the whole session (a documented procedure deviation, §4.3's intro to this capture) — if the case's own reporting requires it closed/holding a bud to refresh, idx=3 could be carrying a last-known value predating this session's log. - Also unresolved: the burst's own trigger — it recurs at irregular intervals in
CAP-011(4:02, 2:56, 8:21 apart), and checking it against that session's near-continuous BLE reconnect churn found no correlation (the churn is far more frequent than this burst, ruling that out as the trigger). - Sent to: DLCI 0x08's private envelope (§2.3) — not DLCI 0x04's official Message Stream, not DLCI 0x02.
- Evidence:
CAP-011-FINDINGS.md§7 ([VERIFIED-LOCAL], 2026-08-23) — full command + raw hex for all 4 occurrences of both message types inCAP-011, the frame-by-frame video re-derivation of the exact UI-change timestamp, and theCAP-001/CAP-002cross-check frames (§7c). - Verified with experiment:
CAP-009(2026-08-23) delivered the "dedicated repeat" this entry called for — a fresh, purpose-built, 101-minute natural-discharge bracket, not a deskresearch correlation against a capture recorded for another purpose. It is now the 4th independent confirming session (afterCAP-001/CAP-002/CAP-011) and by far the longest/densest: 75 occurrences ofGroup 0x0e Code 0x01, Left and Right matching the on-screen value at every one of 14 transitions across the whole session (CAP-009-FINDINGS.md§6). 5th confirming session, 2026-09-04 (CAP-036-FINDINGS.md§12.2):100/100/100, matching on-screen exactly. Completeness addendum, not a new claim:Group 0x04 Code 0x03's own payload has a constant leading field (field2=5) preceding the Right-battery value in every session checked (CAP-001,CAP-002,CAP-011,CAP-036) — not previously called out at this byte-level precision. - Two addenda, maintainer-approved 2026-08-2x (
AGENTS.md§6) —CAP-009-FINDINGS.md§6:- A live charge cycle, observed for the first time on this mechanism. After the Left earbud
is placed in the case (~19:52:15), its
Group 0x0e Code 0x01value climbs monotonically 93→94→95→96→97→98→100 over the following ~6 minutes — the first confirming session to capture charging rather than only discharging. - The Case field's "unknown" placeholder has two distinct wire encodings, not one. For the
~78 minutes before any bud touches the case, every occurrence's Case entry is the shorter,
no-
flag-field form (08 ff 01 18 03, matchingCAP-011's stale-reading encoding). Right as a bud is about to make contact (3 occurrences, ~1.3s before a real value arrives), the Case entry briefly switches to a longer form that does carryflag=1despite still reporting the same "unknown" (255) value (08 ff 01 10 01 18 03). 🟡 HYPOTHESIS: this is the first direct evidence for the mechanismCAP-011-FINDINGS.md§7c could only speculate about (that capture never observed an empty→populated case transition) — the case's reading needs the case closed/holding a bud to be considered fresh; the no-flagform is a long-lived cached placeholder, while theflag=1form marks an actively-attempted-but-not-yet-successful fresh read, immediately before a real value lands. Not confirmed beyond this one transition. Complication, added 2026-09-04 (DESKRESEARCH_FINDINGS.mdentry,CAP-007): the same short, no-flagform (0a 04 08 2d 18 03) is observed carrying a plausible real value (0x2d=45, not the0xff/255 sentinel) throughout an entire ~6-minute session, with no case-contact transition anywhere in that window. This doesn't fit "no-flagform = unknown placeholder specifically" as a universal rule — 🔴 narrowed to an open question: either the short form can carry any value under some other, not-yet-identified condition (not exclusively the unknown/255 case), orCAP-007's own session differs fromCAP-009's in some relevant way not yet isolated (e.g.CAP-007's Group U condition — buds removed from case, case lid variously open/closed — versusCAP-009's natural-discharge idle session). - The Case field itself dips sharply right as charging begins, then declines further, more slowly. 71%→69% in ~21 seconds (far faster than this session's other observed discharge rates), holds ~6 minutes, then 69%→68%. 🟡 HYPOTHESIS: may reflect an instantaneous/voltage-based reading that dips under a sudden charging-current load rather than a smoothed charge-level percentage — the on-screen UI only shows 68% by ~20:00:01, later than either wire step, consistent with the UI smoothing or delaying this relative to the raw value. Not verified further; proposed as a concrete follow-up (a case-insertion bracket with tighter video sampling).
- A live charge cycle, observed for the first time on this mechanism. After the Left earbud
is placed in the case (~19:52:15), its
Implementation priority: 0 (cheap to rule in/out) → A → B → C → D (see
ARCHITECTURE.md §4; A–D's order reflects official-spec confidence and
connection-cost, not raw confidence alone since A requires no active
connection). Option E is not placed in this ordering, even though promoted to FACT
2026-08-23 (ADR-014) — unlike A–D it is not an officially-documented Fast Pair mechanism at all
(it's DLCI 0x08's still-unidentified private envelope), so it doesn't fit the "official-spec
confidence" ranking rationale above. Practically: its own trigger is unconfirmed and irregular
(observed gaps of several minutes), and one session (CAP-011) showed its Case field reading
stale — not yet a reliable enough update cadence to slot ahead of the already-periodic HFP
option (C), even though its per-earbud content is now FACT-confirmed.
-
Status: 🟢 FACT, for Left/Right specifically — promoted 2026-08-23 (maintainer sign-off obtained per
AGENTS.md§6; seeDECISIONS.mdADR-011). Tested 2026-08-21,CAP-025: video-correlated, cross-validated against ANC's already-🟢-FACT command on the same channel (§4.1) perCAPTURE_BLUETOOTH_HCI_SNOOP.md§4.1's Group K discipline. Case/"both simultaneously" are a separate, unresolved mechanism — see below; not covered by this promotion. -
Confirmed opcode/payload (
[Group:1][Code:1][Len:2BE][Value:1],PROTOCOL.md§2.1's Message Stream envelope):Group=0x04(Action),Code=0x01(Ring) — exact match to the spec's own worked example.Valuebyte:0x01= start ringing Right,0x02= start ringing Left,0x00= stop/mute (shared, not per-earbud). EverySentframe is retransmitted once (byte-identical), and answered by two ACK variants:0xFF 0x01 0x00 0x02 0x04 0x01and0xFF 0x01 0x00 0x03 0x04 0x01 0x00(one byte longer).Correction (2026-08-23): the first variant was previously described as a "byte-for-byte match to the spec's worked example." It is not — per §2.1's 2026-08-23 correction, the spec's real worked ACK example is
0xFF 0x01 0x00 0x04 0x04 0x01 0x01 0x3C(4 data bytes), which matches neither of the two variants actually observed on the wire (2 and 3 data bytes respectively). Both remain genuine, confirmed wire observations fromCAP-025— that part is unaffected — but neither can be described as matching the spec's own example. See §6's open item on the second variant's extra byte, reopened against the corrected 4-byte spec tail. -
Sent to: RFCOMM Message Stream channel, DLCI 0x04, per §2.1.
-
Expected response: confirmed — see the two ACK variants above.
-
Major structural finding — Case/"both" use a different mechanism entirely: the official app splits this feature across two screens. "Device details → Find device" has only "Ring Left"/"Ring Right" buttons (the mechanism above). Case and "both simultaneously" are only reachable via "Find device → Most recent location", which opens a Find Hub / Find My Device map view with its own "Play sound" flow — video-confirmed showing a "Connecting…" state and the on-screen copy "If you have another device linked with your Google Account, it may try to play sound on Pixel Buds Pro 2." Across a ~2.5-minute window with this flow active, zero
Group 0x04 Code 0x01frames appear on the wire — 🟢 FACT (checked explicitly, not assumed) that Case/"both" do not use the confirmed local Ring mechanism. 🟡 HYPOTHESIS: this instead routes through Google's Find My Device Network (account/cloud-mediated). Scope decision, 2026-09-13 (maintainer sign-off,DECISIONS.mdADR-027): this Zero-GMS limit is accepted, not further pursued — v1 ships with Left/Right ring only; Case/"both" is explicitly out of scope for this project's own app unless a future capture or protocol change finds a genuine local (non-GMS-mediated) mechanism, which no evidence to date suggests exists. SeePROJECT.md's non-goals for the corresponding scope statement. -
Evidence: official Fast Pair Message Stream spec worked example;
CAP-025-FINDINGS.md§3–§7 ([VERIFIED-LOCAL], 2026-08-21) — video-confirmed taps, 4 action/response pairs (2 starts, 2 stops), cross-validated against ANC's confirmed envelope. -
Verified with experiment:
CAP-025(2026-08-21) — seecaptures/CAP-025-2026-08-21_08-40-52_08-45-26-Group_K/CAP-025-FINDINGS.md. No further capture is planned for Case/"both" —DECISIONS.mdADR-027 closes this as a scope decision, not a remaining research gap.
General-purpose settings-write envelope, discovered 2026-08-21 (CAP-019–CAP-024) — shared
infrastructure underlying every subsection below. All the settings confirmed in this section ride
DLCI 0x02 (libmaestro's Pigweed pw_hdlc channel, §2.2a), inside a common two-level outer
wrapper: field 5 { field 4 { <setting-specific content> } } (standard protobuf wire-format tags,
field=tag>>3, wiretype=tag&7). Payload offsets 0–12, preceding this wrapper, are a constant,
cross-session-stable prefix (03 10 XX 1d ea 71 de 7e 25 1d 9a 8c 9e or a closely related
variant — confirmed identical across CAP-005, CAP-019, CAP-020, CAP-021 on different days),
consistent with CAP-005-FINDINGS.md §5a's "request/response correlation ID" reading, now
confirmed stable across sessions, not just within one. Each individual setting supplies its own
inner field number (and, for the per-earbud settings in §4.5.3, a further field 7{field1|2{...}}
sub-wrapper to select Left/Right). The outer envelope shape/pattern itself is 🟢 FACT, promoted
2026-08-23 (maintainer sign-off obtained, DECISIONS.md ADR-013): a general-purpose
libmaestro settings-apply envelope, evidenced by 9+ distinct settings across 6 independent
captures all sharing the identical outer nesting with no counter-example found — comparable
cross-capture replication to how DLCI 0x02's own HDLC framing was promoted in §2.2a. See
CAP-020-FINDINGS.md §5 for the envelope's first identification.
Scope of this promotion — narrower than it might look: only the outer wrapper's existence and
shape is FACT. Each subsection's specific field-number-to-setting mapping below remains its own,
separately-labeled 🟡 HYPOTHESIS (strong for the ones with 2+ independent samples — In-ear
detection, Volume EQ, press-and-hold's 4/4 combinations; weaker for the single-sample ones —
Conversation Detection, Multipoint, the top-level Touch/Head-gesture toggles, Case sounds) — none
of those individual mappings are promoted by this entry, per the maintainer's explicit 2026-08-23
decision to promote the envelope pattern only, not blanket-promote every field. FrameEncoder
implementation of the generic write path (building the two-level wrapper itself) is unblocked;
implementing what any specific field number means is not, per ARCHITECTURE.md §5's per-command
implementation gate.
- Feature confirmed present: toggle at Device details → Sound → Audio intelligence → Conversation detection. 🟢 FACT (UI presence).
- Opcode/payload — field-number/type identity 🟢 FACT, promoted 2026-09-03 (maintainer sign-off,
DECISIONS.mdADR-019):field5(len5){ field4(len3){ field22 = 0|1 } }(varint),field 22=libmaestro's ownqhrschema field 22, independently confirmed by APK static analysis: write sitehnz.java:29-49(a, logging"Set Speech Detection"), read sidefxb.javacase 22 — a self-describing app-code match confirming this is a real, distinctly-numberedqhrfield. Not promoted: whether the code's own internal name for this field, "Speech Detection," is the same feature as this section's "Conversation Detection" UI label — the two could describe the same feature seen from two angles (an internal/engineering name vs. the UI's own label), or something narrower/broader; this has not been reconciled, and the maintainer explicitly declined to promote that equivalence at this time. It remains 🟡 HYPOTHESIS: ON (1) confirmed video-correlated; OFF direction not captured this session. - Sent to: DLCI 0x02 (
libmaestro, §2.2a). - Expected response:
Rcvd-direction echo of the same field/prefix shape (no distinct ACK opcode observed). - Status: 🟢 FACT for the field-number/type identity (
qhrfield 22 = the app's own "Speech Detection"); 🟡 HYPOTHESIS for the equivalence to this UI's "Conversation Detection" label, and for the OFF-direction wire value. - Evidence:
CAP-019-FINDINGS.md§3 ([VERIFIED-LOCAL], 2026-08-21, frame 1808);REVERSE_ENGINEERING.md'sqhrentry;DECISIONS.mdADR-019. - Verified with experiment:
CAP-019(2026-08-21), single OFF→ON sample.
- Feature confirmed present: toggle at Device details → More settings → Multipoint. 🟢 FACT.
- Opcode/payload — full identity 🟢 FACT, promoted 2026-09-08 (maintainer sign-off, prompt
0002, implementingai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 2/Phase 4):field5(len4){ field4(len2){ field11 = 0|1 } },field 11=libmaestro's ownqhrschema field 11, independently confirmed by APK static analysis tracing forward from the UI: the unobfuscatedcom/google/android/apps/wearables/maestro/companion/ui/settings/multipoint/MultipointFragment.javabinds itskey_multipoint_main_toggleMainSwitchPreference'sOnCheckedChangeListener(cq(CompoundButton, boolean), line 71-75) tohiy.a(boolean)(hiy.java:31-38), which logs"Set device Multipoint as: %s"before callingfyc.e(z)→fyb(z,9)→fya.j(z)=fyo.j(boolean)(fyo.java:146-166) — the exact call site already identified as writingqhr.b = 11. A self-describing app-code match to this section's own "Multipoint" reading, at the ViewModel layer rather thanfxb.java's response handler (which has no distinct log for case 11). ON direction video-correlated on the wire; OFF direction not captured. - Additional finding: enabling Multipoint also triggers a Fast Pair Message Stream SASS
burst on DLCI 0x04, Group
0x07(Codes0x11/0x21/0x34/0x40/0x41/0x42, the last containing the ASCII string"in-use") — the first time this project has correlated SASS content (§2.3's table) with a specific triggering action. Directly confirmsCAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup C's own hint that Multipoint "may trigger an SDP/connection update, not just an RFCOMM command." This SASS correlation remains 🟡 HYPOTHESIS, unaffected by the field-11 promotion above. - Sent to: DLCI 0x02 (setting write) and DLCI 0x04 Group
0x07(SASS negotiation). - Status: 🟢 FACT for
field 11's field-number identity and semantic name ("Multipoint"). 🟡 HYPOTHESIS for the SASS correlation and for the OFF-direction wire value. - Evidence:
CAP-019-FINDINGS.md§4 ([VERIFIED-LOCAL], 2026-08-21, frame 2293 + frames 2296–2319);MultipointFragment.java/hiy.java/fyc.java/fyb.java/fyo.java(APK static analysis);ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 2. - Verified with experiment:
CAP-019(2026-08-21), single OFF→ON sample.
- Feature confirmed present: "Use touch controls" top-level toggle (Device details → Controls and gestures), plus per-earbud "Press and hold" assignment (Toggle ANC / Digital assistant) and an ANC-mode rotation checklist. 🟢 FACT.
- Top-level toggle opcode — 🟢 FACT, promoted 2026-08-30 (maintainer sign-off,
DECISIONS.mdADR-019):field5(len4){ field4(len2){ field4 = 1 } }(CAP-020,[VERIFIED-LOCAL]2026-08-21, frame 1741) — the innerfield 4islibmaestro's ownWriteSettingschema field 4 (Java classqhr), independently confirmed by a 2026-08-30 APK static-analysis pass: write sitefyo.java:124-144, read side logging"Log Gestures Enable setting"— a self-describing app-code match, not a naming inference, cross-validated against this wire evidence by the maintainer. 🔴 Not yet tested in the OFF direction, one session only. - Press-and-hold action selection opcode — 🟢 FACT, promoted 2026-08-30 (maintainer sign-off,
DECISIONS.mdADR-019) (HOLD-001–HOLD-004,CAP-021,[VERIFIED-LOCAL]2026-08-21):field5(len10){ field4(len8){ field7(len6){ field1|field2(len4){ field4(len2){ field1 = 5|6 } } } } }—field 1(inside thefield 7sub-wrapper) = Left,field 2= Right; the value sits one level deeper than originally notated (aqik→qhonested pair, not a bare varint directly underfield1/field2— corrected 2026-08-30, seeCAP-021-FINDINGS.md's addendum):5= Active noise control,6= Digital assistant. All 4 combinations (Left/Right × ANC/Assistant) exercised, each producing exactly the predicted field/value pair with no exceptions; frame 1903 (Rcvd echo) independently contains both the new and a second value in one message. The wire-level field 7 is independently confirmed aslibmaestro's ownqjuschema type by the same 2026-08-30 static pass: write sitefyo.java:300-374(t(gdx)), read side logging the self-describing"Log Gestures Customization for touch and hold setting, left: %s, right: %s"— naming this exact feature by name, not merely matching its shape. - ANC-mode rotation checklist opcode (
HOLD-005,CAP-021):field5(len12){ field4(len10){ field12(len8){ field1..4 = 0|1 } } }— four boolean flags, 🟡 HYPOTHESIS field order = on-screen top-to-bottom order (Noise cancellation / Off / Adaptive / Transparency). Not resolved: this envelope carries no Left/Right-distinguishing field, so which frames belong to which earbud's list is unconfirmed (§6). Field-number identity only — 🟢 FACT, promoted 2026-08-30 (DECISIONS.mdADR-019): wire field 12 is confirmed to belibmaestro's ownqhtschema type (a 4-boolean message, write sitehgj.java:216-331, read side logging"Log ANC gesture loop to Clearcut"). Explicitly not promoted: whether the app's own internal name for this field, "ANC gesture loop," is the same feature as this bullet's "ANC-mode rotation checklist" reading — the two could describe the same setting from two angles, or two different settings sharing a 4-boolean shape; the maintainer reviewed this specifically and declined to promote that equivalence — it remains 🟡 HYPOTHESIS. - Sent to: DLCI 0x02 for all three sub-features.
- Status: 🟢 FACT for the top-level toggle and press-and-hold-action opcodes (field numbers,
values, and — independently — the app's own schema/field identity for both); 🟢 FACT for the
rotation-checklist's field-number identity only; 🟡 HYPOTHESIS for the rotation-checklist's own
field-order/on-screen-mapping reading and for whether it's the same feature as
qht's "ANC gesture loop." - Evidence:
CAP-020-FINDINGS.md§3, §8 (2026-08-30 addendum);CAP-021-FINDINGS.md§3–§4, §8 (2026-08-30 addendum);REVERSE_ENGINEERING.md'sqhr/qjo/qju/qjg/qhtentries (2026-08-30 updates);DECISIONS.mdADR-019. - Verified with experiment:
CAP-020(top-level toggle),CAP-021(press-and-hold + checklist), both 2026-08-21.
- Feature confirmed present: "Use head gestures" toggle (Device details → Controls and gestures), gated behind the touch-controls screen; tapping it the first time also shows a one-time "Optimize head gestures" explainer dialog (client-side only, no separate wire event tied to its dismissal). 🟢 FACT.
- Opcode/payload:
field5(len5){ field4(len3){ field29 = 2 } }. 🟡 HYPOTHESIS —field 29= Head gestures; the wire write fires on the tap itself, ~1s before the explainer dialog even renders. - Sent to: DLCI 0x02.
- Status: 🟡 HYPOTHESIS.
- Evidence:
CAP-020-FINDINGS.md§4 ([VERIFIED-LOCAL], 2026-08-21, frame 1935). - Verified with experiment:
CAP-020(2026-08-21), single OFF→ON sample.
- Feature confirmed present: toggle at Device details → More settings → In-ear detection. 🟢 FACT.
- Opcode/payload — category-level identity 🟢 FACT, promoted 2026-09-08 (maintainer sign-off,
ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 1,DECISIONS.mdADR-019 Update):field5(len4){ field4(len2){ field2 = 0|1 } },field 2=libmaestro's ownqhrschema field 2, independently confirmed by APK static analysis: the same field-2 write site (fyo.java:169-188, methodl) is also reachable fromMaestroDeviceSettingsProviderServicecase2102(the system Settings app's own Bluetooth-device-details page), logged there under the internal category name"CATEGORY_OHD"(fjm.H(14)) — a self-describing internal name confirming this is a real, code-recognized On-Head/In-ear-detection-family boolean, at the same evidence bar already used for fields 22/27's category-level promotions (a self-describing internal name, not a verbatim UI-label match — "On-Head Detection" and this section's own "In-ear detection" UI label are closely related, not identical wording, so the specific label equivalence is not promoted, only that this is a real, distinctly-numberedqhrfield in the on-head/in-ear-detection family). Both ON and OFF directions video-confirmed on the wire. - Sent to: DLCI 0x02.
- Status: 🟢 FACT for
field 2's field-number/category-level identity. 🟡 HYPOTHESIS for the specific "In-ear detection" UI-label equivalence to the code's own "OHD" naming. - Evidence:
CAP-024-FINDINGS.md§3 ([VERIFIED-LOCAL], 2026-08-21, frames 1850/1912);REVERSE_ENGINEERING.md'sqhr/MaestroDeviceSettingsProviderServiceentries;DECISIONS.mdADR-019 Update. - Verified with experiment:
CAP-024(2026-08-21), both directions sampled.
- Feature confirmed present: "Mono audio" toggle at Device details → Sound. 🟢 FACT (UI presence).
- Opcode/payload — full identity 🟢 FACT, promoted 2026-09-03 (maintainer sign-off,
DECISIONS.mdADR-019):field5(len5){ field4(len3){ field19 = 0|1 } }(varint),field 19=libmaestro's ownqhrschema field 19, independently confirmed by APK static analysis: write sitefyo.java:278-298(s), read side logging"received mono setting value"(fxb.javacase 19) — a self-describing app-code match to this section's own "Mono audio" reading, not a naming inference. Both directions (ON/OFF) video-confirmed on the wire. - Sent to: DLCI 0x02.
- Status: 🟢 FACT for the field-number identity and semantic name ("Mono audio").
- Evidence:
CAP-022-FINDINGS.md§3 ([VERIFIED-LOCAL], 2026-08-21, frames 1621/1823);REVERSE_ENGINEERING.md'sqhrentry;DECISIONS.mdADR-019. - Verified with experiment:
CAP-022(2026-08-21), both directions sampled.
- Feature confirmed present: toggle at the bottom of Device details → Sound → Equalizer (not the top-level Sound page). 🟢 FACT.
- Opcode/payload — full identity 🟢 FACT, promoted 2026-09-08 (maintainer sign-off, prompt
0002, implementingai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 2/Phase 4):field5(len4){ field4(len2){ field15 = 0|1 } },field 15=libmaestro's ownqhrschema field 15, independently confirmed by APK static analysis via a literal Android preference-key string match:defpackage/hlv.java:2125-2134(a large R8-merged preference-click dispatcher,e(Preference)) has a case gated onstr.equals("volume_eq_switch")— the literal Settings-XML preference key for the "Volume EQ" toggle described above — logging"Set volume eq: %s"(hlv.java:2127) before callingfyc.h(z)→fyb(z,7)→fya.u(z)=fyo.u(boolean)(fyo.java:376-396) — the exact call site already identified as writingqhr.b = 15. This combines a self-describing log message and the literal preference-key string, a stronger evidence type than any otherqhrfield promoted to date. Both directions video-confirmed on the wire. - Sent to: DLCI 0x02.
- Status: 🟢 FACT for
field 15's field-number identity and semantic name ("Volume EQ"). - Evidence:
CAP-022-FINDINGS.md§4 ([VERIFIED-LOCAL], 2026-08-21, frames 1871/1895);hlv.java/fyc.java/fyb.java/fyo.java(APK static analysis);ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 2. - Verified with experiment:
CAP-022(2026-08-21), both directions sampled.
- Feature confirmed present: "Balance" slider at Device details → Sound.
TESTPLAN_BLUETOOTH_HCI_SNOOP.md§1 claims this is stored locally on the earbuds (persistent, works across devices) — not tested this batch (no disconnect/reconnect cycle captured after setting it). - Opcode/payload — 🟢 FACT, promoted 2026-09-03 (maintainer sign-off,
DECISIONS.mdADR-019):field5{ field4{ field17 = N } },field 17=libmaestro's ownqhrschema field 17, independently confirmed by APK static analysis: write sitefxf.java:82-133(case 16 of that dispatcher), read side logging"received last saved volume balance setting value"(fxb.javacase 17) — a self-describing app-code match to this section's own "Volume balance" reading, not a naming inference. Correction accompanying this promotion:qhr's own schema types field 17 asSINT32(REVERSE_ENGINEERING.mdline 856), so its wire values must be zigzag-decoded, not read as raw unsigned varints as this section previously did. The 7 samples from one continuous drag gesture (CAP-022frames 1922/1944/2019/2039/2056/2073/2099), correctly zigzag-decoded ((n>>1) ^ -(n&1)), are-100, -62, -25, 15, 75, 100, 5(previously misread as the raw unsigned varints199, 123, 49, 30, 150, 200, 10). - Sent to: DLCI 0x02.
- Scale/direction — 🟢 FACT, promoted 2026-09-13 (maintainer sign-off,
DECISIONS.mdADR-026): a dedicated isolated-extreme-position capture (CAP-046, Group AK) found the range clamps at exactly ±100, and — video-confirmed 3/3 times, zero counter-examples, independently re-derived at the wire level a second time (ai-sessions/0012_CROSSCHECK_RESULT_2026_09_12.mdFinding 125, exact byte-level match on all 8 samples) —field17 = +100corresponds to the Left extreme andfield17 = -100to the Right extreme, the opposite of this section's own prior implicit labeling ofCAP-022's first sample as "Left." Center-return samples read0/1/1/2(small drag-imprecision noise around a true zero). Still not confirmed, unaffected by this promotion: whether the field scales linearly between center and the extremes — this capture sampled only the two extremes and near-center, never an intermediate position (its own planned intermediate samples did not actually occur, seeCAP-046-FINDINGS.md§4). - Status: 🟢 FACT for the field-number identity and semantic name ("Volume balance"), and 🟢 FACT for the ±100 range and the Left/+100↔Right/-100 direction mapping. 🔴 still open: intermediate-position scaling, and persistence across a disconnect/reconnect.
- Evidence:
CAP-022-FINDINGS.md§5 ([VERIFIED-LOCAL], 2026-08-21, frames 1922–2099, raw hex and corrected zigzag decode backfilled 2026-09-03);CAP-046-FINDINGS.md§2 ([VERIFIED-LOCAL], 2026-09-12, isolated extreme-position samples, video-confirmed);REVERSE_ENGINEERING.md'sqhrentry;DECISIONS.mdADR-019/ADR-026. - Verified with experiment:
CAP-022(2026-08-21) — a single continuous drag.CAP-046(2026-09-12) — isolated discrete extreme-position samples, video-correlated (Group AK).
- Feature confirmed present: Device details → More settings → Case sounds, with two toggles labeled "Bud return" (app's own settings-list wording: "Earbuds replaced") and "Other alerts" ("Other notifications"). 🟢 FACT.
- Opcode/payload:
"Bud return"=field5(len5){ field4(len3){ field28 = 0|1 } };"Other alerts"=field5(len5){ field4(len3){ field27 = 0|1 } }.field 28("Bud return") — full identity 🟢 FACT, promoted 2026-09-03 (maintainer sign-off,DECISIONS.mdADR-019):libmaestro's ownqhrschema field 28, independently confirmed by APK static analysis: write sitefyo.java:58-78(d), read side logging"received bud return sound setting value"(fxb.javacase 28) — a self-describing app-code match to this section's own "Bud return"/"Earbuds replaced" reading, not a naming inference.field 27("Other alerts") — category-level identity only 🟢 FACT, promoted 2026-09-03 (maintainer sign-off,DECISIONS.mdADR-019):libmaestro's ownqhrschema field 27, independently confirmed by APK static analysis: write sitefyo.java:80-100(e), read side logging"received case earcon setting value"(fxb.javacase 27) — a self-describing app-code match confirming this is a real, code-confirmed case-sound-family boolean. Not promoted: the code's own log message is generic ("case earcon setting"), not specific to which case sound — it does not itself distinguish "Other alerts" from any other case-sound toggle, so the specific "Other alerts"/"Other notifications" label remains 🟡 HYPOTHESIS, based only on this section's own wire/video correlation (CASE-002), not reconciled with the generic code-side name.
- Sent to: DLCI 0x02. No case-specific vs. bud-specific channel/address distinction was found — both use the same shared DLCI 0x02 envelope as every bud-targeted setting.
- Status: 🟢 FACT for
field 28's full identity ("Bud return"). 🟢 FACT forfield 27's category-level identity (a real case-sound-family boolean); 🟡 HYPOTHESIS forfield 27's specific "Other alerts"/"Other notifications" label. The"Bud return"OFF sample (frame 1988) is not cleanly disambiguated between a genuine tap and a screen-open state sync — the ON sample and both"Other alerts"samples are unambiguous; this does not affect the field-identity promotions above. - Evidence:
CAP-024-FINDINGS.md§4–§5 ([VERIFIED-LOCAL], 2026-08-21, raw hex backfilled 2026-09-03);REVERSE_ENGINEERING.md'sqhrentry;DECISIONS.mdADR-019. - Verified with experiment:
CAP-024(2026-08-21), both toggles, both directions.
The following remain 🔴 unconfirmed at the protocol level — no capture has targeted them yet:
- Loud Noise Protection (firmware 4.467+; likely on-device DSP only, no wire-visible command expected — see §6).
- Adaptive Audio dynamic adjustment (firmware 4.467+; likely on-device DSP only — see §6).
Duplicate §4.1–§4.4's structure per command once each is captured and confirmed (opcode/payload structure, target channel, expected response, status, evidence, verifying experiment) — this is now done for every setting captured through
CAP-024; extend §4.5.1–§4.5.8 or add a new subsection as further settings are confirmed, rather than reverting to a bare bullet list.
Full step-by-step sequence not yet captured end-to-end for the RFCOMM
profile/Message-Stream/battery/command portions (steps 3–6 below remain ⚪
ASSUMPTION). The classic BR/EDR link establishment portion (steps 1–2's
link-layer mechanics), however, is now 🟢 FACT — see §5.1. Expected overall
shape, pending confirmation (advertising → scan/CDM pairing → RFCOMM connect →
Message Stream/libmaestro handshake → first battery notification → first
writes/reads):
1. Case opens / device becomes discoverable → BLE advertisement observed
(Fast Pair pairing notification + Battery Notification, §4.3 Option A)
2. User selects device via CompanionDeviceManager (first pairing) or OS
auto-reconnects to a bonded device
3. RFCOMM (BluetoothSocket) connection established
4. Fast Pair Message Stream and/or libmaestro handshake (exact sequence TBD)
5. Battery status received (event-driven, per §4.3) — "notification with
battery status on every reconnect" confirmed at the UI/behavior level
(TESTPLAN_BLUETOOTH_HCI_SNOOP.md §3)
6. App-driven reads (firmware version, serial numbers) and/or writes
(ANC mode, EQ) as triggered by the user
Three independent captures now agree on this state machine's shape —
CAP-001 (reconnect to an already-bonded device, stored link key), CAP-002
(fresh pairing via the official app, first attempt succeeds), CAP-003
(fresh pairing via nRF Connect, first attempt succeeds). Two converging paths
depending on whether a stored link key exists:
Fresh pairing (no stored key) — CAP-002/CAP-003:
Delete stored link key (if any) → Create Connection → Connect Complete
(status 0x00) → Link Key Request → Negative Reply (no prior bonding
material) → IO Capability Request/Reply/Response (SSP negotiation) →
Simple Pairing Complete → Link Key Notification (new key stored) →
Authentication Complete → Set Connection Encryption → Encryption Change
CAP-002(official app): frames 653–734, 17:05:26.717–33.721. The ~6.4s gap between IO Capability Response and Simple Pairing Complete matches the on-screen permission dialog being shown/confirmed, not a passkey step — no passkey digits are ever shown to the user; SSP here is a silent cryptographic exchange behind a permission-confirmation UI.CAP-003(nRF Connect): frames 1621, 1687–1756, 20:59:38.320–39.876. Same shape, but only a ~0.7s IO-Capability-to-Complete gap instead ofCAP-002's ~6.4s, since nRF Connect shows no confirmation dialog to wait on — this cross-check confirms the 6.4s gap above is UI dwell time, not part of the protocol's own timing.CAP-003also shows the BLE (LE) link established ~0.4s before the classic connection attempt, consistent with Fast Pair's BLE-first design (classic pairing triggered from the BLE side).CAP-013(2026-08-26, official app, following a phone-wide "Reset Bluetooth & Wi-Fi"): frames 117–270, 17:11:46.737–48.843. Same shape again, a ~1.2s IO-Capability-to-Complete gap. A fifth independent confirming instance of this path — seeCAP-013-FINDINGS.md§2/§7 for the full frame table (this capture's own purpose wasPAIR-004, not this path itself, which was already 🟢 FACT before this session).CAP-031(2026-08-27, official app, following a genuine narrow per-device "Forget"): frames 598–689, 06:07:15.111–16.451. Same shape again, a ~0.4s IO-Capability-to-Complete gap. A sixth independent confirming instance of this path — seeCAP-031-FINDINGS.md§2/§7 (again, this capture's own purpose wasPAIR-004's still-open primary question, not this path itself).CAP-032(2026-08-27, official app, following a genuine narrow per-device "Forget"): frames 1090–1153, 18:31:26.776–28.019. Same shape again, a ~0.6s IO-Capability-to-Complete gap. A seventh independent confirming instance of this path — seeCAP-032-FINDINGS.md§2/§7 (this capture's own purpose was, for the first time, actually resolvingPAIR-004's primary question — see §6's "Behavior" entry below).
Reconnect (stored key exists) — CAP-001:
Create Connection (may require multiple attempts — CAP-001 needed 3; attempt
1 failed with Page Timeout before the case was fully open on camera) →
Connect Complete (status 0x00) → Link Key Request → Link Key Request Reply
(stored key, no SSP negotiation) → Authentication Complete → Set Connection
Encryption → Encryption Change
CAP-001: frames 732–917, 08:51:01.981–12.208. No IO Capability/SSP exchange at all — the stored key from a prior pairing is reused directly. A BLE (LE) link to the same peer was independently already established earlier (frame 290, 08:50:36.27), before the case was even open on camera — seeCAP-001-FINDINGS.md§6 for the still-open question this raises about exactly when that BLE association was formed relative to the on-screen "Forget" tap (tracked as planned captureCAP-013).
Reconnect, Buds-initiated variant — 🟢 FACT, added 2026-08-18, maintainer sign-off obtained
2026-08-28 (DECISIONS.md ADR-016) (CAP-016-FINDINGS.md §1):
where CAP-001's reconnect is a phone-side Create Connection (needing 3 attempts), CAP-016
shows the Buds paging the phone instead — a single Rcvd Connect Request → Sent Accept Connection Request → Rcvd Connect Complete sequence, landing within 0.5s of the on-camera
earbud removal from the case (frames 1213–1217, 06:32:02.531–749). Same stored-key tail as
CAP-001's path (no IO Capability/SSP exchange visible in this window). Not yet reconciled with
why one session pages and the other is paged — plausibly which side (phone vs. Buds) detects
the case-open/bud-removal event first, not yet tested directly.
Common tail, both paths: Authentication Complete → Set Connection Encryption → Encryption Change, converging to the same encrypted classic
link regardless of which path reached it.
Third path — Cross-Transport Key Derivation (CTKD), gated on a pre-existing LE link — 🟡
HYPOTHESIS (strengthened 2026-08-26), PROPOSAL awaiting maintainer sign-off for promotion to 🟢
FACT. CAP-004-FINDINGS.md §2 first observed a third bonding path when a BLE (LE Secure
Connections) link to the Buds already existed before classic pairing began (nRF Connect,
CAP-004): Delete Stored Link Key → SMP Pairing Request (requesting Linkkey key
distribution) → Public Key/Confirm/Random → DHKey Check → classic Create Connection →
Link Key Request Reply (not Negative) — i.e. the classic link key is derived from the LE pairing
rather than negotiated via classic SSP. CAP-004-FINDINGS.md §10 explicitly withheld this from
promotion, since it rested on one capture with a specific confound: nRF Connect's early BLE
connection might itself be why CTKD occurred, not the GMS-disabled/no-app condition that
session was actually testing. CAP-012 (2026-08-26) directly tested this as a controlled
hypothesis test (CAP-012-FINDINGS.md §2/§10): repeating the same GMS-disabled/no-app
condition with no BLE tool at any point, and independently confirming zero BLE connection to the
Buds anywhere in that session's log, produced classic SSP instead — not CTKD. Combined with
CAP-002/CAP-003 (classic SSP, official app / nRF Connect but classic-only pairing path — no
early BLE link either), the pattern across all captures to date is a clean split: classic SSP in
every session with no pre-existing LE link to the Buds (CAP-002, CAP-003, CAP-012); CTKD in
every session that had one (CAP-004, and now CAP-014 — 2026-08-27, CAP-014-FINDINGS.md §5 — a
second, independently confirming CTKD instance: SMP Pairing Request with Linkkey distribution →
Public Key/Confirm/DHKey Check → classic Create Connection → Link Key Request Reply, again
initiated by a BLE tool, nRF Connect, connecting first). This is a direct causal isolation from a
purpose-built repeat, not merely a repeated negative — but per AGENTS.md §6, promoting "an LE
Secure Connections link already existing gates CTKD vs. classic SSP" into this section's own 🟢 FACT
connection-lifecycle diagram is left to the maintainer rather than done unilaterally here.
5.2 RFCOMM channel-opening sequence (step 3) — 🟡 HYPOTHESIS (strong), reviewed by the maintainer 2026-09-09, kept at HYPOTHESIS
Method: extracted every SABM (channel-open request) event, per DLCI, from the classic ACL
Connection Complete onward, across every already-on-disk capture confirmed untruncated with a
clean connection window (CAP-036, CAP-041 Windows A/B/C, CAP-037's first reconnect on two
different chandles) —
$ tshark -r <log> -Y "bthci_acl.chandle==<chandle> and btrfcomm.frame_type==0x2f" \
-T fields -e frame.number -e frame.time_relative -e btrfcomm.dlci -e frame.p2p_dir
Result, 6 independent fresh-reconnect instances across 3 capture sessions, zero counter-examples within this specific condition:
CAP-036 (frame 906 Connection Complete @ 43.609s):
0x00 @ 43.929s → 0x0c @ 43.946s → 0x04 @ 43.948s → 0x08 @ 44.017s → 0x0a @ 44.060s → 0x02 @ 45.216s
CAP-041 Window A (chandle 0x0002, frame 279 Connection Complete @ 17:11:59.210):
0x00 @ 2.479s → 0x0c @ 2.496s → 0x04 @ 2.693s → 0x0a @ 3.088s → 0x08 @ 3.152s → 0x02 @ 3.791s
CAP-041 Window B (chandle 0x0004, frame 1414 Connection Complete @ 17:13:22.494):
0x00 @ 85.706s → 0x0c @ 85.734s → 0x04 @ 85.957s → 0x0a @ 86.171s → 0x08 @ 86.216s → 0x02 @ 86.968s
CAP-041 Window C (chandle 0x0005, frame 2642 Connection Complete @ 17:15:39.443):
0x00 @ 222.764s → 0x0c @ 222.794s → 0x04 @ 222.877s → 0x08 @ 222.956s → 0x0a @ 222.999s → 0x02 @ 224.037s
CAP-037 (chandle 0x0004, frame 863 Connection Complete @ 29.113s):
0x00 @ 29.561s → 0x0c @ 29.571s → 0x04 @ 29.596s → 0x0a @ 29.653s → 0x08 @ 29.702s → 0x02 @ 31.009s
CAP-037 (chandle 0x0006, frame 3866 Connection Complete @ 81.696s):
0x00 @ 82.062s → 0x0c @ 82.093s → 0x04 @ 82.096s → 0x08 @ 82.144s → 0x0a @ 82.185s → 0x02 @ 83.360s
Every one of these 6 fresh-reconnect instances shows the same macro-order: the multiplexer
control channel (0x00) opens first, HFP (0x0c) opens second (within tens of milliseconds — HFP
is confirmed the fastest channel to establish, matching PROTOCOL.md §4.3 Option C's own
observation that HFP's SLC handshake starts almost immediately), the official Fast Pair Message
Stream (0x04) opens third, then the private envelope (0x08) and the silent channel (0x0a) open
in either relative order (this sub-ordering is the only thing that varies across these 6 samples —
CAP-036/Window C/CAP-037's second sample show 0x08 before 0x0a; Window A/B/CAP-037's first
sample show 0x0a before 0x08), and libmaestro's own channel (0x02) opens last in every
single instance, by a substantial, consistent margin (0.6–1.3 seconds after the fifth channel) —
never interleaved with or ahead of any other channel.
One genuine, documented exception — not suppressed, since honestly scoping a pattern is more
valuable than an artificially clean one. CAP-037's chandle 0x0006 shows a second full
6-channel reopening later in the same log (frame 8827, t≈253.9s), confirmed via HCI-level
Disconnection Complete events (bthci_evt.code==0x05, chandle 0x0006: only two disconnects
exist for this chandle, at t=96.9s and t=274.5s — this reopening at t≈253.9s sits inside one
continuous, unbroken ACL connection, not a fresh reconnect) — i.e. this is an instance of the
already-documented, still-unexplained RFCOMM multiplexer channel-bounce phenomenon
(CAP-016-FINDINGS.md §3/§9, PROTOCOL.md §6), not a new pairing/reconnect. In this instance, the
order is 0x00 → 0x0c → 0x04 → 0x02 → 0x0a → 0x08 — libmaestro's channel opens third, not
last. This is a real counter-example to the "always last" pattern, but it is scoped precisely: it
occurs specifically during a mid-connection channel-bounce, a condition already known to behave
differently from a fresh reconnect in other ways (per PROTOCOL.md §6's own open item on this
phenomenon's trigger). The "opens last" pattern's 6/6 record holds without exception specifically
for fresh RFCOMM-multiplexer establishment following a new/renewed classic ACL connection; it is
not claimed to hold for a mid-session channel-bounce.
Mechanism, consistent with — and explaining — several already-documented individual findings:
each channel appears to independently fire its own connect-time handshake/burst as soon as it
opens, not on a shared, single connect-time clock: DLCI 0x04's Get ANC state/Notify ANC state
pair fires within tens of milliseconds of DLCI 0x04's own channel opening (already 🟢 FACT,
DECISIONS.md ADR-021/ADR-022); DLCI 0x08's 7 unmapped zero-length Get-shaped frames and its own
device-info dump fire within tens of milliseconds of DLCI 0x08's own opening (CAP-036-FINDINGS.md
§5, PROTOCOL.md §6); and DLCI 0x02's own connect-time RPC burst (CAP-036/CAP-041,
ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 4 item 1) fires within tens of milliseconds
of DLCI 0x02's own opening. Since DLCI 0x02 is confirmed (above) to reliably open last of the five
data-carrying channels on a fresh reconnect, its own connect-time burst is, as a direct consequence,
also reliably the last burst to fire in the overall sequence — this is offered as the simplest
explanation consistent with all of the above, not independently verified beyond what the timing data
itself already shows.
Not established by this analysis: why libmaestro's channel specifically waits until last
(a deliberate app-level sequencing choice vs. simply being the slowest internal RFCOMM-socket-selection
path to resolve, per REVERSE_ENGINEERING.md's gbm/fzd entry) — genuinely open, not guessed at.
Status, reviewed by the maintainer 2026-09-09 (chat session that authored prompt
ai-sessions/0004_MAINTENANCE_PROMPT_2026_09_09.md) — kept at 🟡 HYPOTHESIS (strong), not
promoted. The maintainer reviewed this finding directly — 6 independent instances across 3 capture
sessions, zero counter-examples within the fresh-reconnect condition, one honestly-scoped exception
outside it (a mid-session channel-bounce, itself not yet explained) — and explicitly chose to leave
it at HYPOTHESIS (strong) rather than promote to 🟢 FACT at this time, pending either more
independent sessions or a dedicated purpose-built capture, given the one known exception isn't yet
understood. This is a reviewed, deliberate decision, not an oversight — a future session should not
re-propose this same finding without new evidence beyond what's already cited here.
Not covered by this section — still ⚪ ASSUMPTION: the Message Stream/libmaestro handshake's
own internal step-by-step content ordering beyond channel-open timing (step 4), and exactly when
the first battery notification/app command arrives relative to the classic link completing for a
user-initiated action rather than the automatic connect-time bursts characterized above (step 6).
Status: 🟢 FACT for classic BR/EDR link establishment (§5.1, seven
independent captures); 🟡 HYPOTHESIS (strong), maintainer-reviewed and kept at this tier 2026-09-09,
for the RFCOMM channel-opening macro-order (§5.2, step 3); ⚪ ASSUMPTION for the remaining Message-Stream-content/
user-triggered-command portions (steps 4/6); 🟢 FACT for step 5's specific behavioral outcome
(battery notification on reconnect), per TESTPLAN_BLUETOOTH_HCI_SNOOP.md §3.
Evidence: §5.1 above for the classic-link portion (CAP-001 frames
732–917, CAP-002 frames 653–734, CAP-003 frames 1621/1687–1756, CAP-016
frames 1213–1217, CAP-013 frames 117–270, CAP-031 frames 598–689, CAP-032
frames 1090–1153); §5.2 above for the channel-opening-order portion (CAP-036, CAP-041 Windows
A/B/C, CAP-037 ×2, full frame numbers/timestamps quoted in §5.2 itself); the Message-Stream-content
and user-triggered-command portions still need a full connection sequence captured/analyzed
end-to-end with that specific question in mind.
Consolidated from PROTOCOL_NOTES.md §7 — check items off with a date and a
one-line pointer to the evidence when resolved; add new items here rather than
leaving them buried in prose elsewhere.
- Narrowed 2026-08-12, not fully resolved: is
libmaestro's ANC/EQ control channel the same RFCOMM channel as the Fast Pair Message Stream (§2.1), using a custom/vendor Message Group ID — or a separate RFCOMM channel/PSM with its own proprietary envelope (§2.2)? Three coexisting sub-protocols are now evidenced (§2.3's table: DLCI 0x04 official Message Stream, DLCI 0x02 Pigweedpw_hdlc, DLCI 0x08 a third private envelope) — none yet confirmed to carry the actual ANC/EQ command.FrameDecoderstill cannot be implemented until one of these is confirmed as the command channel and aDECISIONS.mdADR records it (AGENTS.md§6). - If §2.2 is confirmed instead: exact magic byte value(s) and
length-field endianness — resolved 2026-08-12 for DLCI 0x02 specifically
(§2.2a): no magic byte — standard HDLC
0x7Eflag delimits frames; no explicit length field — flag-delimited instead. Does not apply to DLCI 0x08, which uses a genuine 2-byte big-endian length field of its own (§2.2a). - Checksum algorithm for §2.2, if confirmed — resolved 2026-08-12 for
DLCI 0x02 specifically (§2.2a): CRC-32 (IEEE 802.3/zlib polynomial,
little-endian), matching Pigweed's
pw_checksummodule exactly; verified at 640/640 (100%) sub-frames across three independent captures. DLCI 0x08 still has no confirmed checksum (unchanged, 🔴). - Added 2026-08-14: DLCI 0x08 checked directly against
pbpctrl's published Maestro transport description (HDLC U-frames) and found to not match (no0x7Eflag framing at all) — a checked negative result narrowing, not resolving, its identity. See §2.3's 2026-08-14 addendum. DLCI 0x02 remains the sole candidate that matchespbpctrl's stated mechanism. - Added 2026-08-15, resolved 2026-08-21 (
CAP-023): wire-baseline firmware version, distinct from the confirmed UI-baseline. §0.1 documentsrelease_5.203as the[VERIFIED-LOCAL]UI-baseline (official app screenshot, 2026-07-30); the requested capture that also records the app's firmware-display screen now exists (CAP-023, 2026-08-21) — the on-screen "Device firmware version" (Left/Right/Case, allrelease_5.203) matches DLCI 0x08's private envelope string byte-for-byte, same session. 🟢 FACT, promoted 2026-08-23 (maintainer sign-off obtained,DECISIONS.mdADR-012):"release_5.203"is what the app calls "the firmware version.""Revision 6"(DLCI 0x04's official field) and"cape2_sm"/"500m"–"500p"remain un-surfaced by the app's own UI — see §0.1's 2026-08-21 update for the full detail.
-
Real
.protofile names and full contents, extracted viapbtkagainst the official companion app APK (§3) — current names are placeholders. -
Channel/Msg ID values for: Set ANC mode, ANC state notification — resolved 2026-08-12, see §4.1. Fast Pair Message Stream Group
0x08("Hearable Controls" extension), Codes0x11/0x12/0x13(Get/Set/Notify), spec- and capture-confirmed. Set EQ band values remains open (§4.2) — no matching official extension found yet. -
Confirm the Ring / Find My Buds action against the spec's worked example (§4.4).
-
Whether
hardware_status.protoexists as a genuine Buds-specific schema, or whether battery is purely generic Fast Pair Message Stream traffic with no Buds-specific protobuf involved (§4.3 Option B). -
Exact Device Information message code for battery within the Message Stream group (§4.3 Option B) — firmware version is confirmed as code
0x09; battery's code is not yet confirmed. -
Protobuf/message mapping for all features listed in §4.5.
-
Added 2026-08-14: Groups
0x01/0x02/0x05/0x09on DLCI 0x08's private envelope are structurally confirmed as genuine, self-contained, standalone Message-Stream-shaped groups (not a reassembly artifact —CAP-004-FINDINGS.md§5a Task 3, checked across all four captures' full DLCI-0x08 byte streams with zero parse errors/leftover bytes). Their semantic meaning remains 🔴 open — no official Fast Pair extension page documents these group numbers under DLCI 0x08's private numbering (CAP-002-FINDINGS.md§2a Task 2). Partially advanced 2026-08-23 for a related, previously-unlisted group:Group 0x0e(also on DLCI 0x08, structurally known sinceCAP-002-FINDINGS.md§2a but not in this item's original group list) —Code 0x01's "3 varint-triple entries" shape now has a proposed semantic reading (per-earbud battery, §4.3 Option E), 🟡 HYPOTHESIS pending sign-off;Code 0x02's value was already known ("google-pixel-buds-pro-v1"). Groups0x01/0x02/0x05/0x09themselves remain fully open, unaffected by this. -
Added 2026-08-23, resolved same day (
CAP-011-FINDINGS.md§7c): DLCI 0x08'sGroup 0x0e Code 0x01battery message's 3rd entry (index=3) is Case — confirmed via a clean 3-for-3 match (Left/Right/Case) against on-screen values in 2 independent captures (CAP-001frame 1114,CAP-002frame 49024). Not fully closed: inCAP-011specifically, idx=3 reads a stale, non-matching value (92 vs. on-screen 89%) and lacks theflagfield every fresh entry carries elsewhere — a plausible but unconfirmed explanation (that session's case sat open/empty, possibly preventing a fresh case-battery read) is offered inPROTOCOL.md§4.3 Option E, not resolved further. -
Added 2026-08-23,
CAP-011-FINDINGS.md§7: what triggers DLCI 0x08'sGroup 0x0e/Group 0x04 Code 0x03battery-push burst? Recurs at irregular intervals (4:02, 2:56, 8:21 apart in one session) — checked against that session's own near-continuous BLE connect/disconnect churn and found no correlation (the churn is far more frequent than this burst). Not yet checked against any other candidate trigger. -
Added 2026-08-14: DLCI 0x08's own purpose/ownership as a whole channel is still 🔴 open. Ruled out as
libmaestrospecifically (§2.3's 2026-08-14 addendum — no HDLC framing, unlike the one concrete transport signaturepbpctrldocuments for Maestro). Leading remaining candidate: a lower-level Nearby/CDM companion-device negotiation, independent of both Fast Pair (DLCI 0x04) andlibmaestro(DLCI 0x02) — not confirmed. Update (2026-08-30,CAP-033-FINDINGS.md§3, audit finding): the channel now has an on-the-wire SDP service name, "GSND CONTROL" (UUIDf8d1fbe4-7966-4334-8024-ff96c9330e15, RFCOMM channel 4) — a new search lead (see §2.3's 2026-08-30 update), not a resolution; no match for "GSND" found in a first APK keyword pass. Still 🔴 open. -
Added 2026-08-14: EQ's opcode/channel is explicitly not assumed to sit alongside ANC's (DLCI 0x04 Group
0x08) — that assumption held only while ANC's own channel was unresolved. SeeCAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup T (new top-priority capture target) and §4.2 above. -
Added 2026-08-15: possible application-layer AES-128 encryption of DLCI 0x02's opaque "Sent" blocks. §2.2a describes the phone→Buds "Sent" blocks on this channel as "opaque ~16-byte blocks, unresolved content" — 16 bytes is exactly the AES block size, and Fast Pair's own Key-based Pairing procedure already uses AES-128 (ECB) elsewhere in the broader Fast Pair ecosystem, making an AES-128-encrypted application-layer payload here a plausible explanation for why these blocks have resisted structural decoding (encrypted data is indistinguishable from opaque/random bytes). Not established either way — this is distinct from Bluetooth link-layer encryption, which per
CAPTURE_BLUETOOTH_HCI_SNOOP.md§7's FAQ is unlikely to still be present at the HCI-snoop capture boundary; an application-layer scheme on top of the already-plaintext HCI capture is a different, live possibility. If confirmed, this would materially affectFrameDecoder's design for this channel (decryption step required before payload parsing) — seeARCHITECTURE.md§5. -
Added 2026-08-15, from
CAP-005-FINDINGS.md(Group T, EQ isolation) §6 — carried over per this session's task instructions. A properly isolated capture (EQP-002preset tap,EQS-004Bass slider drag, ≥10s gaps) found DLCI 0x02'sSentdirection is silent all session except for exactly three 45-byte payloads landing precisely at the two EQ actions (plus a laterSave-button tap) — the first EQ-attributable, structurally-decoded content on any channel (CAP-005-FINDINGS.md§5, all 🟡 HYPOTHESIS, not promoted to FACT). Distinct from the "~16-byte opaque Sent blocks" the AES-128 item above describes — this is a separate, larger (45-byte), now partially-structured payload from the connection-setup handshake blocks, not a resolution of that item either way. Specific open items this raised, not yet resolved: -Which of the 5 decodedResolved 2026-08-18, per a fresh, separatefloat32fields maps to which of the 5 UI sliders is inferred from only one slider (Bass) having been moved this session — needs a capture isolating a different single slider to confirm.CAP-015session (captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md§5) that drags all 5 sliders individually, 3 passes each — field 1↔Low bass, 2↔Bass, 3↔Mid, 4↔Treble, 5↔Upper treble (wire order reversed from the on-screen top-to-bottom order), matching this capture's own single-band inference exactly. Promoted to 🟢 FACT,PROTOCOL.md§4.2. - Whether the outer field number (16during preset-tap/drag,18at theSavetap) means "preview" vs. "save", or something else — needs a capture that drags a slider and deliberately never tapsSave. Still open as of 2026-08-18 — that capture's 15 field-18 frames all fire within ~2s of the preceding field-16 write with no video-visibleSave-button tap in between, revising (not confirming) the reading to "field 18 = slider release" — seePROTOCOL.md§4.2 and that capture's own §6. Code-level trace closes the call-site question but contradicts the "slider-release" revision, 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 4): field 18/fyd.dis reachable only through a dedicated, self-describing Save-button click handler ("On click save EQ button") — no slider-release code path to it exists anywhere in this app version's source. Genuinely unreconciled withCAP-015's own wire timing; seeREVERSE_ENGINEERING.md'sqjwentry for the full trace. - ~13 bytes of apparentcall_id/correlation data (payload offset 1–12, echoed back verbatim by the Buds) are present but undecoded. - Checked 2026-08-17 (deskresearch pass,DESKRESEARCH_FINDINGS.md): whether DLCI 0x02's field-16/18 pair is EQ-specific or a general-purposelibmaestro"apply/save" pair also used by ANC/other settings. CAP-005's own exact structural check (three independent nested-length self-consistency assertions) was re-run against every Sent-direction DLCI 0x02 payload inCAP-001,CAP-002,CAP-003,CAP-006,CAP-007, and the 11:42CAP-010session — includingCAP-006's four cleanly isolated single ANC taps, the best available test case for this exact question. Clean negative result: zero matches anywhere outsideCAP-005's own three already-known frames — no ANC tap, in any capture, produces a payload matching this envelope shape on DLCI 0x02, under any outer field number. 🟡 HYPOTHESIS (strengthened, not yet 🟢 FACT — still only one capture ever produced this shape at all): the field-16/18 envelope is EQ-specific, not a general-purpose settings-apply/save pair. SeeDESKRESEARCH_FINDINGS.md's 2026-08-17 entry for the full method and per-frame results. -
Added 2026-08-17, deskresearch pass (
DESKRESEARCH_FINDINGS.md): DLCI 0x02's HDLC Address field appears to be renegotiated per connection/reconnect, not a small fixed set of protocol-level constants asPROTOCOL.md§2.2a's "two multiplexed pw_rpc channels" framing implied. Two new address pairs —0x1e80/0x2680(Sent, phone→Buds) answered by0xe980(Rcvd, Buds→phone) — appear inCAP-005andCAP-007, always in a burst immediately following a connection/channel-reopen event (not the first connection handshake), and carry the same "device serial + firmware string, repeated 3×" content already documented on the0x0000→0xD180pair sinceCAP-001— including inCAP-007, which never used the official app at all (system Bluetooth Settings only), yet still produced this exact response, on the new address pair instead of the original one. 🟡 HYPOTHESIS: the Address field is a per-connection-negotiated pw_rpc client/channel handle rather than a fixed value, extending the already-established "RFCOMM server channel numbers are session-local, not profile-fixed" methodological note (CAP-001-FINDINGS.md§2) one layer deeper, into DLCI 0x02's own internal addressing. Not confirmed: why the request is apparently duplicated on two addresses at once (0x18vs.0x1aas an inner field-2 value, correlating 1:1 with which address carries it) — genuinely unresolved, not guessed at. SeeDESKRESEARCH_FINDINGS.md's 2026-08-17 entry. Cross-checked against Pigweed's own public documentation, 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 2) — no external confirmation either way, genuinely undocumented upstream. Direct fetches ofpigweed.dev/pw_hdlc/andpigweed.dev/pw_rpc/(plus its linked design page) found no published explanation of how the HDLC Address field or an RPC channel ID is assigned — thepw_hdlcpage's own code examples pass a literal, hardcoded address (123) with no discussion of whether it is static configuration or dynamically negotiated, and neitherpw_rpcpage documents channel/client addressing assignment at all. This neither confirms nor refutes the "per-connection-negotiated pw_rpc handle" HYPOTHESIS above — it establishes that Pigweed's own public docs simply do not cover this specific mechanism, so this project's own wire-observed behavior (the address changing per connection/reopen) remains the only evidence either way, perAGENTS.md§12's no-code-reuse rule (this check consulted only public documentation, notpbpctrl's or Pigweed's own source). -
Added 2026-08-18, from
CAP-010-FINDINGS.md§3 (11:42 session) — byte-level detail for two GATT handles already known to be part of the0x0c0XKey-based-Pairing-shaped cluster (§4.3 Option D context), not yet spec-identified. Resolved 2026-09-01, see theCAP-034update below. Originally 🟡 HYPOTHESIS, tracked as open — not promoted as resolved facts at the time: -0x0c0c:Notification, 40 bytes (frame 2020) — handle already known to be in the cluster (CAP-003-FINDINGS.md§4), payload length not previously characterized. -0x0c13/0x0c14:0x0c13carries 9-byte (Read), 10-byte (Write), and 32-byte (Notification) payloads;0x0c14carries 2-byte CCCD enable/disable writes. These lengths do not cleanly fit the 16-byte AES-block pattern seen on0x0c04/0x0c05/0x0c0a— possibly a structurally distinct characteristic from the Key-based-Pairing pair (a leading0x01byte precedes the payload on all three0x0c13values, not decoded further). Not independently confirmed against any spec. PROPOSAL, pending maintainer approval, added 2026-08-27 fromCAP-014-FINDINGS.md§4c — all of the above byte-length/leading-byte characterizations reproduce exactly a 3rd/4th time (0x0c0c41B notify,0x0c139B-Read/10B-Write/32B-Notify each with a leading0x01,0x0c142B CCCD) in an independent session 11 days later, on the same physical device — strengthens confidence these are stable characteristic shapes, not session artifacts, but did not, at the time, change their status: still 🟡 HYPOTHESIS, still not resolved to real UUIDs. The handle↔UUID mapping question itself remained 🔴 OPEN QUESTION after this 3rd Group-W attempt (CAP-010,CAP-017,CAP-014) —CAP-014confirmed its own wire log was not truncated (unlikeCAP-017), but found a different blocking cause: the session reused an already-bonded phone with a cached GATT client, so Android served this cluster from its cached database instead of re-declaring it live on the wire (only the GATT service itself, handles0x0001–0x0009, was genuinely re-discovered). Neither of Group W's own candidate cache-busting methods (pm clear com.android.bluetooth, or a phone that has never connected to this Buds unit before) had been tried in any of the 3 sessions to that point.**🟢 FACT, resolved 2026-09-01 (`CAP-034`, maintainer sign-off obtained per `AGENTS.md` §6) — the handle↔UUID mapping for this entire cluster, and the full 15-primary-service GATT profile, is now known.** `CAP-034` (4th Group W attempt) combined, for the first time, an unlimited HCI snoop snaplen with a genuine full-database GATT cache miss (`pm clear com.android.bluetooth` run on a Pixel 9a that had never before connected to this Buds unit, plus a fresh nRF Connect install) — the resulting 06:47:42.147–45.490 discovery burst (frames 3264–3469) is a complete, untruncated `Read By Group Type`/`Read By Type`/`Find Information` walk of the entire `0x0001`–`0xffff` handle space, decoded byte-for-byte and independently corroborated by nRF Connect's own on-screen UUID-name rendering (a second, client-side decoding path agreeing with the wire-hex decode on every field checked). See `captures/CAP-034-2026-09-01_06-46-31_06-52-45-Group_W/CAP-034-FINDINGS.md` §4 for the full command+hex evidence per handle. Resolved mapping: | Handle range | UUID | Service | |---|---|---| | `0x0c00`–`0x0c14` | `0xFE2C` | **Google Fast Pair Service** | | `0x0f20`–`0x0f2a` | `0x180A` | Device Information | | `0x0f30`–`0x0f33` | `0x180F` | Battery Service | | `0x0f37`–`0x0f3e` | `109b862f-50e3-45cc-8ea1-ac62de4846d1` | "Unknown Service" (name still unidentified — see below) | | `0x0c15`–`0x0c18` | `15190001-12f4-c226-88ed-2ac5579f2a85` | Accessory Non-Owner Service (out of scope, `DECISIONS.md` ADR-008) | Within the Fast Pair Service, every characteristic in the `0x0c0X` cluster this project has tracked by byte-shape alone since `CAP-002` now has a spec-verified name (live-checked against `developers.google.com/nearby/fast-pair`, not recalled from training data): | Value handle | CCCD | UUID | Name | Properties | |---|---|---|---|---| | `0x0c02` | — | `FE2C1233…` | Model ID | Read | | `0x0c04` | `0x0c05` | `FE2C1234…` | **Key-based Pairing** | Notify, Write | | `0x0c07` | `0x0c08` | `FE2C1235…` | Passkey | Notify, Write | | `0x0c0a` | — | `FE2C1236…` | Account Key | Write | | `0x0c0c` | `0x0c0d` | `FE2C1237…` | Additional Data | Notify, Write | | `0x0c0f` | — | `0x2A26` | Firmware Revision String (standard, 2nd copy) | Read | | `0x0c11` | — | `FE2C1239…` | Message Stream PSM Characteristic | Read | | `0x0c13` | `0x0c14` | `FE2C1238…` | **still unnamed** — checked against the base spec, the Message Stream extension, and the Personalized Name extension; not found on any of the three | Notify, Write, Read | This directly confirms, as an exact UUID/name match rather than only a byte-shape match, the FORM already hypothesized since `CAP-003-FINDINGS.md` §4 for the 80-byte-first-write/16-byte pattern on `0x0c04` (Key-based Pairing) and resolves `0x0f28`="1"/`0x0f2a`="Revision 6" (confirmed across `CAP-002`/`CAP-003`/`CAP-010`/`CAP-017`) as the Device Information Service's ordinary Serial Number String and Firmware Revision String — not a Fast-Pair-proprietary value. It also **corrects** a `CAP-017-FINDINGS.md` §6 hypothesis: "Unknown Service" (`109b862f-…`) is **not** the `0x0c0X` cluster's container — it occupies a separate handle range (`0x0f37`–`0x0f3e`) and its own purpose remains unidentified. **Not resolved by this capture:** `FE2C1238…`'s official name, and "Unknown Service"'s own purpose — both remain 🔴 OPEN QUESTION, tracked in `CAP-034-FINDINGS.md` §8. **Re-checked against the live spec, 2026-09-08 (`ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md` Phase 2) — clean negative, reconfirms rather than resolves.** A direct fetch of the official Fast Pair GATT characteristics page lists exactly the 5 spec-defined characteristics already in this table (Model ID `FE2C1233`, Key-based Pairing `FE2C1234`, Passkey `FE2C1235`, Account Key `FE2C1236`, Additional Data `FE2C1237`) and nothing named `FE2C1238` — the currently-live spec still does not document it, confirming `CAP-034-FINDINGS.md` §8's own check rather than finding anything new. A web search for the "Unknown Service" UUID (`109b862f-50e3-45cc-8ea1-ac62de4846d1`) returned no matches of any kind (not a Bluetooth SIG standard service, no vendor documentation found) — genuinely unidentified, not merely unchecked. The Bluetooth SIG's own assigned-numbers database is published only as a bulk PDF/YAML download, not a searchable web page — checking either UUID against it directly (rather than via search) is a possible further step, not completed this pass. -
Added 2026-08-21,
CAP-019–CAP-024: what do DLCI 0x02's confirmed inner field numbers (§4.5'sfield4=touch controls,field11=Multipoint,field15=Volume EQ,field17=Volume balance,field19=Mono audio,field22=Conversation Detection,field27/field28=Case sounds,field29=Head gestures, plus §4.5.3'sfield7{field1|2{field4=5|6}}for press-and-hold) actually represent — stable per-setting/per-field schema IDs from a real.protodefinition, or something else? No official spec or extracted schema confirms this; inferred purely from timing correlation across 9+ settings in 6 captures. Partially resolved 2026-08-30, maintainer sign-off (DECISIONS.mdADR-019): forfield 4,field 7, andfield 12specifically, the answer is now known — a 2026-08-30 APK static-analysis pass recoveredlibmaestro's realWriteSettingrequest schema (Java classqhr) field-for-field, and re-decoding existingCAP-020/CAP-021frames confirmed these 3 field numbers (plus, at wire-level only, field 29) match that recovered schema exactly, with two of them (7 and 12) additionally matching a self-describing log message in the app's own code. Still open: whether the remaining confirmed field numbers listed above (11, 15, 17, 19, 22, 27, 28) also correspond 1:1 to this same recoveredqhrschema's own field numbers — plausible given the pattern, but not individually checked against the recovered schema yet. SeePROTOCOL.md§2.2a's 2026-08-30 update andREVERSE_ENGINEERING.md'sqhrentry. Further resolved 2026-09-03, maintainer sign-off (DECISIONS.mdADR-019 Update):field 17,field 19,field 22,field 27, andfield 28are now individually checked against the recoveredqhrschema and confirmed to match it (§4.5.1, §4.5.5a, §4.5.7, §4.5.8) —field 17(full identity, plus a zigzag-decoding correction),field 19(full identity), andfield 28(full identity) promoted in full;field 22andfield 27promoted for field-number/type or category-level identity only, with their specific semantic-label equivalence left 🟡 HYPOTHESIS (see those sections for the reasoning).field 11(Multipoint) andfield 15(Volume EQ) resolved 2026-09-08 (maintainer sign-off, prompt0002, implementingai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 2/Phase 4): both traced end-to-end from a named UI fragment/preference key forward to theqhrwrite call site (rather than backward from a response-handler log message, the technique used for the fields above), each confirmed by a self-describing log message (field 15 additionally by the literal"volume_eq_switch"Android preference-key string) — see §4.5.2/§4.5.6. This closes the specific two fieldsTODO.md's "Targeted research follow-ups" section flagged as the remaining unchecked ones from ADR-019's original four. -
Added 2026-08-21: does DLCI 0x02's general-purpose
field5{field4{...}}settings-write envelope (§4.5's shared preamble) generalize to every remaininglibmaestrosetting, or only to the ones captured so far? Does thefield7{field1|field2{...}}Left/Right selector (§4.5.3) generalize to other per-earbud settings beyond press-and-hold? -
Added 2026-08-21,
CAP-021-FINDINGS.md§4: which ofHOLD-005's 16 ANC-mode-rotation checklist frames belong to Left's list vs. Right's — the envelope carries no earbud-distinguishing field for this specific write, unlikeHOLD-001–HOLD-004. -
Added 2026-08-21,
CAP-022-FINDINGS.md§5; resolved 2026-09-13, 🟢 FACT (maintainer sign-off,DECISIONS.mdADR-026):field 17's (Volume balance) numeric scale/range and which direction (L/R) increasing values represent — a dedicated isolated-extreme-position capture (CAP-046, Group AK) confirmed range ±100,+100=Left,-100=Right, 3/3 video-confirmed, zero counter-examples. Intermediate-position scaling remains untested, unaffected by this promotion — seePROTOCOL.md§4.5.7. -
Added 2026-08-21,
CAP-019-FINDINGS.md§4: what do Fast Pair SASS (DLCI 0x04 Group0x07) Codes0x11/0x21/0x40/0x42encode beyond their raw bytes? Is Code0x34(which also fires with no Multipoint action nearby) a periodic/keepalive SASS code unrelated to Multipoint specifically? -
Added 2026-08-21,
CAP-011-FINDINGS.md§4: why do the Fast Pair Service (0xFE2C) BLE advertisement payloads sampled inCAP-011not matchPROTOCOL.md§4.3 Option A's documented Battery Notification byte layout (no sampled byte equals the expected0x33/0x34Length&Type marker at any offset)? Do the 5 rotating BLE addresses observed in that capture all belong to the same physical Buds/Case unit (RSSI proximity is supporting, not conclusive, evidence)? -
Added 2026-08-21,
CAP-025-FINDINGS.md§7; reopened 2026-08-23 against a corrected spec citation (§2.1's 2026-08-23 correction); cross-checked further 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 2): what do the Ring action's two observed ACK variants (0xFF 0x01 0x00 0x02 0x04 0x01and0xFF 0x01 0x00 0x03 0x04 0x01 0x00) represent, now that neither matches the spec's actual worked example (0xFF 0x01 0x00 0x04 0x04 0x01 0x01 0x3C, 4 data bytes)? A direct re-fetch of the official acknowledgement spec page confirms its own literal text for this worked example: "if the Provider receives a message containing the ring action (0x04010002013C), it should acknowledge the action by sending back0xFF0100040401013C", where the trailing0x013Cis explicitly glossed as "the current state of the action message group and code, ring right and 60 seconds timeout" — i.e. the spec's own extra bytes are a documented 2-byte state (channel/side + a timeout in seconds), not an undifferentiated status/result code as this item previously phrased it. Checked against both observed variants: neither fits this 2-byte-state shape — the first variant carries zero extra bytes beyond the echoed group/code, and the second carries exactly one extra byte (0x00), not two. This sharpens, but does not resolve, the open question: it rules out "the observed variants are a truncated/reordered version of the spec's own 2-byte state" as an explanation (a 1-byte or 0-byte state cannot represent both a channel indicator and a timeout), leaving "these are a structurally different ACK shape than the spec's worked example, for a still-unexplained reason" as the leading, unconfirmed reading. The spec page documents a NAK format too (a leading reason byte before the echoed group/code, e.g.0xFF 0x02 0x00 0x04 0x01 0x04 0x01 0x00for "Device busy") — checked against both observed variants, neither fits this shape either (both start with the echoed group/code, not a reason byte). Not yet checked against a fresh capture, per this item's own original ask. -
Added 2026-08-21,
CAP-022-FINDINGS.md§8: does Volume balance (field 17) actually persist locally on the earbuds across a disconnect/reconnect, asTESTPLAN_BLUETOOTH_HCI_SNOOP.md§1'sAUDIO-003row claims from the app's own on-screen text? Not tested —CAP-022only captured the write itself, no reconnect cycle. -
Added 2026-08-21,
CAP-021-FINDINGS.md§4a: DLCI 0x0a (RFCOMM channel 5) — silent (channel-control frames only, zero payload) in every capture that has checked it before or since (CAP-001/CAP-002/CAP-005/CAP-006/CAP-007/CAP-016,CAP-011/CAP-019/CAP-020/CAP-022–CAP-025, and nowCAP-008) — carried a 1123-frame payload burst inCAP-021alone (frames 2093–4926, ~179–277s into that session's log, dominant frame size 215 bytes). Structurally protobuf-tag-shaped (0a d0 01= field 1, length 208) but not decoded further, and not attributable to any single Group G Test-ID's tap time. What triggers this, and why it appears in exactly one session out of fifteen checked, is unresolved.CAP-008(Group V, a real phone call — proposal awaiting sign-off) specifically tests and rules out one candidate: this is not the call's SCO/eSCO audio path —CAP-008-FINDINGS.md§5/§6 shows the actual audio connection is a separate HCI-level synchronous connection (its own connection handle) that never touches RFCOMM/L2CAP framing at all, while DLCI 0x0a stayed open-but-empty through two full calls in that same session. Refined characterization, added 2026-08-23 (external audit pass, independent re-analysis of all 1123 frames — offered as a research direction, not a conclusion): the burst is 100% Rcvd-direction (Buds→phone only; the phone never requests it) and arrives in ~5–6 discrete bursty waves separated by multi-second gaps (8.4s/28.5s/22.5s between waves, vs. 1–100ms within a wave) rather than as continuous streaming; the payload body is a long run of a repeating 3-byte pattern (6d b6 db) with a different repeating pattern (7e ee ed) near each frame's tail and ~20 bytes of higher-entropy data before that. This timing/entropy profile is more consistent with a segmented bulk-data or capability/diagnostic dump than continuous real-time sensor/audio telemetry, but this is weaker evidence than a spec match or cross-capture replication and stays 🔴 OPEN QUESTION — explicitly not promoted to a firmer hypothesis, consistent with this project's 2026-08-22 decision to decline a prior, less-supported "IMU/telemetry" guess about this same burst. A capture bracketing whatever background condition preceded ~08:02:29 in that session (app backgrounded? scheduled sync? battery/charge-state change?) would be needed to attribute a trigger. Update, 2026-09-15 (CAP-047, Group AL, purpose-built hypothesis test, Trigger candidate 3 only — charge-state change) — clean, complete negative. Zero DLCI 0x0a-role payload frames across six independently bracketed dock/undock transitions (two full recordings, three separate classic connections, each with its own session-local silent-channel DLCI —0x0a/0x0b— identified by content signature), checked across each in-scope log's entire duration, not just around the bracketed moments. This includes the burst's own channel undergoing RFCOMM multiplexer channel-bounces (SABM→UAreopens) adjacent to a dock-state change — even these carry no payload. Raises the "1 of N sessions checked" denominator to at least twenty. Trigger candidate 3 is ruled out as an explanation forCAP-021's burst; Trigger candidates 1 (app backgrounded/foregrounded) and 2 (a scheduled idle sync window) remain untested. SeeCAP-047-FINDINGS.md§3 for the full command+hex evidence. -
Added 2026-08-18,
CAP-016-FINDINGS.md§11; sharpened 2026-09-12,CAP-018-FINDINGS.md§3–§4 (PROPOSAL, awaiting maintainer review): a 73-frameHandle Value Notificationburst on BLE ATT handle0x0044(connection handle0x0002), confined to a ~29s window right after the BLE link forms and before the classic link exists; 23 of the 73 contain a recurring0xfea9byte-pair marker. Not decoded — payloads don't obviously match any already-documented envelope shape, and the handle's own UUID was not resolved this session.CAP-018(Group Y,GATT-002) isolated the burst with the buds/case confirmed untouched on video for the session's full 152.16s duration — the burst reappears (23 frames, matchingCAP-016's own count) — but this session's only LE connection (the one carrying the burst) has a GATT profile (a standard Heart Rate service present, no Google Fast Pair Service, no0x0c0X/0x0f2Xhandle cluster) that this project's own evidence attributes to an unrelated nearby device, not the Buds; the Buds' own classic connection carries zero ATT traffic in that session. This does not resolve the question either way forCAP-016's original burst (not re-examined byCAP-018) — it sharpens the open question into a more basic one: is this burst ever genuinely Buds-originated, or has every occurrence to date been an unrelated background device coincidentally connecting near the same time as a bud/case action? Not force-fit into either reading, perAGENTS.md§13.6. -
Added 2026-08-18,
CAP-016-FINDINGS.md§10: Bluetooth HID (PSM0x0011) Feature Report Id0x01's response (frame 1983) is only 3 bytes (a3 00 00) — too short to carry any content comparable to Report Id0x02's decodedAndroidHeadTrackerstring (same section, 🟡 HYPOTHESIS, not yet promoted here); reported as short/near-empty, not decoded further, no content guessed. -
Added 2026-09-04,
CAP-036-FINDINGS.md§5: DLCI 0x08's connect-time burst contains severalSentframes matching the identical[Group:1][Code:1][Len(2BE)=0000]zero-length shape as DLCI 0x04's confirmed "Get" pattern (§4.1):05 0c 00 00,04 02 00 00,04 04 00 00,04 11 00 00,04 13 00 00,04 15 00 00, and0e 04 00 00. None of these Group/Code pairs is mapped to any known setting — genuinely open, not claimed as a settings query given DLCI 0x08's semantics remain largely unresolved (see theGroup 0x01/0x02/0x05/ 0x09item above). -
Added 2026-09-04,
CAP-036-FINDINGS.md§4: a dense, RPC-shaped burst occurs on DLCI 0x02 immediately after channel establishment (CAP-036frames 1404–1591, ~3.1s), containing three ASCII"release_5.203"firmware-version strings and many small request/response pairs sharing a partial match to §4.5's documented correlation-ID prefix (03 10 XX 1d ea 71 de 7e 25...). Not decoded further — whether it carries any settings-state read-back vialibmaestro's own channel (as opposed to the DLCI 0x04 "Get ANC state" mechanism confirmed the same session, §4.1) is unresolved. -
Added 2026-09-04,
CAP-036-FINDINGS.md§3; corrected same day byDESKRESEARCH_FINDINGS.md's two-round cross-check across 10 capture files:CAP-036's "Notify ANC state" frame (08 13 00 04 01 e8 00 20, Settable=0x00) is one of 5 samples showingSettable=0x00, against 12 samples showingSettable=0xe8, across everyGet/Notifypair found in this project's logs to date (CAP-006,CAP-010,CAP-016,CAP-019–CAP-025,CAP-036). A first pass over 2 sessions (CAP-016,CAP-036) mis-read this as a connect-time pattern — corrected once 8 more sessions were checked: 12 of the 17 samples are ALSO taken at a DLCI-0x04-open/reopen moment but show0xe8, not0x00— so "fires right after the channel opens" does not, on its own, predict which value appears. Sharper reading, still 🟡 HYPOTHESIS: every0x00sample sits at a session (or moment within one) where the Buds are plausibly in or near the case —CAP-016's own frame 1521 ("both buds still docked"),CAP-036(buds sitting in the open case the entire session, never worn),CAP-006's last sample (end of an ANC-tap test session), both ofCAP-010's samples (a fresh-pairing repeat, buds just taken from the case). Every0xe8sample sits in a session where the Buds are actively worn/in use throughout (CAP-019–CAP-025,CAP-006's first sample). Not maintainer-reviewed for promotion — recorded as the current best reading, superseding the narrower "connect-time" framing this item originally carried. -
Added 2026-09-04,
CAP-036-FINDINGS.md§12.6 (bonus battery/firmware analysis): DLCI 0x02's periodic push (§4.3 Option E's timing-correlation entry) decodes, HDLC-unescaped and CRC-32-verified, to a repeated triple pattern (0a 04 08 64 10 01,12 04 08 64 10 01,1a 04 08 64 10 02) that resembles but does not field-for-field match Option E's confirmed[value, flag, index]battery-triple shape (only 2 fields per entry here, not 3, and the trailing numbers are01, 01, 02rather than a clean1, 2, 3index). Not decoded further perAGENTS.md§13.6's zero-creativity rule — genuinely open whetherlibmaestro's own channel carries a differently-shaped battery-adjacent message here, or something unrelated that happens to repeat the value100. -
Added 2026-09-06,
CAP-040-FINDINGS.md§3 (Group AG,PRIV-001): the 7 DLCI 0x08 unmapped zero-length[Group][Code][00 00]-shapedSentframes (05 0c,04 02,04 04,04 11,04 13,04 15,0e 04, first flaggedCAP-036-FINDINGS.md§5) each fire exactly once in a ~30-minute session, bundled together in a single frame immediately after DLCI 0x08's one-and-only channel open — because DLCI 0x08 never reopens for the rest of that session (see the next item), none can be correlated against a bracketed value (N=1 for all 7). Still fully unattributed; needs a re-run with a trigger that genuinely reopens DLCI 0x08 (the OS Bluetooth toggle, perCAP-037's own procedure, or physical case/bud cycling). -
Added 2026-09-06,
CAP-040-FINDINGS.md§1: the official app's own in-app "Connect"/ "Disconnect" buttons, tapped ~15 times across a 27-minute stretch, produced zero wire-visible signal — no ACL disconnect/reconnect, no DLCI channel bounce of any kind. The classic link and DLCI 0x08 stayed open continuously throughout. Whatever these buttons actually do (if anything, beyond a local/optimistic UI-state toggle) is not visible at the RFCOMM/HCI level in this session — genuinely open whether this holds under any condition. -
Added 2026-09-06,
CAP-040-FINDINGS.md§4: DLCI 0x08'sGroup 0x0e Code 0x01Left-earbud battery field (DECISIONS.mdADR-014) reads the documented0xff/255 "unknown" sentinel for exactly one occurrence, landing precisely inside a video-confirmed physical-docking event, before settling to100on the very next occurrence 1.27s later. 🟡 HYPOTHESIS, single occurrence: the sentinel may reflect a momentary sensor/contact-read gap at the instant of case contact — not reconciled with any documented mechanism. -
Correction, 2026-09-06: an earlier version of this entry described
CAP-038-FINDINGS.md§3's finding as "DLCI 0x02 and DLCI 0x04 never open at all" on one reconnect, framed as an unexplained gap. That overstated it —CAP-038-FINDINGS.md§3 itself already identifies the cause: on that one reconnect (immediately following physical removal of both earbuds from the case), the official Fast Pair Message Stream andlibmaestro's own channel opened normally, just under different session-local DLCI numbers (0x05and0x03respectively) instead of the more usual0x04/0x02— consistent withCAP-001-FINDINGS.md§2's already-established finding that RFCOMM channel/DLCI numbers are session-local, not fixed (DECISIONS.mdADR-018). Not a new open question; the Get/Notify pair on DLCI0x05fired normally per ADR-022. The genuinely open item from that same reconnect is a different one: the resulting "Notify ANC state" frame readSettable-toggles=0x00(docked) immediately after the Buds were reported physically removed from the case — a real tension withDECISIONS.mdADR-024's dock-state reading, not yet reconciled (seeCAP-038-FINDINGS.md§3's own discussion). -
Added 2026-09-06,
CAP-038-FINDINGS.md§5: two DLCI 0x04 "Notify ANC state" frames occur with no preceding08 11Get and no08 12Set frame found anywhere in that session's log. 🟡 HYPOTHESIS: plausibly a hardware press-and-hold gesture accidentally triggered while handling the Buds (matchesCAP-027-FINDINGS.md§4's established Buds-initiated Notify-without-Set mechanism forTOUCH-007), but not video-confirmed (camera was on the phone screen, not the buds/ears) — could equally be some other still-unidentified trigger. Second candidate added 2026-09-07 (AUDIT_REPORT_2026-09-07.md§1.1), code-evidenced but not wire-tested:REVERSE_ENGINEERING.md'sqhrentry documentsqhrfield 13 (ANC_STATE-typed, alongside the already-FACT field 12/qht) as reachable fromfye.java'sa(qhs)method withANC_STATE_ACTIVE/ANC_STATE_AWARE— a binary Active↔Aware ANC-state write throughlibmaestro's own DLCI 0x02WriteSettingchannel, distinct from DLCI 0x04's official Message Stream. If something (a gesture or otherwise) writes ANC state via this DLCI-0x02 path and the resulting hardware-side change is independently reported out via DLCI 0x04's Notify, that would produce exactly this pattern without requiring a press-and-hold explanation. Not proposed as a resolution — a second, un-preferred candidate to check a future session against (does aqhrfield-13 write on DLCI 0x02 immediately precede an unexplained DLCI-0x04 Notify?). -
Added 2026-09-06,
CAP-041-FINDINGS.md§4 (Group AH,OBS-007): a recurring 2-field sub-message inside DLCI 0x02's connect-time burst (first flagged, structurally, inCAP-036-FINDINGS.md§12.6) holds a constant value across an entire session that happens to match the on-screen Case battery percentage (0x4f=79, matching "Case: 79%" throughout). Because the value never changed during that session, this is consistent with, but does not confirm, the field tracking Case battery — a second, still-inconclusive data point (the first beingCAP-036's own all-100% session, equally unable to rule out coincidence). A capture bracketing an actual mid-session Case-percentage change against this specific field is the natural next step. -
Added 2026-09-06,
CAP-041-FINDINGS.md§2/§6, closed at the content level 2026-09-08 (CAP-041-FINDINGS.md§8,ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 4 item 1). DLCI 0x02's connect-time RPC burst's length/shape signature (frame count, dominant run of 26-byte frames) is essentially invariant across 3 genuinely different, independently-logged non-default settings states in one session, and matchesCAP-036's own all-defaults baseline — a scoped negative result forARCHITECTURE.md§3.1's "doeslibmaestrocarry a settings-state read-back" question. The follow-up content-level diff this item asked for is now done: HDLC-unescaped and CRC-32-verified every sub-frame across all 4 sessions (184 sub-frames, zero CRC failures) — the burst's dominant tail run (30 of ~46 sub-frames) is byte-for-byte identical across all 4 sessions; the header portion contains the exact same set of sub-frame values in every session, differing only in transmission order, with exactly one exception per session: a single sub-frame that varies session-to-session in a way plausibly consistent with a per-session timestamp/correlation nonce (fux.java's already-documentedSetWallclockRPC is a candidate, not confirmed), not a settings value.OBS-007is now closed as a clean negative at the content level, not merely the length level — no evidence found that this burst carries any settings-state read-back. -
Added 2026-09-07 (
AUDIT_REPORT_2026-09-07.mdQ4, cross-checked inEXTERNAL_REVIEW_VALIDATION_2026-09-07.md), 🟡 HYPOTHESIS, not FACT — which specificmaestro_pw.*service(s) fire inside DLCI 0x02's connect-time burst (above).fux.java's service catalog (case 8,fux.java:55-66) confirmsGetSoftwareInfo/GetHardwareInfo/SubscribeRuntimeInfo/SetWallclock/WriteSetting/ReadSetting/SubscribeToSettingsChanges/SubscribeToOobeActionsare real, callablemaestro_pw.Maestromethods, andfxm.java:55confirms one concrete call site literally building aGetSoftwareInfoinvocation ("maestro_pw.Maestro","GetSoftwareInfo"), invoked 4× fromfxm.i()(fxm.java:78-117,"Start fetch SoftwareInfo"), itself called from a"Change primary route to %d"lifecycle hook (frb.java:83) that is plausibly, but not confirmedly, connect-adjacent. This is a static service catalog plus one plausible trigger — it does not establish that these are the specific RPCs insideCAP-036/CAP-041's characterized ~44-46-frame burst, as opposed to available-but-unfired methods or a different trigger entirely; no byte-level or frame-count correlation has been done. An earlier external review (ANTIGRAVITY_AUDIT_REPORT_2026-09-07.md) cited the same two files and labeled this connection🟢 FACT, recommending an ADR — that promotion was rejected on cross-validation (EXTERNAL_REVIEW_VALIDATION_2026-09-07.md) as an evidentiary overclaim, independent of a separate governance problem with how it was submitted (an AI report self-assigning FACT status, whichAGENTS.md§6/DECISIONS.mdADR-017 do not permit); recorded here at the HYPOTHESIS level the evidence actually supports. Next step unchanged fromCAP-041-FINDINGS.md§6's own recommendation: a byte-for-byte content diff of the burst across differing settings states. Re-attempted 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 3) —gjv.p()'s own caller (the second, more plausibly connect-lifecycle-shaped trigger candidateREVERSE_ENGINEERING.md'sfrb/fuh/glk/gjventry already named) was still not found, same conclusion as the prior session. Confirmedgizis an abstract class withgjvas its sole subclass, and confirmed none of the three classes holding agiz-typed field call.p()anywhere in their own source — a generic-token search (.u =/.p(), both too common to be productive) was judged not worth pursuing further. Found adjacent context instead:gaa.javais theGetSoftwareInforesponse-side handler (unpacksqjb, the already-decoded response type), calling a differentgizmethod (n(gdm)) once a response lands — confirming which class sits on the response side of this round-trip, but not resolving the request-side trigger. Given this static-analysis avenue is now exhausted twice, the byte-level capture-correlation path (this section's own "Next step") is the recommended way forward — attempted this same session, see the item below. Byte-level correlation against existing capture data, 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 4 item 2) — a plausible structural match found, not a confirmed one; full closure still needs a fresh capture. Per this project's own scoping (a byte-level correlation against a fresh capture's burst vs.qjb's decoded shape is out of this task's scope — no new capture was taken), checked whether any already-captured connect-time burst frame is structurally consistent withqjb's decoded shape instead. WithinCAP-036's own connect-time burst (frame 1423,bthci_acl.chandle==0x0005, DLCI 0x02, Rcvd), a sub-message decodes to three length-14 strings inside a nested field —"57071WRBEC0251","57081WRBDR2309","57071WRBDL3147"— structurally matchingqie's documented shape (three optional sub-fields,REVERSE_ENGINEERING.md'sqjb/gaaentries:qie.c/.d/.e, one ofqjb's two oneof alternatives) far more closely than a coincidence would predict (three strings, same length, each resembling a component serial number — plausibly Case/Right/Left given the "EC"/"DR"/"DL" substrings, not decoded further perAGENTS.md§13.6). What this does NOT establish: whether this specific frame isqjbitself (as opposed to a different, sibling response type sharing the sameqie-shaped nested structure, e.g.GetHardwareInfo's own response) — the outer wire-format tags surrounding this sub-message were not individually matched field-by-field againstqjb's own top-level 4-field schema (fields 3/4/5/6) this pass, only the nested shape. A separate frame in the same burst (1421) contains a long run of repeated(index, float32)pairs — structurally telemetry-shaped, not matchingqjb's own simple 4-field shape at all, and plausibly a different RPC (SubscribeRuntimeInfo/GetHardwareInfo, both real catalog entries perfux.java) riding the same burst. Conclusion: existing capture data supports a plausible, not confirmed, structural correlation for one candidate frame — this is a genuine advance over "no correlation attempted at all," but per this item's own original scoping, full closure (confirming which exact RPC response a given frame is, not just a shape resemblance) needs a fresh, purpose-built capture correlating the burst against a live, independently-readGetSoftwareInfovalue — flagged as still needing that, not treated as closed. Re-traced 2026-09-13 (ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.mdPhase 5) — the "structurally matchingqie's documented shape" reading above is corrected, not confirmed, by decodingqie's own shape and re-checking frame 1423's exact bytes field-by-field. 🟢 FACT (mechanical decode + direct byte comparison, command+hex in the RESULT file):qiedecodes to 3 fields, each typedMESSAGE, notSTRING— aqieinstance's 3 fields would each need an extra nested tag+length wrapper around their string content. Frame 1423's actual 48-byte sub-message decodes cleanly as 3 fields (1/2/3), each a direct length-14STRING(0a 0e <14 bytes>,12 0e <14 bytes>,1a 0e <14 bytes>), with no such extra nesting — this does not matchqie's shape. It instead matchesqjm/qjr(GetHardwareInfo's own two oneof alternatives,qiv) exactly: both are 3-field, fields-1/2/3, all-STRINGmessages. Net effect: this pass strengthens the case that frame 1423 isGetHardwareInfo-shaped content, notGetSoftwareInfo/qjb-shaped — the opposite lean from the 2026-09-08 entry above, though RPC identity (service/method) still isn't decoded, only field shape. SeeREVERSE_ENGINEERING.md'sqjbentry for the full trace. A live correlation capture (video- transcribed serial numbers vs. the wire burst, checking which RPC actually fires) is designed as Group AS (PROPOSAL — new capture, plannedCAP-057). -
Added 2026-09-06,
CAP-042-FINDINGS.md§5 (Group AI,OBS-002): across a genuinely idle, ~37m39s, app-backgrounded session, the DLCI 0x02/0x04/0x08 periodic push recurred only twice (at ~16m and ~35m in — far sparser thanCAP-036's several-per-few-minutes sample with the app open on-screen), and HFP'sAT+BIEVdid not recur even once, dropping out of the previously-documented 4-channel near-lockstep sync (CAP-036-FINDINGS.md§12.5) entirely, while DLCI 0x02/0x04/0x08's own mutual near-lockstep timing continued to hold. 🔴 Genuinely open whether this reflects an app-foreground-driven trigger (a UI-refresh poll issued only while the app's own screen is active) rather than a Buds-autonomous or link-supervision-level mechanism — consistent with the data, not established by it; the natural next capture toggles the app between foreground/background mid-session with everything else held constant. Refinement proposed 2026-09-07 (EXTERNAL_REVIEW_VALIDATION_2026-09-07.md, cross-validating an external review), 🟡 HYPOTHESIS, contingent onAUDIT_REPORT_2026-09-07.md§1.0's own still-unconfirmed premise that DLCI 0x04/0x08 are GMS-implemented: if that premise holds, the companion app's own foreground state could only indirectly trigger this push, via some IPC call into GMS's Fast Pair/Nearby component, rather than the app polling the Buds directly — no such IPC call site was found in the companion app's decompiled code (checked,AUDIT_REPORT_2026-09-07.md§1.0), so this refines, without resolving, the open question above: is the trigger app-foreground-driven at all (via either path), or something else entirely? -
Added 2026-09-06,
CAP-042-FINDINGS.md§6:CAP-027-FINDINGS.md's "streaming specifically breaks the [4-channel] sync" reading (DESKRESEARCH_FINDINGS.md2026-09-04 round 2) does not hold as stated —CAP-042's fully idle, no-streaming session also shows HFP dropping out of the sync (previous item). The more general reading both sessions together support: HFP is the piece of the sync group that most easily decouples under multiple different conditions (active streaming, idle backgrounding), not specifically streaming — genuinely open which factor(s) actually govern HFP's participation. -
Added 2026-09-08 (
ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1/Phase 4, maintainer-approved per prompt0002) — informational context, not an implementation option. The companion app itself sources its on-screen battery display and handles Find My Device Terms-of-Service consent via two genuinely named, unobfuscated AIDL interfaces incom.google.android.libraries.bluetooth.fastpair—IFastPairDeviceDetailService(returns aTrueWirelessHeadset{leftBud, rightBud, headsetCase}Parcelable, each aHeadsetPiece{batteryLevel, charging, lowLevelThreshold, ...}) andIFastPairFmdProxyService(Find My Device ToS accept/skip) — both bound through Google's Chimera dynamic-module broker (com.google.android.gms.chimera.GmsBoundBrokerService,defpackage/ijk.java:42-52,defpackage/ijp.java:37-45,defpackage/iji.java:20-33). This is informational context on how the reference app is built, not a candidate implementation path for this project's own app — using acom.google.android.gms-internal Chimera-broker AIDL interface would itself require Google Play Services, whichAGENTS.md§1's Zero-GMS rule forbids. It does not add a mechanism to §4.3's Option A–E list. Seeai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1 "Finding 1" for the full trace. -
Added 2026-09-08 (
ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1/Phase 3/Phase 4, maintainer-approved per prompt0002).com.google.android.apps.wearables.maestro.companion.settingprovider.service.MaestroDeviceSettingsProviderService(manifest:exported=true,permission=android.permission.BLUETOOTH_PRIVILEGED, intent-filter actioncom.google.android.apps.wearables.maestro.companion.services.BIND_SERVICE) is a second, previously-undocumented UI entry point into theqhr/WriteSettingpipeline, independent of this project's already-documented in-app screens: it implements the AOSPSettingsLib"Bluetooth Device Details" custom-settings-extension callback interface (com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener, obfuscated asfhk), and itsct(DeviceInfo, DeviceSettingState)handler routes at least 6 case IDs (2102/2103/2104/2113/2115/2116) into the sameftj/fya-interface accessor chain (.f(),.b(), etc.) already used by the confirmed in-app write sites — i.e. the system Settings app's own Bluetooth-device-details page can also trigger aqhr/WriteSettingwrite, not only this project's own already-documented in-app screens. Which specificqhrfields these 6 case IDs map to was not individually traced (case 2104 passingfpm.ENABLED_HEAD_GESTURES/fpm.UNKNOWNis a promising but unconfirmed lead for head gestures/field 29). 🔴 the case-ID→qhr-field mapping. Not GMS-related — this is Android's own platform settings-extension framework, notcom.google.android.gms.*. Seeai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1 "Finding 1" (last bullet) and Phase 3 "Touch controls"/"Head gestures". Update (2026-09-08,ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 1) — all 6 case IDs now traced to their exact accessor call; the "head gestures" lead above did NOT pan out as expected.2102→qhrfield 2 (also logged under an internal category name,CATEGORY_OHD— promoted to category-level identity 2026-09-08, maintainer sign-off,DECISIONS.mdADR-019 Update; see §4.5.5);2103→field 27 (matches the existing category-level identity);2104→field 11 (Multipoint, not head gestures — the case's own logged category is literallyCATEGORY_MULTIPOINT, the same internal-name evidence type that helped promote fields 11/15 to full identity, though thefpm.ENABLED_HEAD_GESTUREStag riding alongside it in the same call is unreconciled, a genuinely open naming tension, not resolved);2113→field 5 (still unnamed);2115→does not touchqhrat all (a separate, unnamed "Feature A" toggle);2116→field 32 (a field not previously in this project'sqhrregister at all, logged under an internal diagnostic-sounding category,CATEGORY_RV_BLOCK_AUTO_TEST). Case 2104 also fires a second, non-qhrwrite to the same "Feature A" mechanism as case2115— 🟡 plausible, unconfirmed lead that toggling Multipoint here disables a mutually-exclusive feature (Spatial Audio is a plausible, unevidenced candidate). Head gestures (field 29) is NOT among these 6 mappings — none of the 6 cases route tofyo's field-29 write path; this specific lead is now a checked negative, not merely still open. Twoqhrfield-register corrections surfaced as a byproduct: field 6 has a real write site (previously wrongly recorded as "not found"); field 32 (new) is now registered. Full trace:REVERSE_ENGINEERING.md'sMaestroDeviceSettingsProviderServiceentry. Field 2's promotion above and the case-2104/fpm.ENABLED_HEAD_GESTURESnaming tension were both reviewed directly by the maintainer in the chat session that authored this task's own prompt (ai-sessions/0003_MAINTENANCE_PROMPT_2026_09_08.md); the naming tension was left open, not resolved. -
Added 2026-09-08 (
ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1, maintainer-approved per prompt0002). The manifest also declaresMaestroEndpointService(grpc.ondevicegrpcserver), exported with noandroid:permissiongate, hosting a generic on-device gRPC server (mig/oez— a method-descriptor-keyed dispatch table structurally likeio.grpc.ServerServiceDefinition). No other reference to this class/package exists anywhere else in the decompiled tree (grep -rln "MaestroEndpointService\|ondevicegrpcserver"finds only the class itself and itsghlbase). 🔴 OPEN QUESTION: which gRPC service(s) it actually registers, and whether anything (GMS included) ever binds to it —onCreate()is JADX-undecompilable bytecode ("Method dump skipped... 599 instructions"), which perAPK_REVERSE_ENGINEERING_PROCEDURE.md§6 would need anapktoolsmali fallback read, not yet attempted. Plausibly unrelated to DLCI 0x04/0x08 entirely (could be a different feature, e.g. cross-device sync) — its exported-with-no-permission shape is worth a maintainer look on its own terms. Seeai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 1 "Verdict on Q1–Q3", Q3. Update (2026-09-08,ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.mdPhase 3 item 2) —onCreate()read via theapktoolsmali fallback; the registration mechanism and its authorization model are now understood, the literal service names are not. The registered services come from a Dagger/Hilt multibinding (Map<String, Optional<ofd>>) assembled elsewhere in the app — no code inside this class hardcodes a service list, and the multibinding's own assembly site wasn't found this pass.ofd's single method takes a calling UID, not a method index (despite superficially resembling a gRPC dispatch interface) — it is an authorization-policy check: reject or allow a call, before it's honored. Two concrete policies exist:ofb(allow only the app's own UID —"Rejected by (internal-only) security policy"otherwise) andmie(allow only an allowlisted, Google-signed caller package —"Rejected by (1st-party only Allowlist) security policy..."otherwise). This substantively answers "is anything actually gated to bind to this," without naming GMS specifically: the exported-with-no-Android-permission manifest shape is not the whole picture — the service applies its own per-service, application-layer authorization. Whether GMS specifically is ever allowlisted for any of this service's own registered methods was not determined (the onemieconstruction site found belongs to an unrelated outbound gRPC client connection to a different Pixel system app,com.google.android.apps.pixel.dcservice— a genuinely new, incidental finding, out of scope for this project's own Bluetooth focus). Full trace:REVERSE_ENGINEERING.md'sMaestroEndpointServiceentry.
-
Added 2026-08-21,
CAP-025-FINDINGS.md§7/§8 — directly relevant to this project's Zero-GMS goal (AGENTS.md§1); scope decision resolved 2026-09-13 (maintainer sign-off,DECISIONS.mdADR-027). Whether "Find My Buds" for the Case and "both simultaneously" (FIND-003/FIND-004) genuinely requires Google's Find My Device Network with no local-only fallback is still, on the research question alone, not fully closed (see the code-level support below) — but this no longer blocks anything: the maintainer decided v1 ships with Left/Right ring only, and Case/"both" is now an explicit, permanentPROJECT.mdnon-goal rather than an open item pending further research. Reopen only if a future capture or protocol change surfaces a genuine local mechanism. Update (2026-09-08,ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.mdPhase 3, maintainer-approved per prompt0002) — code-level support (not proof) for the Zero-GMS-hard-limit reading. The companion app's owncom.google.android.apps.wearables.maestro.companion.fmd.FmdWorkersendsFmdRequests (operation codes3="accept"/4="skip") overIFastPairFmdProxyService— but only for Find My Device Terms-of-Service accept/skip; no otherFmdRequest.d()call site exists anywhere in this APK version's decompiled source (grep -rn "FmdRequest\.d()"→ 2 hits total, both accept/skip). This confirms, at the code level, that the companion app's own role in the Case/"both" path is limited to consent/onboarding plumbing — the actual ring-trigger is not constructed anywhere in this app's decompiled source, consistent with (and partially explaining) the wire-observed "Connecting…" Find Hub map-view hand-off to a separate app surface — supporting evidence for ADR-027's decision, not what settled it (the decision itself was a scope call, not a research closure). Not resolved by this decision, still genuinely open if anyone cares to chase it (low priority, no longer blocking): what triggers the three repeated classic-RFCOMM-connection- reopen bursts (~40s apart) observed while this Find Hub flow was active inCAP-025. -
Added 2026-08-21,
CAP-011-FINDINGS.md§4/§5: does an active classic RFCOMM connection suppress or alter the Buds' Fast Pair Battery Notification BLE advertisement?CAP-011's own attempted passive-scan capture had an active connection present throughout (a procedure deviation, not the intended design), leaving this un-isolated from the capture's other finding (the sampled payload not matching the documented Battery Notification byte layout at all — see the Commands & schemas entry above). -
Added 2026-08-21,
CAP-024-FINDINGS.md§4: does Case sounds'"Bud return"setting (CASE-001) require an explicit tap to register a write even when the value doesn't change, or does opening the "Case sounds" screen itself trigger a state-sync write on DLCI 0x02? One sampled frame (1988) isn't disambiguated between these two readings from video alone. -
Whether Loud Noise Protection and/or Adaptive Audio generate any Bluetooth traffic toward the phone at all, or remain fully on-device DSP decisions with no wire-visible signal.
-
Whether Adaptive Audio requires the official app to remain active to keep functioning, or is a one-time write to the buds — relevant to the
ForegroundServicedesign inARCHITECTURE.md§2/§6. -
Confirmed press duration for triggering pairing mode via the case button, distinct from the confirmed 30-second factory-reset hold.
-
Whether captured RFCOMM payload bytes are ever link-layer encrypted in a way that requires extra Wireshark configuration to decode.
-
Added 2026-08-14; narrowed 2026-08-26 (
CAP-008-FINDINGS.md§3, Group V, proposal awaiting sign-off): why HFP AT-command traffic never recurs afterCAP-001's own handshake — confirmed as a genuine negative inCAP-002(zeroAT+traffic anywhere else across a full 8+ hour shared log spanning multiple reconnects,CAP-002-FINDINGS.md§5).CAP-008(Bluetooth radio switched off then back on, a fresh classic-link connection) shows the full SLC handshake does reoccur on that kind of reconnect, structurally identical toCAP-001's — narrowing the question to "which reconnections retrigger it," not "does it ever recur." Still open: whether a reconnect that does not involve a full radio power-cycle (e.g.PAIR-003, disconnect/reconnect to an already-bonded device without toggling Bluetooth off) also retriggers it, or whetherCAP-002's negative result specifically reflects that its ACL connection was simply never torn down within that log's window — not tested by either capture to date. Further narrowed 2026-08-26 (CAP-012-FINDINGS.md§6, Group S repeat, incidental — proposal awaiting sign-off): this exact untested case now has a data point. A manual disconnect + reconnect via system Bluetooth settings (no radio toggle —PAIR-003,CAP-012's Sequence 2) does retrigger the full HFP AT-command SLC handshake on DLCI 0x0c, the same shape asCAP-008's radio-power-cycle case. Two independently-triggered reconnect types now both show recurrence and none showCAP-002's original silence —CAP-002's negative result increasingly looks attributable to "that session's ACL connection was simply never torn down" rather than to which reconnect mechanism is used, but this is two data points, not yet a settled rule. (CAP-012's own log was severely ACL-truncated, so only the handshake's recurrence, not its exact AT-command content, is confirmed here.) -
Added 2026-08-14: live GATT primary-service discovery requires stronger cache-busting than bond removal — confirmed as a genuine requirement, not an assumption: three independent captures (
CAP-002,CAP-003,CAP-004) all failed to trigger a liveRead By Group Typeresponse against the Buds despite bond removal beforehand in two of them (CAP-003-FINDINGS.md§1,CAP-004-FINDINGS.md§6).CAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup W (new) proposes two untried, stronger candidates. -
Added 2026-08-18,
CAP-016-FINDINGS.md§3/§9: what triggers the RFCOMM multiplexer channel-bounce class (all 4 DLCIsDISC+reopened in sequence, ACL link itself undisturbed)? Confirmed not solely tied to bud removal —CAP-016reproduces the same bounce shape with no camera-visible cause anywhere nearby, whileCAP-007(old) shows one coincident with a bud removal. No positive mechanism identified in either capture. -
Added 2026-08-18,
CAP-016-FINDINGS.md§3/§9: why does the ANC Notify's settable-toggles byte revert from0xe8to0x00a second time (frame 3054, 06:33:23.456), after already having re-announced0xe8/Transparency twice during the same channel bounce (frames 2768, 3012)? Not resolved by any video-visible action in that capture. -
Added 2026-08-18,
CAP-016-FINDINGS.md§6: docking a bud produces no distinct "docked" wire event either — the ±3s window around each of the two bud-redocking actions this session shows only routine periodic traffic (or unrelated background BLE scan noise), no RFCOMM data frame, ANC re-notify, or DLCI 0x08 Code0x12push tied to the docking action itself; only the eventualDisconnection Completeonce both buds are back (§5's promoted FACT). -
Added 2026-08-18,
CAP-016-FINDINGS.md§2/§9: does the second, distinct BLE address (4f:25:00:85:9a:b1, connected 06:31:40.983) actually belong to the same physical Buds unit as classic peer04:00:6e:cf:6e:07? Time-coincident only — not content-verified in that pass; a GATT-level read of that handle's advertised service data would settle it. Second occurrence, 2026-08-26 (CAP-013-FINDINGS.md§6): a further second BLE link, to yet another random/resolvable address (43:8a:82:03:4b:f2), forms shortly after classic pairing/RFCOMM-channel-open in that session — same open question (not content-verified as the Buds' own address), not yet the same address asCAP-016's either, so this doesn't confirm a stable secondary identity, only that the pattern (an unattributed second BLE link appearing around connection time) recurs. Tested and not reproduced, 2026-08-27 (CAP-031-FINDINGS.md§6), PROPOSAL — pending maintainer approval: a third capture (CAP-031) checked its full log for anyLE Enhanced Connection Completebeyond the Buds' own link — found exactly one, resolving to the Buds' own public address (04:00:6e:cf:6e:07), with zero occurrences of either43:8a:82:03:4b:f2or4f:25:00:85:9a:b1. This is a clean negative data point (the phenomenon is not universal, plausibly session-specific noise or a nearby unrelated device rather than a stable Buds-side secondary identity) but does not itself resolve what either prior address actually was — both remain 🔴 OPEN independently. -
Re-raised 2026-08-26, still unresolved from
CAP-001-FINDINGS.md§6 (primary questionCAP-013was meant to answer,TESTPLAN_BLUETOOTH_HCI_SNOOP.md'sPAIR-004): did a BLE link and/or a still-valid classic link key already exist for this peer before the on-screen clearing action (CAP-001's "Forget" tap at 08:51:02–03, pre-dated by a BLE link at 08:50:36)?CAP-013(2026-08-26) attempted the planned repeat — start HCI snoop logging before any association exists at all — but logging in that session did not actually begin until 2m21s after its own clearing action ("Reset Bluetooth & Wi-Fi", not a single-device "Forget") and after the entire subsequent case-open/pair-button/device-selection sequence (CAP-013-FINDINGS.md§0). Still 🔴 OPEN QUESTION, not narrowed byCAP-013— a genuine repeat, with logging verified to start before the clearing action itself, is still needed. WhatCAP-013did confirm: the classic-link re-pairing that followed its own clearing action used a fresh SSP handshake, not a reused key (CAP-013-FINDINGS.md§2/§7) — another instance ofPROTOCOL.md§5.1's already-FACT "fresh pairing" path, not a new finding in itself. Second attempt, 2026-08-27 (CAP-031-FINDINGS.md§0), PROPOSAL — pending maintainer approval:CAP-031retried the same repeat, this time with a genuine narrow per-device "Forget" (screenshot-confirmed, unlikeCAP-013's broader reset) and a live snoop-log file-size-polling check during recording specifically meant to avoidCAP-013's failure — but the log's first frame still starts 66s after the Forget tap, and after the case-open/pair-button/first-scan-attempt sequence too. Still 🔴 OPEN QUESTION, untested a third time — a fourth attempt is needed, this time verifying the snoop log's own content freshness (last-frame timestamp against a live wall clock), not just its file size, before the Forget tap (CAP-031-FINDINGS.md§8). WhatCAP-031did confirm: a sixth instance of the fresh-SSP path (CAP-031-FINDINGS.md§2/§7), and two negative results againstCAP-013's own bonus findings — DLCI 0x02's ~61s-delayed open and the unattributed second BLE link both failed to reproduce this session (CAP-031-FINDINGS.md§5/§6), suggesting those were single-session artifacts rather than recurring behavior. Third/fourth attempt, 2026-08-27 (CAP-032-FINDINGS.md§0), PROPOSAL — pending maintainer approval — succeeded. Extracted via the raw BTSnoop file path instead of thebtsnooz.pyfallbackCAP-012/CAP-013/CAP-031all used — the resulting log is genuinely untruncated and its first frame (18:29:45.72) lands ~58s before the on-screen Forget tap (18:30:42), and ~30s before the video itself starts, finally covering the pre-clearing-action window. Across that entire covered window: zero classic BR/EDR connection events of any kind, exactly one LE connection (resolving to an unrelated random-address device exposing a Heart Rate GATT service, not the Buds), and theDelete Stored Link Keycommand issued at the Forget tap's own moment reportsNum_Keys_Deleted = 0(CAP-032-FINDINGS.md§0.3/§1, byte-level HCI evidence). For this session specifically: no BLE link and no valid classic link key existed for the Buds anywhere before the Forget tap. This is a clean counter-example toCAP-001's original finding, not a reproduction or a refutation of it —CAP-001's own session-specific puzzle (why that session had a BLE link and a valid key present before its clearing action) remains independently 🔴 OPEN, and this section's status is left as OPEN QUESTION rather than moved to "Resolved" below, pending maintainer review of whether a single clean session settles the general claim or only demonstrates it is non-universal.CAP-032also reconfirmed the fresh-SSP path a seventh time (CAP-032-FINDINGS.md§2/§7 Test B) and, as a genuinely new finding not previously documented, found a vendor-specific HCI command (0xFD57/0x0157, frame 91, 105ms into the log) whose payload embeds the Buds' address as part of an apparent bulk bonded-device-list provisioning at Bluetooth-stack bring-up — recorded 🔴 OPEN QUESTION on its own terms (unconfirmed vendor semantics), not bearing on this section's primary question (CAP-032-FINDINGS.md§5). -
Added 2026-09-06,
CAP-037-FINDINGS.md§5 (Group AD,OBS-004purpose-built repeat); resolved 2026-09-12,CAP-048-FINDINGS.md§4; maintainer sign-off obtained 2026-09-13 (ai-sessions/0015_MAINTENANCE_RESULT_2026_09_13.md): on one of 26 same-sessionDECISIONS.mdADR-022 replications, a chandle shows a second "Notify ANC state" frame 18 seconds after the first, with no new08 11Get frame in between, and itsSettable-togglesvalue flips from0xe8to0x00.CAP-048(a repeat with continuous physical dock-state video) reproduced this exact shape (chandle0x0010,17:52:25.14) and resolved it directly: the video, extracted at the exact wire timestamp, shows a hand actively placing a bud into the case at that moment — a genuine real-time dock-state change while the ACL connection stayed open, consistent with (not in tension with)DECISIONS.mdADR-016 — the second bud's own docking, 5.4s later, is what then triggers the disconnect, matching ADR-016 exactly. -
Added 2026-09-12,
CAP-048-FINDINGS.md§5: a genuine, video-confirmed counter-example to a simple reading ofDECISIONS.mdADR-024: two fresh classic reconnects (17:44:45,17:47:42) reportSettable-toggles=0x00(docked) while the video, checked at essentially the same wire timestamp, shows the case visibly empty. Four other readings in the same session (including two same-chandle DLCI reopens, not fresh reconnects) are correct. 🟡 HYPOTHESIS, not confirmed: a fresh reconnect's own Get/Notify might sometimes return a value queried before the Buds' own firmware has settled on an already-changed physical state — offered as a testable direction only; does not by itself explain why four other fresh reconnects in the same session read correctly. Reported plainly, not reconciled away, perAGENTS.md§13.6. Extended 2026-09-15 (CAP-047, Group AL) — two more counter-example readings, plus a first direct test of a swapped-slot (mismatched L/R) seating, in an independent session.CAP-047confirmsDECISIONS.mdADR-024's core finding directly answers a previously-untested case:Settable-togglesreads0x00("both docked") during a swapped-slot seating exactly as it does for a correctly-slotted one (CAP-047-FINDINGS.md§5, frame 2803,06:10:20.78, video-correlated to the on-screen swapped-dock moment) — the dock-sensor mechanism does not check slot correctness, only physical presence. Separately, two further stale-reading counter-examples were found (CAP-047-FINDINGS.md§5): one following a channel-level reopen (not a full ACL reconnect) that self-corrects 1.15s later via a spontaneous re-Notify — a second, independent instance of this file's own "settling" HYPOTHESIS above — and one with no preceding channel-(re)establishment at all (a spontaneous Notify on an apparently-empty case), which does not fit the settling explanation and is left as its own, still-open question. -
Added 2026-09-15,
CAP-047-FINDINGS.md§4 (Group AL) — unreconciled tension withDECISIONS.mdADR-016's disconnect-on-redock mechanism. Of three swapped-slot (mismatched L/R) dockings captured across two sessions/recordings, two produce a genuine ACLDisconnection Complete(reason0x13, Buds-initiated) within seconds, matching ADR-016 exactly — but the third (CAP-047Recording 1,06:10:21) does not: the classic connection stays up continuously for the rest of that ~22-minute log, despite the dock-state sensor itself reading "both docked" (Settable-toggles=0x00) at that exact moment, the identical reading the other two instances also show. No video- or wire-visible procedural difference between the three docking actions was found that would explain the discrepancy. 🔴 OPEN QUESTION — reported plainly, not force-fit into a guessed mechanism, perAGENTS.md§13.6. -
Added 2026-09-12,
CAP-048-FINDINGS.md§6: a 7-event connection-retry burst (17:48:51–17:49:47, repeatedCreate Connection/Connect Completecycles on one chandle, no Disconnection Complete between them) coincides with the app showing "Connecting…" and the case appearing closed on video. 🟡 HYPOTHESIS, not confirmed: a closed case may impede reliable classic-link establishment — plausible, not verified against any documented mechanism. -
Added 2026-09-06,
CAP-039-FINDINGS.md§6 (Group AF,OBS-006): across a single ~6-minute session, the classic ACL connection to the Buds disconnected and reconnected 5 times with no clearly camera-visible trigger for most of them (4 of 5 disconnects locally terminated, reason0x16, the same patternCAP-040's own procedure used deliberately with the app's Connect/Disconnect buttons — thoughCAP-040-FINDINGS.md§1 separately found those specific buttons produce zero wire signal, so this session's own trigger remains unidentified). Genuinely open what specifically caused the repeated disconnect/reconnect cycling here. Repeated 2026-09-12 (CAP-049, continuous phone-screen recording) — clean negative, does not resolve the trigger: the cycling did not reproduce; the classic connection stayed open and stable for the entire ~9-minute repeat session after one deliberate reconnect. This rules out nothing aboutCAP-039's own original occurrence — only confirms the phenomenon is not reliably reproducible under the same general procedure. -
Added 2026-09-12,
CAP-018-FINDINGS.md§3 (Group Y,GATT-002): the classic reconnect took ~64s from the Bluetooth-toggle tap to the phone actually issuingCreate Connection(video-confirmed toggle at~06:07:23, wireCreate Connectionnot until06:08:27.77) — slower than the few-seconds-or-less reconnect timing typical of this project's other captures. Not investigated further this session (out of scope forGATT-002's own question); genuinely open why. -
Added 2026-09-12,
CAP-028-FINDINGS.md§3: a third session (afterCAP-032,CAP-018) shows the same unrelated-nearby-device BLE signature — a standard Heart Rate service (0x180D) present, no Google Fast Pair Service, no0x0c0Xhandle cluster — this time coincidentally overlapping a head-gesture test window with substantialHandle Value Notification/Write Commandtraffic on handles0x0044/0x0042. Not attributable to the Buds. Genuinely open whether this is the same physical device recurring across sessions (BLE address rotation makes this unverifiable from the address alone) — flagged for awareness, not investigated further. Re-checked 2026-09-13 (ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.mdPhase 3): a full re-scan ofCAP-028's entire 227.71s log (not just the originally-checked 07:16:25–07:17:15 window) for DLCI 0x02/0x04/0x08 traffic, AVRCP/AVCTP, genuine SCO/eSCO (Synchronous Connection Complete) events, and anyAT+HFP command reproduces the same clean negative — zero new signal anywhere in the log, and the 3 additional DLCI-0x04/0x08 pushes found after the original window's end (07:17:15.55, 07:18:02.17, 07:19:35.78) are the already-documented routine periodic battery/keepalive push (§4.3 Option B/E content), not gesture-related. A correctly scoped repeat (active call/notification, camera also framing the gesture) is designed asCAPTURE_BLUETOOTH_HCI_SNOOP.mdGroup AQ (PROPOSAL — new capture, plannedCAP-055). -
Added 2026-09-12,
CAP-029-FINDINGS.md§2: Conversation Detection's on-screen media-pause effect (CONV-002) produces zero wire-visible signal — no ANC-mode Set/Notify (08 12/08 13), no DLCI 0x02 settings write, no AVRCP command — anywhere in the session. Genuinely open whether the pause is driven by a mechanism this project hasn't identified, or whether the documented "switches to Transparency" behavior simply didn't trigger this time (ANC was already Adaptive).
- UI-baseline firmware version for the test device —
release_5.203, confirmed via official app screenshot, 2026-07-30. This is what the app's About/settings screen displays — not the same thing as confirming what appears on the wire (see the "wire-baseline" item under Framing, added 2026-08-15). - DLCI 0x08 Group
0x04Code0x12's alternating value — resolved 2026-08-18, 🟢 FACT, maintainer sign-off obtained 2026-08-28 (DECISIONS.mdADR-016): neither purely reactive nor purely free-running — it fires in step with DLCI-0x08 channel-(re)open events, and continues firing autonomously during otherwise-idle stretches after a gap with no channel churn. First characterized this way inCAP-004-FINDINGS.md§5a Task 5 (irregular-interval, near-perfect alternation) andCAP-007-FINDINGS.md(old) §3.2/§5 (fires with channel-(re)opens, and independently during idle periods); independently reconfirmed by a second, distinct session,CAP-016-FINDINGS.md§7 (8 pushes, cycling0x02/0x03, 2 in step with channel-(re)opens, 4 during idle stretches with no churn) — same characterization holds across two independent captures, clearingPROJECT_RULES.md§1's promotion bar for the behavior (event-driven and autonomous). What the value itself encodes remains 🔴 open — not resolved by either capture.
| Scenario | Observed behavior | Status | Evidence |
|---|---|---|---|
| Malformed/unparseable frame (bad magic/length, checksum failure) | Dropped silently, surfaced internally as BudsError.MalformedFrame, never a crash |
Design rule (not yet capture-verified) | AGENTS.md §6, ARCHITECTURE.md §5/§7 |
| Connection lost during write | ConnectionState moves to DISCONNECTED; in-flight polling coroutines cancelled |
Design rule (not yet capture-verified) | ARCHITECTURE.md §6 |
| Buds out of range | Expected: IOException → ConnectionLost, per architecture |
⚪ ASSUMPTION | — |
| Case closed during connection | Terminates the active Bluetooth Classic connection — capture-verified 2026-08-18: the trigger is specifically both buds being docked (ACL Disconnection Complete fires the instant the second bud is placed in the case, reason 0x13, Buds-initiated), not the lid closing itself — closing/reopening the lid alone, with no bud docked, is wire-silent (see row below) |
🟢 FACT (maintainer sign-off 2026-08-28, DECISIONS.md ADR-016) |
TESTPLAN_BLUETOOTH_HCI_SNOOP.md §2, CAP-016-FINDINGS.md §1 |
| Case lid opened/closed while both buds remain outside the case | No wire-visible signal on any RFCOMM channel (0x02/0x04/0x08/0x0a) — whatever senses the lid position, if anything, does not report it to the phone while no bud is docked |
🟢 FACT — 2 independent captures (maintainer sign-off 2026-08-28, DECISIONS.md ADR-016) |
CAP-007-FINDINGS.md §3.4, CAP-016-FINDINGS.md §5 |
| Inbound frame matching no known schema version | Returns UnsupportedFirmware rather than a best-effort parse |
Design rule (not yet capture-verified) | ARCHITECTURE.md §8 |
| Date | Change | Author (human/AI model) |
|---|---|---|
| 2026-08-07 | Initial formal specification promoted from PROTOCOL_NOTES.md; includes both RFCOMM framing hypotheses, battery mechanism options A–D, Find My Buds/Ring hypothesis, and consolidated open questions |
Claude (AI), reviewed by maintainer |
| 2026-08-12 | Added §2.2a: DLCI 0x02's framing confirmed as Pigweed pw_hdlc (flag/escape/LEB128-address/control/CRC-32), matching pbpctrl's own Maestro-transport notes; promoted to 🟢 FACT for the framing mechanism (640/640 sub-frames verified across 3 captures). Restructured §2.3's binary framing question into a three-channel table (DLCI 0x04/0x02/0x08). §4.1 ANC mode promoted to 🟢 FACT: Google's official "Hearable Controls" Fast Pair extension (Message Group 0x08, Codes 0x11/0x12/0x13) matches CAP-001 byte-for-byte, including a 4/4 content+timing correlation against that capture's own recorded ANC taps — resolves the project's original highest-priority open command question, on the official Message Stream (DLCI 0x04), not libmaestro. Updated §6 Framing and Commands checklists accordingly. libmaestro (DLCI 0x02) and the private DLCI-0x08 envelope's command content, and EQ/other settings, remain unconfirmed — FrameEncoder/FrameDecoder implementation gate (AGENTS.md §6) remains closed pending a DECISIONS.md ADR |
Claude (AI), deskresearch task, not yet reviewed by maintainer |
| 2026-08-17 | §4.3 Option D (BLE Battery Service 0x180F) raised from 🔴 to 🟡 HYPOTHESIS: service existence confirmed via CAP-017's (18:30) session's live GATT discovery (CAP-017-FINDINGS.md §3) — content/usage still unconfirmed, handle range still unresolved. Cross-check pass across all 9 capture sessions' documents; also updated TESTPLAN_BLUETOOTH_HCI_SNOOP.md's GATT-001 row to include this session (it previously only referenced the earlier, unsuccessful 11:42 CAP-010 attempt) |
Claude (AI), deskresearch task (HYPOTHESIS-level only — no FACT promotion, no sign-off needed) |
| 2026-08-17 | §6 Commands & schemas: DLCI 0x02 deskresearch pass across all captures with DLCI-0x02 traffic (CAP-001–CAP-003, CAP-006, CAP-007, 11:42 CAP-010). Answered the "is field-16/18 EQ-specific?" open item with a clean negative result (zero matches outside CAP-005, including CAP-006's clean isolated ANC taps). Surfaced a new open item: two previously-undocumented HDLC addresses (0x1e80/0x2680 Sent, 0xe980 Rcvd) recur at connection-reopen events in CAP-005/CAP-007, carrying the same already-documented serial+firmware content as the 0x0000/0xD180 pair — HYPOTHESIS that DLCI 0x02's Address field is per-connection-negotiated, not fixed. Full method in DESKRESEARCH_FINDINGS.md |
Claude (AI), deskresearch task (HYPOTHESIS-level only — no FACT promotion, no sign-off needed) |
| 2026-08-18 | §4.2 EQ updated from a fresh, independent CAP-015 session (captures/CAP-015-2026-08-18_06-11-06_06-17-40-Group_T/CAP-015-FINDINGS.md) that drags all 5 EQ sliders individually (3 passes each) and taps 5 presets, resolving the 2026-08-15 capture's field-to-band open question: field-to-band mapping promoted to 🟢 FACT (field 1↔Low bass, 2↔Bass, 3↔Mid, 4↔Treble, 5↔Upper treble, wire order reversed from on-screen order), matching the earlier single-band inference exactly. Also added: the ±6.0 band-gain clamp (🟢 FACT, units unconfirmed), a confirmed preset-quintet reference table, and a revised (still 🟡) reading of outer field 16/18 as preview/slider-release rather than preview/explicit-Save-tap. Updated the corresponding §6 open-question entry non-destructively |
Claude (AI), capture-analysis task; retroactive maintainer sign-off obtained 2026-08-28, DECISIONS.md ADR-016 |
| 2026-08-18 | Synced with CAP-016 (Group U re-run, captures/CAP-016-2026-08-18_06-31-31_06-33-58-Group_U/CAP-016-FINDINGS.md): §5.1 added the Buds-initiated reconnect-on-removal variant (🟢 FACT, frames 1213–1217); §7 added the case-lid-closed/re-docked disconnect row (🟢 FACT, Disconnection Complete reason 0x13 fires the instant the second bud is docked, not on lid-close alone) and the case-lid-open/close-while-buds-are-out zero-signal row (🟢 FACT, 2-capture-confirmed with CAP-007); §6 "Resolved" added the DLCI 0x08 Group 0x04 Code 0x12 behavior characterization (🟢 FACT for the event-driven-and-autonomous behavior, value's meaning still 🔴 open) and several new open items (RFCOMM channel-bounce trigger, ANC settable-toggles byte, the 0x0044 BLE notification burst, the AndroidHeadTracker HID Feature report) |
Claude (AI), capture-analysis task; retroactive maintainer sign-off obtained 2026-08-28, DECISIONS.md ADR-016 |
| 2026-08-21 | Synced with 8 new captures (CAP-011, CAP-019–CAP-025): §4.4 Find My Buds/Ring — Left/Right confirmed 🟡 HYPOTHESIS (strong), video-correlated, proposed for 🟢 FACT pending maintainer sign-off (CAP-025); Case/"both" found to route through a separate, likely GMS-mediated Find Hub mechanism producing no local wire command — flagged as a possible Zero-GMS hard limit. §4.5 rewritten from a bare unmapped-feature bullet list into per-command subsections (§4.5.1–§4.5.8), each with a confirmed DLCI 0x02 opcode, following §4.1–§4.4's structure, plus a new shared preamble describing the general-purpose field5{field4{...}} settings-write envelope discovered this batch (9+ settings, 6 captures, no counter-example). §4.3 Option A — CAP-011 attempted a passive BLE scan; result recorded as inconclusive (Fast Pair Service traffic present but not structurally matching the documented Battery Notification layout), not force-fit; procedure deviation (active connection present) flagged. §0.1 — wire-baseline-vs-UI-baseline firmware version resolved (CAP-023): on-screen release_5.203 matches DLCI 0x08's already-documented string, same session. §6 updated with ~10 new open items across Commands & schemas and Behavior, including a newly-raised Zero-GMS-relevant question about Find Hub's Case/"both" ring mechanism |
Claude (AI), capture-analysis task, not yet reviewed by maintainer |
| 2026-08-23 | Remediation from an external audit pass (maintainer-approved fixes, see CHANGELOG.md's 2026-08-22/23 entry for the full report summary): §2.1/§4.4 corrected — the cited "spec worked ACK example" for the Ring action did not match Google's actual Fast Pair acknowledgement spec (verified by direct fetch); corrected via non-destructive dated notes per PROJECT_RULES.md §3, and the "byte-for-byte match to spec" claim for one observed ACK variant retracted (neither observed variant actually matches the corrected spec example). §6 reopened the Ring ACK extra-byte open item against the corrected spec tail, and added a refined characterization (timing/direction/entropy profile) of CAP-021's still-unexplained DLCI 0x0a burst. §4.3 Option C annotated to explain DLCI 0x08 vs. 0x09 both being called "channel 4" (same RFCOMM multiplexer session, disambiguated by direction bit — not a numbering error). §4.3 Option D added the Battery Level characteristic UUID (0x2A19) alongside the already-documented service UUID (0x180F) |
Claude (AI), audit-remediation task, maintainer-directed |
| 2026-08-23 | Three pending FACT promotions reviewed and explicitly approved by the maintainer (AGENTS.md §6), each recorded with its own DECISIONS.md ADR: §4.4 Find My Buds Left/Right promoted to 🟢 FACT (ADR-011) — Case/"both" remains a separate, unresolved mechanism, not covered. §0.1 wire-baseline firmware version ("release_5.203" on DLCI 0x08) promoted to 🟢 FACT (ADR-012) — "Revision 6"'s meaning remains open, not covered. §4.5's shared preamble, general-purpose DLCI 0x02 settings-write envelope shape promoted to 🟢 FACT (ADR-013) — narrower than it may look: only the outer field5{field4{...}}} wrapper's existence/shape is FACT; every individual setting's specific field-number mapping in §4.5.1–§4.5.8 remains its own, separately-labeled 🟡 HYPOTHESIS, per the maintainer's explicit decision not to blanket-promote |
Claude (AI), maintainer-directed sign-off session |
| 2026-08-23 | §4.3 Option E added — re-analysis of CAP-011 (prompted by the maintainer spotting a 1% battery drop in the recording) pinpointed the exact UI-change timestamp (09:52:25.8, correcting an initial ~09:45:47 estimate) and found a DLCI 0x08 message (Group 0x0e Code 0x01) whose entries track on-screen battery values. Cross-capture check same day found a clean 3-for-3 match (Left/Right/Case) in 2 further independent sessions (CAP-001, CAP-002, both 2026-08-09) — upgrading this from a single-session (CAP-011, 4 internal recurrences) finding to a 3-session, 12-day-spanning one; CAP-011's Case entry specifically reads stale/non-matching, flagged as its own open item, not treated as contradicting the mapping. 🟡 HYPOTHESIS (strong), proposed for FACT pending maintainer sign-off — not yet reviewed. Refines an already-known-but-undecoded message shape from CAP-002-FINDINGS.md §2a (2026-08-12), not a newly-found packet type. §6's item on the message's 3rd entry resolved (index=3=Case); a new item added for CAP-011's specific staleness anomaly; the burst's irregular, BLE-churn-uncorrelated trigger interval remains open; one pre-existing item partially advanced (Group 0x0e, previously outside its listed group set) |
Claude (AI), maintainer-requested capture re-analysis |
| 2026-08-2x | CAP-009 (BATT-006), independently re-analyzed, then 5 findings reviewed and explicitly approved by the maintainer (AGENTS.md §6): §4.3 Option C — battchg confirmed 🟢 FACT a stale single snapshot; AT+BIEV confirmed 🟢 FACT per-earbud (Right, this session) rather than a fixed aggregate, revising the project's earlier aggregate assumption; push cadence corrected from "fixed ~6–7s" to "settling burst, then irregular" (also updates AGENTS.md §5's implementation guidance) — all recorded in ADR-015; BATT-006 closed. §4.3 Option E — two addenda added at 🟡 HYPOTHESIS (a live charge-cycle observation; the Case field's two distinct "unknown"-placeholder wire encodings) as part of the "fully purpose-built confirmation" Option E's own entry had called for. §4.3 Option B — DLCI 0x04's Group 0x03 Code 0x03 added as a 🟡 HYPOTHESIS candidate for the still-unconfirmed battery code (208 occurrences, Left/Right in near-lockstep with AT+BIEV/Option E outside the charging period). §4.3 Option A — a BLE Fast Pair scan added as a 🟡 HYPOTHESIS timing correlation for on-screen updates after HFP/Option E both close post-reconnect; device attribution not yet confirmed. See CAP-009-FINDINGS.md and CAP-009-EVENT-NOTES.md for the full independent re-analysis (its own video timeline, MAC re-derivation, filter-sanity/DLCI-inventory checks) behind all of the above |
Claude (AI), maintainer-directed sign-off session |
| 2026-08-27 | PROPOSAL, pending maintainer approval. CAP-014 (Group W repeat, snaplen-fixed) analyzed: §4.3 Option D and the 0x0c0X/0x0f2X open item annotated, no status change — the handle↔UUID mapping remains 🔴 OPEN after a 3rd Group-W-labeled attempt, but the blocking cause is now precisely identified as GATT-cache reuse on an already-bonded phone (not a snaplen issue this time, which this session's own check confirmed fixed) — see CAP-014-FINDINGS.md §4/§8 for the full analysis and the recommended next capture (genuinely combining a fixed snaplen with one of Group W's own untried cache-busting methods, pm clear com.android.bluetooth or the Pixel 9a). Byte-length/leading-byte shapes for 0x0c0c/0x0c13/0x0c14 and content for 0x0f2a ("Revision 6")/0x0f32 (0x64) reproduce exactly across independent sessions, strengthening confidence without changing any status |
Claude (AI), capture-analysis task, not yet reviewed by maintainer |
| 2026-08-30 | Four pending FACT promotions from a combined Tier 0 (capture re-decode) / Tier 2 (APK static-analysis) session reviewed and explicitly approved by the maintainer, per-point (AGENTS.md §6), recorded in DECISIONS.md ADR-019: §2.2a — the "..." inside DLCI 0x02's field5{field4{...}} wrapper confirmed 🟢 FACT to be libmaestro's own recovered WriteSetting schema (qhr), for 2 sampled fields (4, 29), via independent APK static analysis. §4.5.3 — the top-level "Use touch controls" toggle opcode (field 4) and the press-and-hold action-selection opcode (field 7/qju, plus a corrected, one-level-deeper qik→qho nesting) both promoted to 🟢 FACT, each now backed by both wire+video correlation and a self-describing log message in the app's own code. The ANC-mode rotation-checklist opcode's field number (field 12/qht) promoted to 🟢 FACT; its equivalence to the app's own "ANC gesture loop" name explicitly not promoted — the maintainer reviewed this specific point and kept it at 🟡 HYPOTHESIS. See REVERSE_ENGINEERING.md's qjc/qja/qhr/qjo/qju/qjg/qht entries (2026-08-30 updates) and CAP-020-FINDINGS.md/CAP-021-FINDINGS.md's 2026-08-30 addenda for the full byte-level and code-level evidence |
Claude (AI), maintainer-directed per-point sign-off session |
| 2026-08-30 | Remediation from a 2026-08-30 project-wide documentation audit (maintainer-directed fixes, no new FACT promotion or ADR): §2.2a — added CAP-033 as a fourth independent, SDP-service-name-level corroboration of DLCI 0x02's "MAESTRO APP" channel-ownership finding. §2.3 — added a 2026-08-30 update recording CAP-033's SDP-browse naming of DLCI 0x08 ("GSND CONTROL"), DLCI 0x0a ("GSND AUDIO"), DLCI 0x06 ("DEBUG APP"), and DLCI 0x12 ("BTIS") — new leads, 🟡 HYPOTHESIS, explicitly not a resolution of DLCI 0x08's identity. §6 — added a matching dated update to the DLCI-0x08-ownership open item |
Claude (AI), audit-remediation task, maintainer-directed |
| 2026-09-03 | §4.2 EQ — FrameEncoder/FrameDecoder implementation explicitly unblocked (DECISIONS.md ADR-020, maintainer-directed, closing a gap a 2026-09-02 documentation audit found: EQ's protocol knowledge was already fully FACT per ADR-016, but no ADR had ever explicitly cleared ARCHITECTURE.md §5's implementation gate for it, unlike ANC/ADR-009 and Find My Buds/ADR-011). No new protocol knowledge; field-16-vs-18 and gain-unit questions remain open |
Claude (AI), maintainer-directed sign-off session |
| 2026-09-03 | Remediation from a 2026-09-02 documentation audit (mechanical fixes, no new FACT promotion or ADR beyond ADR-020 above): §4.3 Option A — the "shown ≥8s, auto-hidden after 20s" Battery Notification visibility-timing claim downgraded from unqualified [OFFICIAL-SPEC] to 🟡 HYPOTHESIS after two direct re-fetches of the official batterynotification extension page found no matching text; the byte-layout table in the same section was re-confirmed exactly and is unaffected |
Claude (AI), audit-remediation task, maintainer-directed |
| 2026-09-04 | §4.1 "Get ANC state" (0x11) promoted to 🟢 FACT, in two steps, both maintainer-approved: opcode identity first (CAP-036, DECISIONS.md ADR-021 — the first-ever wire observation of this spec-documented opcode), then trigger-reliability (DECISIONS.md ADR-022 — 17 occurrences across 10 independent captures, zero misses). §0.1 — a documentation-gap fix backfilling DLCI 0x04 Device Information codes 0x01/0x02 (Model ID, rotating BLE address) that had reached FACT back on 2026-08-10 but were never copied in. §4.3 Option A/B — added a device-attribution advance (a DLCI 0x04 "BLE address updated" value tied to 407 live BLE advertisements in one session) and a cross-channel timing-synchronization extension to DLCI 0x02, both 🟡 HYPOTHESIS |
Claude (AI), capture-analysis + maintainer-directed sign-off sessions |
| 2026-09-05 | Two further maintainer-approved FACT promotions, each its own ADR: §4.3 Option C — HFP battery reporting (AT+BIEV) confirmed independent of GMS/the companion app and confirmed working on GrapheneOS itself (DECISIONS.md ADR-023, retroactive sign-off). §4.1 — the "Notify ANC state" Settable-toggles byte confirmed as a dock-state indicator (0x00 = both earbuds docked, 0xe8 otherwise), video-verified across 7 of 7 checked samples with zero counter-examples (DECISIONS.md ADR-024) |
Claude (AI), maintainer-directed sign-off sessions |
| 2026-09-06 | Synced with a 6-capture batch (CAP-037–CAP-042), the first purpose-built repeats of CAP-036's own questions. No new FACT promotions — this batch's own role was large-scale replication of already-FACT findings (CAP-037: 26/26 ADR-022 replications and 26/26 ADR-024 dock-state matches in one session; CAP-039: 10 same-session Set-vs-Get samples confirming ADR-024's trigger-independence) plus several new 🔴 open items added to §6: DLCI 0x08's 7 unmapped Get-shaped codes still unattributed (CAP-040, inconclusive — the session's own procedure left N=1 per code); the in-app "Connect"/"Disconnect" buttons producing zero wire signal (CAP-040); a chandle-level Settable-toggles anomaly with no preceding Get (CAP-037); a Settable-toggles=0x00 reading immediately after physical case-removal, in tension with ADR-024 (CAP-038, not resolved); two Get-less/Set-less ANC Notify frames (CAP-038); DLCI 0x02's connect-time burst shown length-invariant across 3 differing settings states, content-level diff still pending (CAP-041); and the periodic DLCI 0x02/0x04/0x08 push shown far sparser over a genuinely idle ~37-minute window than CAP-036's short sample suggested, with HFP dropping out of the cross-channel sync entirely (CAP-042) |
Claude (AI), capture-analysis task, not yet reviewed by maintainer |
| 2026-09-07 | Remediation from a project-wide audit + cross-validation cycle (AUDIT_REPORT_2026-09-07.md, ANTIGRAVITY_AUDIT_REPORT_2026-09-07.md, EXTERNAL_REVIEW_VALIDATION_2026-09-07.md — all three retired after processing, see CHANGELOG.md): backfilled this table's own 2026-09-04/05/06 gap (this row's three predecessors); §4.3 Option C corrected — HFP's own DLCI documented as a fixed 0x09 (CAP-001-only) but the clear majority of later captures (CAP-004/CAP-007/CAP-033/CAP-042) land it on 0x0c — now documented as session-local, matching how DLCI 0x02/0x04 are already treated; §6 — added qhr field 13 (a second, code-evidenced DLCI-0x02 ANC-state write path) as a named candidate to CAP-038-FINDINGS.md §5's open item, and added an app-foreground-vs-IPC refinement to CAP-042-FINDINGS.md §5's open item. No new FACT promotion; DECISIONS.md ADR-025 separately records that GMS reverse-engineering is out of scope, since no DLCI 0x04/0x08 transport code exists anywhere in the companion app's own decompiled source |
Claude (AI), audit-remediation task, maintainer-directed |
| 2026-09-08 | Implementing ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.md Phase 4's proposals, maintainer-approved via ai-sessions/0002_MAINTENANCE_PROMPT_2026_09_08.md (DECISIONS.md ADR-025's 2026-09-08 Update notes): §4.5.2 Multipoint (qhr field 11) and §4.5.6 Volume EQ (qhr field 15) promoted to 🟢 FACT for full field-number/semantic identity, each forward-traced from a named UI fragment/preference key to its write call site. §6 — added four items: an informational note on IFastPairDeviceDetailService/IFastPairFmdProxyService (how the reference app sources battery data and handles Find My Device consent, explicitly out of scope for this project's own implementation); a refinement to the Find My Buds Case/"both" open item (FmdWorker/ijp construct only ToS accept/skip requests, no ring/play-sound trigger found); a new open item on MaestroDeviceSettingsProviderService as a second UI entry point into the qhr/WriteSetting pipeline; a new open item on MaestroEndpointService's undetermined gRPC service registrations. Also closed the field 11/field 15 entry in the "what do DLCI 0x02's confirmed inner field numbers actually represent" open item |
Claude (AI), maintainer-directed sign-off session (prompt 0002) |
| 2026-09-08 | ai-sessions/0003_MAINTENANCE_PROMPT_2026_09_08.md Phase 2 — external-source validation pass, no new FACT promotion. §4.3 Option A — the "shown ≥8s, auto-hidden after 20s" timing claim re-checked against the base Message Stream spec page (the alternate location proposed 2026-09-03); also absent there, closing both candidate official pages with a clean negative. §4.4/§6 — the Ring ACK open item sharpened with the acknowledgement spec's exact literal text (the worked example's trailing 2 bytes are explicitly glossed as a channel+timeout state, "ring right and 60 seconds timeout"); checked against both observed ACK variants, neither fits a 2-byte state (one has zero extra bytes, the other exactly one) — the spec's own documented NAK format (a leading reason byte) was also checked and doesn't fit either. §6 — the DLCI 0x02 Address-field-renegotiation item cross-checked against Pigweed's public pw_hdlc/pw_rpc documentation directly: neither publishes how HDLC addresses or RPC channel IDs are assigned, so this remains genuinely undocumented upstream, not merely unread. §2.3 — pbpctrl's own published notes re-fetched in full; confirmed no detail exists beyond the already-quoted transport-framing paragraph and a bare feature list (no opcode/byte-layout detail for any setting). §6 — FE2C1238…'s name and the "Unknown Service" UUID re-checked against the live Fast Pair characteristics page and a web search respectively; both reconfirm the existing negative result (still undocumented) rather than finding anything new |
Claude (AI), external-validation task (HYPOTHESIS-level re-checks and negative-result confirmations only — no FACT promotion, no sign-off needed) |
| 2026-09-08 | ai-sessions/0003_MAINTENANCE_PROMPT_2026_09_08.md Phase 3/4 — APK reverse-engineering and capture cross-checks. §4.5.5 In-ear detection (qhr field 2) promoted to 🟢 FACT for field-number/category-level identity, maintainer-approved (DECISIONS.md ADR-019 Update): the field's existing write site is also reached from the system Settings app's MaestroDeviceSettingsProviderService (case 2102), logged there under the internal category name "CATEGORY_OHD" — the specific "In-ear detection" UI-label equivalence stays 🟡 HYPOTHESIS. §4.2 EQ — fyd.d/fyd.e's call sites traced: field 16 confirmed fed from the slider-drag/preset path; field 18 found reachable only via a dedicated "Save EQ" button click handler, directly contradicting (not confirming) CAP-015's own "fires on slider-release" wire-timing hypothesis — recorded as an open tension per the maintainer's own review, not resolved either way. §6 — MaestroDeviceSettingsProviderService's remaining 5 case IDs traced (field 27, field 11/Multipoint with a new internal-name confirmation and an unreconciled fpm.ENABLED_HEAD_GESTURES naming tension, field 5, a non-qhr "Feature A" toggle, and a new field 32); MaestroEndpointService.onCreate() read via apktool smali fallback (a Dagger-multibinding-based, per-call UID-authorization-gated service registry, service names not recovered); gjv.p()'s caller re-attempted and still not found (static analysis judged exhausted). CAP-041-FINDINGS.md §8 — full byte-for-byte content diff of the DLCI 0x02 connect-time burst across 4 settings states: content-level clean negative for a settings-state read-back, closing OBS-007 beyond the prior length-only result. §6 — a plausible (unconfirmed) structural match found between CAP-036's existing connect-time burst and qjb's qie-shaped nested structure; TrueWirelessHeadset.modelId confirmed to need the maintainer's own device access, no existing data found |
Claude (AI), APK-reverse-engineering + capture-analysis task; one item (qhr field 2) maintainer-directed sign-off, prompt 0003 |
| 2026-09-09 | ai-sessions/0004_MAINTENANCE_PROMPT_2026_09_09.md — connection-lifecycle analysis on existing captures. New §5.2: DLCI 0x02 (libmaestro) reliably opens last of the five data-carrying RFCOMM channels on a fresh reconnect — 6 independent instances across CAP-036/CAP-037/CAP-041, zero counter-examples in that condition, one honestly-scoped exception during a mid-session RFCOMM channel-bounce (where the order differs). Maintainer reviewed this directly in the chat session that authored this prompt and explicitly chose to keep it at 🟡 HYPOTHESIS (strong) rather than promote, pending more evidence or an explanation for the one exception |
Claude (AI), capture-re-analysis task; maintainer-reviewed, kept at HYPOTHESIS (not promoted), prompt 0004 |
| 2026-09-13 | ai-sessions/0013_FEATURE_PROMPT_2026_09_13.md — pending 0012 decisions resolved. §4.5.7 Volume Balance — the ±100 range and +100=Left/-100=Right polarity promoted to 🟢 FACT (DECISIONS.md ADR-026), maintainer-approved in the same chat session that authored this prompt; matching §6 open item closed. §4.4/§6 Behavior — Find My Buds Case/"both simultaneously" scope decision resolved (DECISIONS.md ADR-027): v1 ships with Left/Right ring only, Case/"both" out of scope absent a future local mechanism; see PROJECT.md's non-goals and TODO.md's Phase 1 item for the corresponding updates. Also this session: mechanical count corrections to CAP-036-FINDINGS.md/CAP-037-FINDINGS.md (not a PROTOCOL.md change), a bounded APK research pass (REVERSE_ENGINEERING.md's frb/fuh/glk/gjv entry and a new "GSND" naming-lead entry), and the first real, buildable/tested Android Studio project (android/, five Gradle modules, ANC FrameEncoder/FrameDecoder implemented and unit-tested against real capture fixtures) — none of which touch this document's own body |
Claude (AI), maintainer-directed sign-off session, prompt 0013 |
| 2026-09-13 | ai-sessions/0015_MAINTENANCE_PROMPT_2026_09_13.md — resolved ai-sessions/0010's two remaining sign-off items. §6 — the CAP-037/CAP-048 dock-timing anomaly's "(PROPOSAL, awaiting maintainer sign-off)" hedge removed; the explanation itself (a genuine real-time docking action, consistent with DECISIONS.md ADR-016) was maintainer-approved, citing ai-sessions/0015. No new FACT beyond ADR-016 was introduced — this only closes a previously-open citation. DECISIONS.md ADR-024 gained a dated Update recording CAP-048's two counter-example Settable-toggles readings as a documented, unreconciled 🟡 HYPOTHESIS exception, maintainer-approved |
Claude (AI), maintainer-directed sign-off session, prompt 0015 |
| 2026-09-13 | ai-sessions/0017_MAINTENANCE_PROMPT_2026_09_13.md — re-verification of 5 open items, no FACT/ADR promotion (all proposals pending maintainer sign-off). §4.2 EQ — the 2026-09-08 "field 18 reachable only via the Save button" trace corrected: a genuine second call path exists (hod.java, a "navigate away with unsaved changes" trigger), adding a third candidate alongside Save-button and the still-unconfirmed slider-release reading; a new capture (Group AO, CAP-053) is proposed to isolate all three. §6 serial-number candidate — CAP-036 frame 1423 re-traced field-by-field: its 3-string sub-message structurally matches qjm/qjr (GetHardwareInfo's oneof alternatives) exactly, not qie (GetSoftwareInfo's alternative, which is typed MESSAGE not STRING) as the existing entry read — reverses which RPC is the better structural candidate; a live correlation capture (Group AS, CAP-057) is proposed. §6 head-gesture item — CAP-028's clean negative re-verified across its entire log (not just the originally-checked window) plus HID/AVRCP/SCO checks, reproducing the same result; a correctly-scoped repeat with an active call/notification (Group AQ, CAP-055) is proposed. Also (not touching this document): TESTPLAN_BLUETOOTH_HCI_SNOOP.md's HOLD-005 row updated with a Group AR (CAP-056) re-run proposal (CAP-045 never opened the rotation-checklist screen), and PROTOCOL.md §4.3 Option A's Battery Notification item — CAP-043's non-match re-verified byte-for-byte, a single-bud-insertion/removal bracket proposed as Group AP (CAP-054). Full phase-by-phase detail and the complete pending-decision inventory: ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md |
Claude (AI), maintenance/re-verification task, not yet reviewed by maintainer |
| 2026-09-15 | ai-sessions/0022_CAPTURE_PROMPT_2026_09_15.md — CAP-047 (Group AL, CAP-021's DLCI 0x0a burst trigger, Trigger candidate 3 only), no FACT/ADR promotion (all proposals pending maintainer sign-off). §6 DLCI 0x0a item — a dated update recording a clean, complete negative for Trigger candidate 3 (charge-state change) across six independently bracketed dock/undock transitions in two full, untruncated logs; the burst's "1 of N sessions checked" denominator rises to at least twenty. §6 (new item, ADR-016 tension) — of three swapped-slot (mismatched L/R) dockings captured this session, two produce a genuine ACL disconnect matching DECISIONS.md ADR-016 exactly, but a third does not, despite an identical dock-sensor reading — flagged as an unreconciled 🔴 open question. §6 (extends the existing CAP-048-FINDINGS.md §5 item) — DECISIONS.md ADR-024's dock-state byte confirmed to read "both docked" during a swapped-slot seating (a previously-untested case, directly answered); two further stale-reading counter-examples found, one matching the already-proposed "settling" hypothesis and one that does not. A dense video re-check (1fps + 4fps, cross-validated against the phone's own per-earbud charging-icon indicator) also confirmed, contrary to a maintainer recollection, that no corrected (matching-slot) docking occurred in either of this capture's two recordings. See CAP-047-FINDINGS.md for the full command+hex evidence and proposed downstream updates |
Claude (AI), capture-analysis task, not yet reviewed by maintainer |
https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/PROTOCOL.md - https://tedsluis.github.io/opencontrolpixelbudspro2/PROTOCOL