btsnoop-parser
btsnoop-parser is a small library and CLI for exploring Bluetooth HCI captures: Android
btsnoop_hci.log files and Linux BlueZ captures written with btmon -w. It is a lightweight alternative to Wireshark
when you need a quick look at packet metadata or want to script over captures in Python.
$ btsnoop_parser capture.log --stats
Capture Statistics ───
Duration: 4m 32.871s
Total Packets: 1,842
Data Volume: 142.67 KB
Packet Types:
ACL Data 1,204
Event 512
Command 126
Detected Devices:
AA:BB:CC:DD:EE:FF Unknown
11:22:33:44:55:66 Unknown
Connection History:
2024-06-01 09:12:03.441 Connected (LE) 0x001 -> Device: AA:BB:CC:DD:EE:FF
2024-06-01 09:14:21.009 Disconnected 0x001 -> Reason: Remote User Terminated Connection (Remote Device)
2024-06-01 09:15:44.230 Connect Failed (LE) 0x002 -> Device: 11:22:33:44:55:66 — Page Timeout
Potential Issues (1):
[WARN] 2024-06-01 09:15:44.230 - LE Connection Failed: Failed to connect to 11:22:33:44:55:66: Page Timeout
$ btsnoop_parser capture.log --ai
Loading Qwen/Qwen2.5-0.5B-Instruct (this may take a moment)...
Yes, something failed.
(1) What failed: the LE connection attempt to 11:22:33:44:55:66 (handle 0x002).
(2) When: at 09:15:44, after an earlier connection to AA:BB:CC:DD:EE:FF had ended normally.
(3) Likely root cause: Page Timeout — the other device never answered the connection request — it was most likely out of range, switched off, or not accepting connections.
That's unedited output for the capture above, from a 0.5B model fine-tuned for this job.
It runs on a laptop CPU in a few seconds. It is still a small model working from a summary,
so treat --ai as a plain-English starting point and check it against --stats.
Features
- Parses BTSnoop HCI logs into friendly Python dicts — zero dependencies. Reads Android snoop logs and Linux/Raspberry Pi captures from
btmon -w capture.log. - Wireshark-style packet list: each answer names its command and result, failures in red, and data packets say what they carry (e.g. "AVRCP: Next pressed", "HFP: AT+CLCC").
--filter— filter by packet type and direction before processing.--pcap— export to PCAP (link type 201) for Wireshark / tshark.--interactive— load a capture once and ask questions: "is there a disconnect around 21:04?", "who ended it?", "any Next presses between 21:00 and 21:02?", "why did the car not reconnect?". Event questions are answered exactly from the decoded log; "why" questions go to the local model.--report— the full session report: the phone (name, address, Bluetooth version, chip), each device (name, type, Bluetooth version, chip, signal), each connection (who started it, how long connecting took, how and why it ended, pairing and encryption, LE link settings, power saving, data sent), every profile in use, and the problems found. Add--jsonfor machine-readable output.--stats— connection history, device list, issue detection, and a per-device profile summary: music played and for how long, codec, buttons pressed, songs, volume changes (including a device overriding the phone), calls and cellular service, LE Audio streams, Android Auto start-up.--protocols— timeline of what the devices said above HCI: L2CAP channels, A2DP streaming (codec, start, pause, stalls), AVRCP buttons, volume and track changes, wireless Android Auto start-up, phonebook and message sync activity (counts and timings, never contents), GATT (services, device name, battery, heart rate), LE Audio stream set-up (codec, link quality, what each stream is for, failures), volume, media and call control, RFCOMM serial ports, HFP commands in plain English, voice links. Works on filtered Android logs too.--ai— a small local model, fine-tuned on HCI sessions, says in plain English whether anything failed and why (runs offline, optional extra).--link-keys— extract Classic BT link keys seen in HCI traffic (for use with your own authorized captures).- Decodes common HCI command/event payloads.
- Corrects the Android ±378-day timestamp bug automatically.
Installation
pip install btsnoop-parser
The --ai flag is an optional, heavier extra (pulls in torch/transformers —
several GB) — the rest of the library stays dependency-free:
pip install "btsnoop-parser[ai]"
CLI Usage
# Wireshark-style table, first 20 packets
btsnoop_parser capture.log --limit 20
# Show only HCI events
btsnoop_parser capture.log --filter type:event
# Show only TX commands
btsnoop_parser capture.log --filter type:command --filter dir:tx
# Export filtered records to a PCAP file — open directly in Wireshark
btsnoop_parser capture.log --filter type:event --pcap events.pcap
# Convert the whole capture to PCAP
btsnoop_parser capture.log --pcap full.pcap
# Ask questions about a capture (type help inside for examples)
btsnoop_parser capture.log --interactive
# Everything the capture says about the session, in one report
btsnoop_parser capture.log --report
btsnoop_parser capture.log --report --json > session.json
# Capture statistics and issue detection
btsnoop_parser capture.log --stats
# Decode the protocols above HCI (L2CAP channels, A2DP streaming, RFCOMM ports, HFP commands)
btsnoop_parser capture.log --protocols
# Ask a local LLM to diagnose issues — requires `pip install "btsnoop-parser[ai]"`
# Only the decoded/summarized capture is sent to the model (not raw packets),
# and everything runs locally: no cloud calls, only a one-time model download.
btsnoop_parser capture.log --ai
btsnoop_parser capture.log --ai --question "Why did the connection drop at 09:15?"
# Use your own model or LoRA adapter instead of the default (see training/)
btsnoop_parser capture.log --ai --base-model Qwen/Qwen2.5-1.5B-Instruct
btsnoop_parser capture.log --ai --adapter-path training/checkpoints/Qwen2.5-0.5B-Instruct-lora
# Extract Classic BT link keys seen in the capture (your own authorized captures only)
btsnoop_parser capture.log --link-keys
# JSON output for scripting
btsnoop_parser capture.log --json | jq '[.[] | select(.direction=="RX")]'
Run btsnoop_parser --help for the full option list.
--filter expressions
| Key | Values | Example |
|---|---|---|
type |
command, acl, event, sco, iso, 0xNN |
--filter type:event |
dir |
tx, rx |
--filter dir:tx |
Comma-separate types for OR logic: --filter type:command,event
Repeat the flag to AND filters: --filter type:event --filter dir:rx
Python API
from btsnoop_parser import (
parse_btsnoop_file,
iter_records,
filter_records,
write_pcap,
decode_hci_packet,
)
# Load all records
records = parse_btsnoop_file("btsnoop_hci.log")
# Filter to HCI events only
events = filter_records(records, ["type:event"])
# Export to Wireshark-compatible PCAP
write_pcap(records, "capture.pcap")
# Stream large files without loading everything into memory
for record in iter_records("btsnoop_hci.log"):
decoded = decode_hci_packet(record["packet_type"], record["payload"])
if decoded.get("type") == "COMMAND":
print(record["timestamp"], decoded["name"])
Full API reference: btsnoop-parser.readthedocs.io
Local LLM analysis (--ai)
--ai gives a local model a plain-text summary of the capture (connection history,
detected issues, routine failures, a short note on each status code involved, and what
each device did: music, buttons and volume, calls, LE Audio, sync, Android Auto) and asks
whether anything actually failed, and if so what, when and why. When several things went
wrong it leads with the one that stops the device working and lists the rest. The model
never sees raw packets, and nothing leaves your machine.
The default is Qwen/Qwen2.5-0.5B-Instruct
with the kdonut/btsnoop-hci-qwen2.5-0.5b-lora
adapter, trained on synthetic HCI sessions with the scripts in training/.
On 254 held-out sessions it gets 244 right (every single-problem and normal session, and
30 of 40 sessions with two to four problems at once); the same model without the adapter
gets 15. Those sessions are synthetic, so expect lower accuracy on real captures,
and check details against --report.
--base-modeltakes a Hugging Face model id or a local model folder;BTSNOOP_AI_MODELchanges the default. The default adapter is only applied to the default model.--adapter-pathtakes a local adapter folder or a Hugging Face repo id.- Models are downloaded once (about 1 GB) and cached.
HF_HUB_OFFLINE=1skips the network check on later runs.
Link key extraction (--link-keys)
Classic Bluetooth (BR/EDR) link keys pass over HCI in the clear — the
controller hands a freshly-paired key to the host via a Link Key
Notification event, and the host replays a cached key back via a Link Key
Request Reply command on every reconnection. --link-keys scans a capture
for both and prints the device address, key, and (when known) how the key
was derived — useful for e.g. loading a key into Wireshark to decrypt your
own encrypted captures.
A link key is credential material for that device — only run this against captures you're authorized to analyze. BLE isn't covered (it negotiates an LTK through a different mechanism).
Development
pip install -e ".[dev]"
pytest
ruff check
License
MIT © Kranthi
Contributing
Please read CONTRIBUTING.md for guidelines on setting up your environment, running tests, and submitting pull requests.
Metadata
Release files for btsnoop-parser 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| btsnoop_parser-0.9.0.tar.gz | 126.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| btsnoop_parser-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 232.0 kB
Release files / btsnoop_parser-0.9.0.tar.gz
| Download URL | btsnoop_parser-0.9.0.tar.gz |
|---|---|
| Size | 126.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0e7cc9f5659e3777d2312726605ccc7656ecba302a5ad7574baac846c8c1e9e7
|
|
BLAKE2b-256 checksum How to use checksums |
affb78162ec658796bf7a45213c8db9a8225d670510093c4c0e34142c8310fa9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Release files / btsnoop_parser-0.9.0-py3-none-any.whl
| Download URL | btsnoop_parser-0.9.0-py3-none-any.whl |
|---|---|
| Size | 105.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
93397a61cd3f9fd922b2c13504ac5027c425b7877276c46b38239c25e5853595
|
|
BLAKE2b-256 checksum How to use checksums |
1ef786269f1143b97236df6134616e4cdd6d15cd1fd7fe640c43d421009013a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.2
|
Security for btsnoop-parser 0.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.