Integrating a UWB implementation
This guide explains how to plug a UWB radio into the nRF Door Lock and Access Control Add-on and the Aliro stack through the stable external-provider contract.
The core repository ships the generic UWB abstraction (the UltraWideBand facade) and an in-tree stub implementation.
Provide the concrete radio driver by either editing the in-tree stub or supplying an out-of-tree module.
Select your implementation with the DOOR_LOCK_ALIRO_UWB_IMPL Kconfig option
For the layered architecture and stack interaction, see UWB integration in the reference applications.
Prerequisites
Before starting your port, make sure you understand the scope of the work and have the required components in place.
This guide assumes familiarity with the following:
Aliro specification — In particular, the Bluetooth LE message layout (Protocol Header, Message ID, Length, Payload) and the ranging session setup flow (M1-M4 exchange). Several integration points require you to forward complete Aliro messages unchanged, so a working understanding of this layout is essential. See Aliro application interactions for the relevant sequence diagrams.
Facade pattern used in the add-on — The platform code separates a stable public API (
UltraWideBand) from an implementation (UltraWideBandImpl). You implement only the private methods; you do not modify the facade or the stack interface wiring. See Integration with the door lock add-on for details.nRF Connect SDK and Zephyr build system — You configure the port through Kconfig options and adapt one or two CMake files, or supply the implementation from an out-of-tree Zephyr module.
Vendor SDK requirements
Your UWB silicon and its driver or SDK must be able to provide the following capabilities, which the Aliro stack and the reference AccessManager rely on:
Capability |
Required |
Notes |
|---|---|---|
Ranging session configuration from URSK, session ID, and protocol version |
Yes |
The stack extracts these values from the Bluetooth LE session and passes them to |
Distance reporting |
Yes |
You deliver distance samples through the |
Session lifecycle events |
Yes |
Your driver must signal session state transitions (for example, ranging started, suspended, or destroyed) so you can forward them through |
Forwarding opaque UWB setup messages |
Yes |
During setup, the vendor stack must accept and produce the UWB-related payloads carried inside Aliro Bluetooth LE messages, without requiring direct access to the link. |
Multiple concurrent sessions |
Recommended |
If your hardware supports more than one User Device at a time, your implementation must map each |
Bluetooth LE/UWB time synchronization |
Optional |
Implement |
If your SDK cannot satisfy one of the required capabilities, the corresponding Aliro ranging flow cannot complete, and the stack may terminate the Bluetooth LE session.
Starting point
The subsys/aliro/uwb/stub_impl/ directory is the starting point for an in-tree port.
It contains skeleton uwb_impl.h and uwb_impl.cpp files whose methods return -ENOSYS.
With the default CONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_STUB choice, the build system selects this directory automatically, so you can build, link, and run the reference application before writing any vendor code.
This is the basis for the first step of the porting workflow.
To confirm your environment is ready, build a reference application with the specific options set and verify that it links and logs UWB is not implemented.
Solution architecture
Your code sits in UltraWideBandImpl, which translates between the Aliro-facing API and your vendor driver or SDK.
To learn more about the interactions between the Aliro stack and the application, see the Aliro application interactions documentation page.
The table below summarizes each layer, its location in the codebase, and its responsibility. The sections that follow divide the port into stack integration and application integration so you can implement and test each part independently.
Component |
Location |
Responsibility |
|---|---|---|
Aliro stack state machine |
nRF Connect SDK Aliro stack (precompiled binary library) |
Enters |
|
|
Single stack entry point for inbound Bluetooth LE/UWB traffic. The stack forwards only UWB Ranging Service and Notification/Ranging messages as complete Aliro messages. |
|
|
Calls |
|
|
Stable public API used by the application; forwards to private |
|
|
Vendor-specific driver and SDK glue, session bookkeeping, and callback invocation. |
|
|
Applies access policy by evaluating distance thresholds, deciding unlock and lock actions, and handling UWB session lifecycle. |
Application |
|
Registers UWB callbacks and initializes the module before |
Integration steps
Integration of a UWB chip consists of the following steps:
After completing both steps, follow the recommended bring-up sequence to implement and verify the port incrementally.
Integration with the Aliro stack
The Aliro stack remains vendor-agnostic. It never includes specific UWB driver headers. All hardware access goes through two interface functions implemented in the application tree.
Stack interface contract
When the CONFIG_NCS_ALIRO_BLE_UWB Kconfig option is enabled (automatically enabled when the CONFIG_DOOR_LOCK_BLE_UWB Kconfig option is selected), the stack expects the following implementations (guarded by the same Kconfig symbol):
Function |
When it is called |
Contract and behavior |
|---|---|---|
|
On UWB Ranging Service messages and Notification messages with the Ranging Message ID. |
The |
|
When the stack enters the |
Delegates to |
Both functions have reference implementations under the applications/*/src/aliro/interface_impl/ directory.
The following example shows the reference HandleBleMessage() implementation in interface_impl/uwb.cpp:
return ::Aliro::Uwb::UltraWideBandInstance()
.HandleBleMessage(data, length, sessionContext);
Note
In most ports you only provide uwb_impl.cpp (and uwb_impl.h).
You normally do not modify interface_impl/uwb.cpp or interface_impl/session.cpp.
Keep that wiring as-is and implement ranging setup in UltraWideBandImpl::_ConfigureRangingSession().
Outbound path: UWB and Bluetooth LE inter-operation
When your UWB stack needs to send a response to the User Device (for example M2 after M1), it must not call Bluetooth LE APIs directly.
Instead, invoke the mBleMessageTransmit callback registered during UltraWideBand::Init().
The reference application code wires this callback to the Aliro stack public API:
.mBleMessageTransmit = [](UltraWideBand::SessionContextHandle sessionContext,
const uint8_t *data, size_t length) {
Aliro::AliroStack::Instance().SendBleMessage(sessionContext, data, length);
},
SendBleMessage() accepts the same full Aliro message layout as HandleBleMessage() (Protocol Header, Message ID, Length, Payload).
The stack parses the buffer, extracts the protocol type and message ID, encrypts the payload, and transmits it over Bluetooth LE through Interface::Session::Send().
Messages you send through this API should therefore use either UWB Ranging Service or Notification with Ranging Message ID — the same types the stack forwards inbound through HandleBleMessage().
Passing only a payload without the Aliro header is invalid and will be rejected by the stack.
Session context handle
Both interface functions receive an Aliro::ConnectionHandle (typedef UltraWideBand::SessionContextHandle).
This opaque handle identifies the Aliro Bluetooth LE session and must be stored in your implementation so that:
Incoming Bluetooth LE payloads from
HandleBleMessage()can be routed to the correct UWB session object.Outbound
mBleMessageTransmitcalls can be directed to the Bluetooth LE session the stack is expecting.Ranging callbacks (
mRangingData,mRangingSessionStateChanged) correlate measurements with the correct access-control context.
Inbound path: UWB to the access policy layer
Your implementation reports two event streams through the Callbacks structure defined in the subsys/aliro/uwb/uwb.h file:
Callback |
Payload type |
Purpose |
|---|---|---|
|
|
Report session lifecycle transitions. |
|
|
Deliver distance samples. |
mRangingSessionStateChanged
Notify state transitions using Aliro::RangingSessionState (include/aliro/types.h):
enum class RangingSessionState : uint8_t {
Uninitialized = 0x00,
Initialized,
Idle,
Ranging,
RangingSuspended,
RangingResumed,
Destroyed,
};
The AccessManager acts on Destroyed and RangingSuspended to update reader state and prevent unwanted unlock toggling.
mRangingData
Delivers distance samples as Aliro::UwbRangingData (a data structure that contains a pointer to the data and its length).
The reference AccessManager reads a 16-bit big-endian distance in centimeters from the first two bytes (see ExtractDistanceFromUwbData() in the access_manager_impl.cpp file).
If your vendor reports distance in another format, do one of the following:
Convert in
UltraWideBandImplbefore invoking the callback.Override
ExtractDistanceFromUwbData()in yourAccessManagerImpl.
Integration with the door lock add-on
Platform UWB code is located under the subsys/aliro/uwb/ directory.
The build selects the implementation through the DOOR_LOCK_ALIRO_UWB_IMPL Kconfig choice:
Choice option |
Result |
|---|---|
|
Builds the in-tree |
|
Builds no in-tree implementation; an out-of-tree module provides |
Choose the integration path that matches how you ship your driver:
In-tree — Keep the default
CONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_STUBKconfig option and replace the stub instub_impl/with your driver logic.Out-of-tree — Select the
CONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_EXTERNALKconfig option and supply the implementation from an external UWB provider module (see Providing the implementation from an external module).
Additionally, enable the CONFIG_DOOR_LOCK_BLE_UWB Kconfig option.
The option selects CONFIG_DOOR_LOCK_ALIRO_UWB (the platform module in subsys/aliro/uwb/) and CONFIG_NCS_ALIRO_BLE_UWB (the UWB support in the Aliro stack).
The UltraWideBand facade pattern
Following the same design as AccessManager, the add-on exposes a singleton facade:
UltraWideBand— Declares the public methods (Init(),HandleBleMessage(), …).UltraWideBandImpl— Implements the private methods (_Init(),_HandleBleMessage(), …).UltraWideBandInstance()— Returns the facade reference used throughout the application.
External code should call UltraWideBandInstance() and should not call UltraWideBandImpl methods directly.
The public UltraWideBand API in subsys/aliro/uwb/uwb.h defines the contract between the reference application and your implementation.
To use it:
Implement
UltraWideBandImplinuwb_impl.handuwb_impl.cpp, using the files instub_impl/as a skeleton. Replace each-ENOSYSstub with real driver logic.Implement only the methods declared in the facade. Keep any additional chip-specific types and helpers private to
UltraWideBandImpl.
The implementation methods table lists every method in the contract and the behavior each one must provide.
Implementation methods
Each row below maps a public UltraWideBand method to the private UltraWideBandImpl method you implement, and describes the behavior it must provide.
Methods marked Optional can remain no-op or -ENOSYS stubs if your hardware does not need them.
Public method (via facade) |
Private implementation method |
Expected behavior |
|---|---|---|
|
|
Initialize hardware, store |
|
|
Release sessions and shut down the radio. |
|
|
Optional: trigger CCC Procedure 0 Bluetooth LE/UWB time sync. Implement if your UWB vendor requires explicit time alignment after Bluetooth LE connection. |
|
|
Pass the full decrypted Aliro message (header + payload) to your vendor stack.
This is the critical path during session setup (M1-M4 exchange over Bluetooth LE).
Outbound replies must use the same message layout when invoking |
|
|
Create a per-session context keyed by |
|
|
Start or arm ranging after configuration. Currently not used by the reference applications. |
|
|
Tear down the UWB session when the Bluetooth LE session ends or access process completes. |
|
|
Pause active ranging (Aliro suspend and resume flow). Currently not used by the reference applications. |
|
|
Resume after suspend. Currently not used by the reference applications. |
|
|
Optional: human-readable firmware string for logging or shell commands.
Return |
|
|
Return whether |
Note
Currently, UWB setup and suspend/resume in the reference applications are driven by Bluetooth LE messages from the User Device (relayed through the Aliro stack and HandleBleMessage()), not by explicit calls to InitiateRangingSession(), SuspendRangingSession(), or ResumeRangingSession().
Those methods remain part of the public API for integrators who need the application to initiate setup or control ranging directly.
CMake integration
When the CONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_STUB Kconfig option is selected, the build is wired up in the following places:
Subsystem library —
subsys/aliro/uwb/stub_impl/CMakeLists.txtcompiles youruwb_impl.cpp. The parentsubsys/aliro/uwb/CMakeLists.txtalways builds the shared facadeuwb.cpp.Application interface —
applications/*/src/aliro/interface_impl/CMakeLists.txtaddsuwb.cppwhen you set theCONFIG_NCS_ALIRO_BLE_UWBKconfig option.Include path — Application code includes the
uwb_impl.hfile. With the stub implementation selected, Zephyr resolves that header fromstub_impl/.
Providing the implementation from an external module
Select the CONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_EXTERNAL Kconfig option to keep the UWB backend outside the core repository.
In this configuration, the core repository does not build an in-tree UltraWideBandImpl.
The external module must do the following:
Provide the
uwb_impl.hfile and its implementation sources.Place
uwb_impl.hon the include path, so that application code including it resolves the header from your module.Implement the
UltraWideBandImplmethods declared by the facade insubsys/aliro/uwb/uwb.h, following the implementation methods table.Add the module to your workspace, for example through a west manifest project, and register it as a Zephyr module so that the build picks up its Kconfig and CMake files.
Kconfig checklist
Option |
Required for UWB |
Notes |
|---|---|---|
|
Yes |
Application-level Bluetooth LE with UWB integration.
Selects |
|
Yes (via |
Enables stack UWB HSM and interface symbols. |
|
Yes (via |
Builds |
|
Default |
Builds the in-tree |
|
Optional |
Builds no in-tree implementation; an external module provides |
Recommended bring-up sequence
Implement the methods in the order below. The first stages — initializing the hardware, configuring sessions, and relaying Bluetooth LE messages — form the minimum viable port, the smallest set of methods needed for an end-to-end ranging session. With these in place, the Aliro stack can drive a complete UWB setup exchange through your implementation.
The later stages refine reporting and validate the integration. Each stage adds a verification point, so you can confirm progress before moving on.
Build with stubs — Enable
CONFIG_DOOR_LOCK_BLE_UWBwith the defaultCONFIG_DOOR_LOCK_ALIRO_UWB_IMPL_STUBchoice, as described in Prerequisites. Confirm the application links and logsUWB is not implemented(-ENOSYSfrom_Init()).Initialize the hardware — Implement
_Init(),_Deinit(), and_IsInitialized(). VerifyIsInitialized()returns true after init, and optionally surfaceGetFirmwareVersion()in a log or shell command.Configure and tear down sessions — Implement
_ConfigureRangingSession()and_TerminateRangingSession(). Confirm thatStartRangingSession()from the stack succeeds and that a session is cleanly removed on Bluetooth LE disconnect.Relay Bluetooth LE messages — Implement
_HandleBleMessage()and wiremBleMessageTransmit. Use logging to confirm M1–M4 traffic flows through your module and back to the User Device.Deliver ranging reports — Fire
mRangingDatawith a 16-bit big-endian centimeter distance. TuneAccessManagerthresholds throughSetMaxAllowedDistance()and confirm the distance reaches the access policy layer.Emit state notifications — Call
mRangingSessionStateChangedsoAccessManagerupdates reader state on suspend and destroy.Regression test — Run an Aliro Bluetooth LE + UWB session against a certified User Device or Aliro Test Harness.