nrf-ota
Flash firmware to Nordic nRF5x devices over BLE from Python. Implements the Nordic Legacy DFU protocol (nRF5 SDK ≤ 15.x) and works on Linux, macOS, and Windows.
Installation
pip install nrf-ota
CLI
Run using uvx:
uvx nrf-ota firmware.zip # interactive device picker
uvx nrf-ota https://example.com/firmware.zip # download then flash
uvx nrf-ota firmware.zip --device OD216205 # select by name
uvx nrf-ota firmware.zip --device FC:06:1C:C8:DE:47 # select by address
Accepts a local ZIP path or an HTTP(S) URL. Scans for nearby BLE devices, lets you pick one, and flashes the firmware. If the device is running application firmware the bootloader is triggered automatically.
Library
import asyncio
from nrf_ota import perform_dfu, scan_for_devices
async def main():
devices = await scan_for_devices(timeout=5.0)
# local file
await perform_dfu("firmware.zip", devices[0], on_progress=lambda pct: print(f"\r{pct:.0f}%", end=""))
# or a URL — downloads and flashes in one call
await perform_dfu("https://example.com/firmware.zip", devices[0])
asyncio.run(main())
API
perform_dfu(zip_path, device, *, on_progress=None, on_log=None, packets_per_notification=...)
Performs a full OTA update, triggers the bootloader if needed, waits for the device to reboot into DFU mode, transfers the firmware, and activates it.
| Parameter | Type | Description |
|---|---|---|
zip_path |
str | DFUZipInfo |
Local path, HTTP(S) URL, or pre-parsed DFUZipInfo |
device |
BLEDevice | str |
Device from scan_for_devices, or a raw Bluetooth address |
on_progress |
Callable[[float], None] |
Called with percentage (0–100) as firmware is sent |
on_log |
Callable[[str], None] |
Called with status messages |
packets_per_notification |
int |
Packets sent per receipt notification. Default: 8 on macOS, 10 elsewhere. |
Raises DFUError on failure, DeviceNotFoundError if the bootloader can't be found after reboot.
DFUZipInfo
Named tuple returned by parse_dfu_zip. Can be passed directly to perform_dfu to skip re-parsing:
from nrf_ota import perform_dfu, parse_dfu_zip
info = parse_dfu_zip("firmware.zip")
print(f"{info.bin_file} {len(info.firmware):,} bytes")
await perform_dfu(info, device)
scan_for_devices(timeout=5.0) -> list[BLEDevice]
Scans for nearby named BLE devices and returns a list of bleak.BLEDevice objects.
Exceptions
| Exception | Description |
|---|---|
DFUError |
Base exception for all DFU failures |
DeviceNotFoundError |
Bootloader not found after reboot |
Platform notes
Works on Linux, macOS, and Windows via bleak. On macOS, the default packets_per_notification is lowered to 8 (from 10) to stay within CoreBluetooth's write-without-response flow control limits.
Development
git clone https://github.com/OpenDisplay-org/nrf-ota.git
cd nrf-ota
uv sync --all-extras
uv run pytest tests/ -v
uv run ruff check .
uv run mypy src/nrf_ota
Metadata
Release files for nrf-ota 0.4.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 | |
|---|---|---|---|
| nrf_ota-0.4.0.tar.gz | 85.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nrf_ota-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 108.2 kB
Release files / nrf_ota-0.4.0.tar.gz
| Download URL | nrf_ota-0.4.0.tar.gz |
|---|---|
| Size | 85.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4e645a1db9df600020ec4127e0dbfde79b684b431f7ccd71dce270f5a4f3f6a9
|
|
BLAKE2b-256 checksum How to use checksums |
986625b62e03883c589f13fe3c3793929e08178202f3d2d8e581a22c66e5afff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 2, 2026.
Transparency logRelease files / nrf_ota-0.4.0-py3-none-any.whl
| Download URL | nrf_ota-0.4.0-py3-none-any.whl |
|---|---|
| Size | 23.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
073613c7a1d58b2bf7866b13d6ccf2c435128f7e585dbc18d7d44f9939cd4972
|
|
BLAKE2b-256 checksum How to use checksums |
c7e4ae471c72f2b3dc8e219002478bf90434e6f4e8ebb23d0e4513b1f7fbff99
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Jun 2, 2026.
Transparency log