ppk2-python
Python library and CLI for the Nordic Power Profiler Kit II (PPK2).
Features
- Device control — source/ampere meter, voltage, DUT power, 100kHz measurement
- File I/O — save/load
.ppk2files (nRF Connect Power Profiler compatible) — see file format - Daemon server — keeps the PPK2 open so multiple consumers can share one device. Required for source meter workflows on macOS — see daemon guide
- Event annotation — overlay DUT firmware trace events (Chrome JSON scopes) onto PPK2 digital channels D0–D7 for visual correlation against the power trace
- Reporting — markdown tables, interactive HTML charts, GitHub Actions annotations
- Synthetic profiles — build realistic profiles programmatically with a phase/ramp builder
- AI integration — generate, analyze, and validate profiles using Claude — see AI integration
- Desktop automation — open
.ppk2files in nRF Connect via Playwright — see desktop - GitHub Action — power profiling reports in CI workflows — see GitHub Action
- Firmware management — query, check against upstream, and flash PPK2 firmware over USB — see firmware
Status
Live example reports are regenerated on every build.
Installation
pip install ppk2-python # core library
pip install ppk2-python[report] # + plotly HTML charts
pip install ppk2-python[desktop] # + Playwright for nRF Connect automation
pip install ppk2-python[ai] # + Anthropic SDK for Claude integration
pip install ppk2-python[all] # everything
Quick Start
List connected devices
ppk2 list
Take a measurement (ampere meter mode — DUT has its own supply)
A one-shot CLI command works fine in ampere meter mode because the PPK2 only measures; nothing about closing the serial port disturbs the DUT.
ppk2 measure 5.0 -o capture.ppk2
ppk2 info capture.ppk2
ppk2 report capture.ppk2 --html report.html
Or from Python:
from ppk2 import PPK2Device, save_ppk2
with PPK2Device.open() as ppk:
result = ppk.measure(duration_s=5.0)
print(f"Mean: {result.mean_ua:.1f} uA")
save_ppk2(result, "capture.ppk2")
Source meter mode — use the daemon
In source meter mode the PPK2 itself supplies the DUT rail, and closing the
serial port causes the rail to collapse (DTR drop on close → DUT power off).
A sequence of one-shot CLI calls — voltage, power on, measure — would
reset the DUT between every command. Hold the port open with the daemon
instead:
from pathlib import Path
from ppk2.daemon import start_daemon
from ppk2.client import DaemonClient
serial, _ = start_daemon() # auto-detects a single PPK2
sock = Path(f"~/.local/state/ppk2/{serial}.sock").expanduser()
with DaemonClient(sock, serial) as client:
client.use_source_meter()
client.set_source_voltage(3700)
client.toggle_dut_power(True)
result = client.measure(duration_s=5.0)
print(f"Mean: {result.mean_ua:.1f} uA")
DaemonClient exposes the same surface as PPK2Device, so application code
can switch between direct and daemon-backed access without other changes.
Multiple PPK2s
Daemons are keyed by serial number. Spin one up per device, then address each by serial:
from ppk2.transport import list_ppk2_devices
from ppk2.daemon import start_daemon
from ppk2.client import connect_to_daemon
for dev in list_ppk2_devices():
start_daemon(port=dev.port)
with connect_to_daemon(serial="C9F6358A") as a, \
connect_to_daemon(serial="AB123456") as b:
print(a.measure(2.0).mean_ua, b.measure(2.0).mean_ua)
See the daemon guide for the full lifecycle (startup, shutdown, multi-device state directory, and the underlying limitations).
Load and inspect a .ppk2 file
from ppk2 import load_ppk2
result = load_ppk2("capture.ppk2")
print(f"Samples: {result.sample_count:,}")
print(f"Mean: {result.mean_ua:.1f} uA, Peak: {result.max_ua:.1f} uA")
Build a synthetic profile
from ppk2 import ProfileBuilder, save_ppk2
profile = (
ProfileBuilder(seed=42)
.phase("deep_sleep", current_ua=3.5, duration_s=5.0, noise_ua=0.5)
.ramp("wakeup", start_ua=3.5, end_ua=15_000, duration_s=0.05)
.phase("radio_tx", current_ua=45_000, duration_s=0.2, noise_ua=2000)
.ramp("shutdown", start_ua=45_000, end_ua=3.5, duration_s=0.01)
.phase("deep_sleep", current_ua=3.5, duration_s=5.0, noise_ua=0.5)
.build()
)
save_ppk2(profile, "synthetic.ppk2")
Annotate a capture with DUT trace events
If your firmware emits Chrome JSON scope events over a serial link
({"ph":"B"/"E","ts":...,"name":"..."}), overlay them as D0–D7 logic
channels on the PPK2 capture:
from ppk2.events import parse_serial_events
from ppk2.ppk2file import save_ppk2
mapper = parse_serial_events(dut_serial_output, {"gps": 0, "radio": 1})
mapper.apply(measurement)
save_ppk2(measurement, "annotated.ppk2",
events=mapper.to_scopes(measurement))
The HTML report renders embedded scopes as a "Named scopes" table without
needing a sidecar legend. Events outside the capture window are clamped and
a warning is logged. See
examples/capture_with_events.py for a
runnable walkthrough including the daemon-client / serial-reader pattern
for real hardware.
CLI
ppk2 list List connected PPK2 devices
ppk2 power on|off [--port ...] Toggle DUT power
ppk2 mode source|ampere [--port ...] Set measurement mode
ppk2 voltage 3300 [--port ...] Set source voltage (mV)
ppk2 measure 5.0 [-o capture.ppk2] Measure for N seconds
ppk2 info <file.ppk2> Show file statistics
ppk2 report <files...> --html report.html Generate reports
ppk2 open <file.ppk2> Open in nRF Connect
ppk2 generate "<description>" -o out.ppk2 Generate from text (requires AI)
ppk2 analyze <file.ppk2> Analyze with Claude (requires AI)
ppk2 validate <file.ppk2> --spec "<spec>" Validate against spec (requires AI)
ppk2 merge <trace.json> <capture.ppk2> Merge Chrome trace with PPK2 power
ppk2 firmware [info|check|upgrade|...] Query or upgrade PPK2 firmware
The single-command CLI shapes (ppk2 power on, ppk2 voltage 3300, etc.)
work directly in ampere meter mode. In source meter mode, drive the PPK2
through the daemon so the rail isn't reset between
commands.
A few worked examples
# HTML report with pass/fail thresholds
ppk2 report deep_sleep.ppk2 gps_fix.ppk2 \
--thresholds '{"deep_sleep": 10, "gps_fix": 50000}' \
--html report.html
# Generate a synthetic profile from natural language
ppk2 generate "BLE beacon: 3uA deep sleep, wakes every 2s to TX at 8mA for 5ms" \
-o beacon.ppk2
# Open in nRF Connect for visual comparison
ppk2 open beacon.ppk2
# Validate against a specification
ppk2 validate recording.ppk2 \
--spec "Deep sleep should be 3-5uA. GPS acquisition under 50mA for max 60s."
Development
git clone https://github.com/m-mcgowan/ppk2-python.git
cd ppk2-python
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v
Metadata
Release files for ppk2-python 0.1.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 | Actions |
|---|---|---|---|---|
| ppk2_python-0.1.0.tar.gz | 155.1 kB | With provenance | Details for ppk2_python-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ppk2_python-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for ppk2_python-0.1.0-py3-none-any.whl |
Total release size: 243.6 kB
Release files / ppk2_python-0.1.0.tar.gz
| Download URL | ppk2_python-0.1.0.tar.gz |
|---|---|
| Size | 155.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d267cc6f3bda1e86794b820ecfa8218571ed9944e71eebef45bbf4f6a5c6311b
|
|
BLAKE2b-256 checksum How to use checksums |
3ac28d4df855794263c55f91c7eb1c989cbc8c8ee9924d7a0a4f268ea9ccc5b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logRelease files / ppk2_python-0.1.0-py3-none-any.whl
| Download URL | ppk2_python-0.1.0-py3-none-any.whl |
|---|---|
| Size | 88.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4c57f34b0f3d9fe36af9e48213a268d2da38fda302ab1ac84e02c77edbc4944
|
|
BLAKE2b-256 checksum How to use checksums |
d549b12012da9eea4a3bba443f4c42d1371ef80b0986113dcfced11f9908b48b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logSecurity for ppk2-python 0.1.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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for ppk2_python-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 10, 2026.
Transparency logA 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.