Skip to content

OpenDisplay/nrf-ota

Repository files navigation

Tests PyPI Python Version

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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages