Minimal reference firmware for Nordic nRF52 devices that demonstrates the Hubble protocol running on an nRF52840. This repo contains a tiny example application that constructs and emits Hubble BLE frames and shows the integration points required to onboard a device to Hubble.
- Developers who want the simplest working example of broadcasting Hubble frames from Nordic nRF52 devices.
- Engineers who need a reference illustrating how to integrate Hubble code into an nRF5 SDK + SoftDevice workflow.
NOTE! If you plan to start a new product today, note that Nordic recommends the nRF Connect SDK / SoftDevice Controller for new designs. This repo is intended as a concise SoftDevice (nRF5 SDK) reference.
Before starting, you must download gcc-arm-none-eabi-10.3-2021.10 and locate the instalation path to pass to GNU_INSTALL_ROOT. Other revisions may work but have not been tested.
# 1) Clone repo (with submodules)
git clone --recurse-submodules git@github.com:HubbleNetwork/hubble-reference-nordic-softdevice.git
cd hubble-reference-nordic-softdevice/app
# 2) Build (example: set toolchain root/version and your base64 KEY)
make GNU_INSTALL_ROOT=/opt/gcc-arm-none-eabi-10.3-2021.10/bin/ GNU_VERSION=10.3 KEY=<BASE64_KEY>
# 3) Flash SoftDevice (only required once per device / device family)
make GNU_INSTALL_ROOT=/opt/gcc-arm-none-eabi-10.3-2021.10/bin/ GNU_VERSION=10.3 flash_softdevice
# 4) Flash application
make GNU_INSTALL_ROOT=/opt/gcc-arm-none-eabi-10.3-2021.10/bin/ GNU_VERSION=10.3 KEY=<BASE64_KEY> flash
# 5) Verify with Python (optional)
pipx install pyhubblenetwork # or: pip3 install --user pyhubblenetwork
hubblenetwork ble scan -k <BASE64_KEY>- GCC ARM toolchain (e.g., gcc-arm-none-eabi-10.3-2021.10) If your toolchain is installed at the nRF SDK default path you may omit GNU_INSTALL_ROOT/GNU_VERSION. The project was built/tested with 10.3 — your mileage may vary. README
- Segger / Nordic Command Line Tools (
nrfjprog) for flashing the device. nRF5 SDK (compatible version used during development). If you vendor the SDK locally, configure your paths accordingly. - A SoftDevice HEX compatible with your SoC (e.g., S132 for nRF52832, S140 for nRF52840).
- Python 3 and
pyhubblenetwork(for quick verification). - A Hubble device key (base64) obtained from the Hubble console — this is passed as KEY to the Makefile. The existing README expects a base64 key; preserve that format.
From the app directory:
cd app
make GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=<GNU_VERSION> KEY=<BASE64_KEY> [BOARD=<BOARD>]GNU_INSTALL_ROOT: path to the gcc-arm-none-eabi binaries (optional if installed in SDK default).GNU_VERSION: the GCC toolchain version string (e.g., 10.3).KEY: your base64-encoded Hubble device key obtained when registering your device with Hubble.BOARD: target board (optional, defaults topca10056).HUBBLE_CRYPTO: crypto backend (optional). One ofcc310,nrf,oberon,mbedtls. Defaults tocc310onpca10056andmbedtlselsewhere. See Crypto backend below.NRF_CRYPTO_BACKEND: backend used whenHUBBLE_CRYPTO=nrf(optional). One ofcc310,mbedtls. Defaults tocc310onpca10056andmbedtlselsewhere.
BOARD |
Kit | SoC | SoftDevice | Build output |
|---|---|---|---|---|
pca10056 (default) |
nRF52840-DK | nRF52840 | S140 7.2.0 | _build/nrf52840_xxaa.hex |
pca10040 |
nRF52-DK | nRF52832 | S132 7.2.0 | _build/nrf52832_xxaa.hex |
BOARD selects the SoC and board defines, the MDK startup/system files, the
SoftDevice headers and hex, and the linker script. It must be passed to every
make invocation for a given board, including flash and
flash_softdevice — otherwise you will flash the wrong SoftDevice. Example
for the nRF52-DK:
make BOARD=pca10040 GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=10.3 KEY=<BASE64_KEY>
make BOARD=pca10040 GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=10.3 flash_softdevice
make BOARD=pca10040 GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=10.3 KEY=<BASE64_KEY> flashExample:
make GNU_INSTALL_ROOT=/Applications/ARM/bin/ GNU_VERSION=10.3 KEY=7HLguuPSA5Y2thEfqfCwnKVELdeR+g7sGWGssI3WO0w=app/hubble_app_port.c implements the system abstraction the SDK expects from
the application (hubble/port/sys.h):
-
hubble_uptime_get()— monotonic milliseconds, built onapp_timerwith RTC rollover tracking. -
hubble_log()— forwards toNRF_LOG. -
hubble_lock_init()/hubble_lock()/hubble_unlock()— required since SDK v3.0.0.hubble_ble_advertise_get()allocates the sequence number and validates the nonce under this lock, and both the main thread and theapp_timerhandler reach that path in this app.The implementation uses a SoftDevice critical region (
app_util_critical_region_enter/exit) rather than a mutex: it cannot deadlock when taken from the timer interrupt, and the only state it guards — the SDK's default RAM sequence counter — never blocks. If you replacehubble_sequence_counter_get()with one that can block (persisting the counter to flash, say), switch to a blocking-capable recursive mutex assys.hrequires. Note thatCRITICAL_REGION_ENTER/EXITcannot be used directly here: underSOFTDEVICE_PRESENTthey are brace-scoped macros that keep the nesting flag in a local, so they cannot be split across two functions.
hubble_sequence_counter_get() is left to the SDK default (a RAM counter), so
the sequence restarts at 0 on every boot.
On boot the SDK logs its configuration vector, which is a quick way to confirm the build matches what the Cloud recorded for the device:
Hubble Device SDK initialized (HDCV:1.0/E:256/CS:DU/EC:128/RP:S86400/N:T/TV:0)
This reference is configured for the Hubble SDK's device-uptime counter source
(CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME). The Ephemeral Identifier (EID)
counter is derived purely from device uptime, so no real-time clock or time
synchronization is required — that is why there is no longer a TIME/epoch
build parameter.
hubble_init()is called with an initial counter value of0, so the device starts at EID counter 0 on every boot.- The EID rotates once per
CONFIG_HUBBLE_EID_ROTATION_PERIOD_SEC(locked at 86400s — daily) and wraps at the fixed EID pool size of 128. - Because the counter restarts at 0 after a power cycle, the receiver recovers the
EID by scanning the 128-entry pool; no clock alignment is needed. To instead keep
the counter continuous across reboots, persist
hubble_counter_get()to flash and pass the saved value tohubble_init().
CONFIG_HUBBLE_NETWORK_SECURITY_ENFORCE_NONCE_CHECK is enabled in
app/hubble_config.mk. The SDK's Kconfig turns this on by default, but a
bare-metal build has no Kconfig, so it must be set explicitly or the check is
silently compiled out.
With it enabled, hubble_ble_advertise_get() returns -EPERM rather than
emitting an advertisement that reuses an (EID counter, sequence number) nonce
— which would reuse the AES-CTR keystream. hubble_advertiser_update_data()
logs the error and leaves the previous advertisement on air.
The default 5-minute refresh means ~288 advertisements per EID rotation period, well under the 1024-entry sequence space, so the check should not fire in normal operation.
HUBBLE_CRYPTO selects which crypto implementation is compiled in. Override
it on any make invocation. The default is cc310 on pca10056 and
mbedtls on every other board.
HUBBLE_CRYPTO |
Implementation | AES key size | Boards |
|---|---|---|---|
cc310 |
Direct CryptoCell (SaSi_Aes*) driver |
AES-256 | pca10056 only |
nrf |
nRF5 SDK nrf_crypto wrapper API |
AES-128 | any (backend-dependent) |
oberon |
Prebuilt Oberon ocrypto software library |
AES-256 | any |
mbedtls |
Raw mbedTLS software | AES-256 | any |
cc310andnrf(with its CryptoCell backend) both run on the nRF52840's CryptoCell hardware, so they requireBOARD=pca10056— the nRF52832 has no CryptoCell. The build errors out if you request them on another board.nrfuses the backend-agnosticnrf_crypto_aes_crypt()API and picks up the SDK's mutex, RAM-location check, and DMA chunking. It is locked to a 128-bit key because thenrf_cryptobackends only register 128-bit AES-CTR/CMAC descriptors.oberoncalls the prebuilt Oberonocrypto_aes_*functions directly (nonrf_cryptowrapper, no runtime init). It is pure software, works on any board, and pulls in far less code than the full mbedTLS cipher family.mbedtlsis pure software and works on any board. It is the default onpca10040.
When HUBBLE_CRYPTO=nrf, NRF_CRYPTO_BACKEND chooses which backend
actually implements the AES primitives:
NRF_CRYPTO_BACKEND |
Implementation | Boards |
|---|---|---|
cc310 |
CryptoCell hardware | pca10056 only |
mbedtls |
Software (mbedTLS) via the nrf_crypto wrapper |
any |
It defaults to cc310 on pca10056 and mbedtls elsewhere. The wire
format stays 128-bit regardless of which backend you pick.
Examples:
# nRF52840 with the nrf_crypto wrapper over the software mbedTLS backend
make GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=10.3 KEY=<BASE64_KEY> \
HUBBLE_CRYPTO=nrf NRF_CRYPTO_BACKEND=mbedtls
# nRF52-DK, software mbedTLS (the default there)
make BOARD=pca10040 GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=10.3 KEY=<BASE64_KEY> \
HUBBLE_CRYPTO=mbedtlsThe key size differs by backend.
cc310,oberon, andmbedtlsuse AES-256;nrfuses AES-128. The base64KEYyou pass is the same, but make sure the backend you build matches how your device is registered with Hubble.
Flash the SoftDevice (once per board/SoftDevice version):
make GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=<GNU_VERSION> flash_softdeviceFlash the application:
make GNU_INSTALL_ROOT=<GNU_INSTALL_ROOT> GNU_VERSION=<GNU_VERSION> KEY=<BASE64_KEY> flashNotes:
- Pass the same
BOARD=value you built with (see Supported boards above). Theflash_softdevicetarget programs the SoftDevice that matchesBOARD, so omitting it on an nRF52-DK would flash S140 onto an nRF52832. - The SoftDevice only needs programming once for a given part or device family. See
flash_softdevicetarget for exact behavior. README - If flashing fails or nrfjprog cannot find the device, confirm Segger drivers are installed and try ```nrfjprog --recover`` or reconnecting the device.
After flashing, you can verify that Hubble packets are being constructed and broadcast with the pyhubblenetwork helper:
pipx install pyhubblenetwork
hubblenetwork ble scan -k <YOUR_BASE64_KEY> --counter-mode device_uptimeThis command scans for and decodes Hubble advertisements using your device key.
--counter-mode device_uptimeis required. This firmware derives its EID counter from device uptime (see Counter source above), butble scandefaults to--counter-mode unix_time. Without the flag every packet fails to decrypt even though the key is correct — verified: inunix_timemode the same key failed on 100% of packets from this device.
Expected output for a working device (DECRYPT column via
--show-failed-decryption):
# freshly reset device (uptime < 5 min)
| DECRYPT | RSSI | VERSION | EID | TAG | COUNTER | SALT/SEQ | PAYLOAD |
| OK | -43 | 0 | e96073a9 | b22658a4 | 0 | 0 | |
# after the first refresh timer fire (uptime >= 5 min)
| DECRYPT | RSSI | VERSION | EID | TAG | COUNTER | SALT/SEQ | PAYLOAD |
| OK | -43 | 0 | e96073a9 | b22658a4 | 0 | 1 | 300 |
COUNTERis0until the device has been up for a full EID rotation period (86400s), then increments and wraps at 128.PAYLOADis empty for the first 5 minutes after boot. The application seeds the advertisement with no payload inhubble_advertiser_init()and only writes the uptime string when theADV_UPDATE_INTERVAL_MStimer first fires. An empty payload on a freshly reset device is expected, not a failure.SALT/SEQincrements each time the advertisement is regenerated, so it is the quickest way to confirm the refresh timer is running.
This application depends on:
- Hubble Device SDK - core SDK for Hubble functionality (pinned to v3.0.0)
- libb64 - used for decoding the Hubble key (this is just for convenience and not a requirement for Hubble functionality)
- nRF5-SDK - for Nordic SDK functionality (this is a misc mirror - for any product usage you should pull the code directly from Nordic)
- Crypto backend (selected with
HUBBLE_CRYPTO— see Crypto backend above). Options come from within the nRF5-SDK:mbedtls- software mbedTLScc310- the nRF52840 CryptoCell hardware driver (nrf_cc310prebuilt library)nrf- the SDK'snrf_cryptowrapper over either CryptoCell or mbedTLSoberon- the prebuilt Oberonocryptosoftware library (nrf_oberon)
- Build errors referencing toolchain: confirm
GNU_INSTALL_ROOTandGNU_VERSIONare correct and GCC is installed. - Device not found by
nrfjprog: check Segger drivers, USB cables/ports, and runnrfjprog --recoverif necessary. - App crashes after flashing SoftDevice: verify the app linker script and memory offsets match the SoftDevice variant programmed to the device (SoftDevice reserves a flash/RAM region).
- KEY or payload decode failures: confirm your
KEYis the base64 key issued by Hubble and is passed verbatim to the build.
- License: Apache-2.0
- Security: If you find a vulnerability, please contact support@hubble.com.