Reverse engineering the ChargerLAB POWER-Z KM003C USB-C power analyzer protocol.
docs/protocol_reference.md- Capture-backed reference for the verified KM003C protocol.docs/features/pd_analysis.md- USB PD capture and official SQLite export format.
- Rust-based pcapng to Parquet converter
- Extracts 41 USB protocol fields using tshark
- Handles multiple devices and capture sessions
data/processed/usb_master_dataset.parquet - 20,862 USB packets from 14 capture files, containing 8 distinct USB device-address values.
km003c_analysis/- Python library for reusable USB transaction processing and GUIscripts/- Analysis and data export scripts for research workflowsnotebooks/- Jupyter notebooks for manual protocol explorationrust_pcap_converter/- PCAP processing tool
For device access and USB packet capture on Linux, install the provided udev rules:
# Device access (required for communicating with KM003C)
sudo cp 71-powerz-km003c.rules /etc/udev/rules.d/
# USB monitoring (required for capturing USB traffic with Wireshark)
sudo cp 99-usbmon.rules /etc/udev/rules.d/
# Reload rules
sudo udevadm control --reload-rules
sudo udevadm triggerDevice access (71-powerz-km003c.rules): Uses the modern uaccess tag approach for secure, dynamic access to logged-in users. See the Arch Wiki on udev for details.
USB monitoring (99-usbmon.rules): Grants the wireshark group access to usbmon devices for USB packet capture. Add your user to the wireshark group: sudo usermod -aG wireshark $USER (logout/login required).
uv syncuv sync installs the Rust-backed km003c Python package from the pinned
km003c-rs release in uv.lock.
./rust_pcap_converter/target/debug/pcap_to_parquet --input capture.pcapngfrom km003c_analysis import split_usb_transactions, tag_transactions
import polars as pl
df = pl.read_parquet("data/processed/usb_master_dataset.parquet")
df_with_transactions = split_usb_transactions(df)
df_tagged = tag_transactions(df_with_transactions)# Comprehensive SQLite PD export analyzer
uv run --locked python -m km003c_analysis.tools.pd_sqlite_analyzer --verbose
# Export PD analysis to JSON/Parquet
uv run --locked python -m km003c_analysis.tools.pd_sqlite_analyzer --export-json results.json
uv run --locked python -m km003c_analysis.tools.pd_sqlite_analyzer --export-parquet messages.parquet# Regenerate the KM003C protocol analysis
uv run --locked python scripts/parquet/analyze_km003c_protocol.py
# Export PD messages to Parquet
uv run --locked python scripts/parquet/export_pd_messages.py
# Wrapped PD format parsing
uv run --locked python scripts/parquet/parse_pd_wrapped.pyjust app # Launch Streamlit analyzer- Use the Rust-backed
km003cpackage for protocol parsing in scripts and tools. Preferparse_packet()for semantic payloads (ADC, AdcQueue, PD status/events) andparse_raw_packet()for low-level headers and logical packets when needed. - Control header layout (wire):
type7 bits,reserved_flag1 bit,id8 bits, 1 unnamed bit, thenatt15 bits. - The 15-bit
attfield holds attribute values directly (no shifting). Examples:0x0001ADC,0x0002AdcQueue,0x0008Settings,0x0010PdPacket. - Byte layout gotcha (for humans/agents reading hex): the
attfield starts at bit 17 of the 32-bit little-endian header. Foratt=0x0001(ADC), the third byte often appears as0x02— this is correct and does not mean0x0002(AdcQueue). Always parse via the 32-bit little-endian bitfield or usekm003c. reserved_flagis vendor-specific and is not an indicator of extended headers. PutData (0x41) responses use extended logical-packet headers regardless of this bit.- Extended header (per logical packet):
att:15 | next:1 | chunk:6 | size:10.
Recommended usage:
from km003c import parse_packet, parse_raw_packet
from scripts.km003c_helpers import get_packet_type, get_adc_data, get_pd_status, get_pd_events
pkt = parse_packet(packet_bytes)
if get_packet_type(pkt) == "DataResponse":
adc = get_adc_data(pkt)
pdst = get_pd_status(pkt)
pdev = get_pd_events(pkt)
raw = parse_raw_packet(packet_bytes)
if isinstance(raw, dict) and "Data" in raw:
hdr = raw["Data"]["header"]
for lp in raw["Data"]["logical_packets"]:
attr = lp.get("attribute") # e.g. 1, 2, 8, 16Important: Avoid manual bit/byte parsing for KM003C headers in Python. Agents and scripts should use the Rust parser to prevent off-by-one mistakes in attribute masks and misinterpretation of reserved_flag.
- USB VID: 0x5FC9, PID: 0x0063
- Bulk transfer endpoints: 0x01 (OUT), 0x81 (IN)
- Application layer protocol with extended headers
- Dual ADC measurement and Power Delivery analysis modes
- USB analysis scopes each capture separately and pairs pending Submit/Complete URBs chronologically
- km003c-rs - Rust implementation
- Linux kernel driver
- Community implementations