Customizing the Aliro Application#
The Aliro extension is delivered as a set of Simplicity SDK Component (SLC) components, not as a closed library with a thin sample application. The SLC components implement the Aliro reader role, while a small amount of application source is copied into your project when you create it from a project template.
The reader protocol, transport layers, and hardware drivers are implemented in the SLC components and typically do not require modification. The application source, including the entry points, reader loop, and access hook, is intended for product-specific behavior and where most customization occurs.
This page explains the separation between the SLC components and the application source and describes the purpose of each customization point to help you determine where to implement your product logic. For instructions on creating a project, see Getting Started.
What You Own in a Generated Project#
When you create a project from Aliro Minimal Reader or AliroMatterLock, the following files are copied into the project and compiled as project sources rather than component sources:
File | Role |
|---|---|
| Application entry points; brings up the reader, the optional shell, and (on AliroMatterLock) the Matter stack |
| The reader loop: builds the reader from configuration and drives Aliro transactions |
| Your implementation of the valid-access hook |
| Standard Silicon Labs startup and FreeRTOS glue |
AliroMatterLock keeps its application sources under src/ and adds the Matter lock application itself — AppTask.cpp, CustomerAppTask.cpp, LockEndpoint.cpp, LockEndpointInit.cpp, AliroShellBridge.cpp, and related files — all of which are project sources you can modify.
Everything else — aliro_actuator, aliro_reader_task, the transport components, the Near Field Communication (NFC) driver, the shell, and the Matter integration glue — arrives as installed components. Treat those as the platform: configure them through their SLC configuration headers and component selection (see Project Configuration) rather than editing them in place, so that extension updates remain drop-in.
The practical consequence is that a production application usually starts as a copy of the example and diverges in three files: app.cpp, ExampleTask.cpp, and AliroLockBridge.cpp.
Application Entry Points#
An Aliro application uses the same entry points as any other Silicon Labs application. app_init() runs once at startup and is where the example starts the NFC frontend, creates the reader task, and registers shell commands. app_process_action() is the cooperative main-loop hook, guarded by app_is_process_required() and scheduled by app_proceed(); it must not block.
Add product initialization to app_init() — actuator drivers, input handling, product-specific storage, a manufacturing interface — in the same way you would in any Simplicity SDK application. Add lightweight periodic or event-driven work to app_process_action(). Because the Aliro reader runs in its own FreeRTOS task, long or blocking product work belongs in that task or in a task of your own, not in either entry point.
On AliroMatterLock, app_init() additionally initializes the Matter stack through SilabsMatterConfig::AppInit() and, for Bluetooth Low Energy (BLE) reader builds, the Aliro BLE side channel that lets the Aliro reader advertise and connect alongside the Matter BLE usage (see BLE Side Channel). Keep the ordering of those calls when you extend the function.
The Valid-Access Hook#
The single most important customization point is OnAliroValidAccess(), declared in aliro/reader_task/include/AliroLockBridge.h. The reader task calls it after every successful Aliro transaction. The component deliberately provides no implementation: it is a link-time contract, and each application must supply its own definition. The Aliro Minimal Reader example defines a log-only body because it has no actuator; AliroMatterLock defines one that drives the Matter door lock. A product defines one that drives the real lock hardware.
The hook receives an AliroAccessGrant, a framework-agnostic description of what authorized the tap. It carries the matched access credential (index and type) so an application can attribute the unlock to a specific credential and user, and it carries the step-up fields (needsProvision, the endpoint public key, and the credential-issuer key that validated the Access Document) when the tap authenticated a previously unknown device that the step-up phase then validated. No Matter types cross this boundary, which is what allows the same reader task to serve a standalone reader and a Matter lock.
Two constraints define how you implement the hook. First, the hook runs in the reader task context, so it must not block indefinitely. Queue long-running actuation to your own task or timer. Second, in a Matter build, the hook must not access the Matter data model directly. Instead, the example passes the event by value to the application task so that each tap retains its own attribution, even when multiple taps are queued.
The Reader Loop#
ExampleTask.cpp is where Aliro behavior is actually driven, and it is the file to read first when you want to change how the reader operates rather than what happens after a successful read. The loop is intentionally written as ordinary application code with no hidden state machine: it initializes storage, restores any persisted reader configuration, and then repeats a simple cycle — notice configuration changes, rebuild the reader when they occur, run one transaction, interpret the result.
Three regions of that file are natural customization points.
BuildReader() translates a configuration snapshot into a Reader object. This is where the transport is selected, where the reader key pair, group identifier, and optional certificate chain are attached, where the credential authorizer is injected, and where step-up trust anchors and requested document element identifiers are installed. Product policy about which Aliro capabilities the reader offers is expressed here.
The per-transport transaction helpers (RunOneTransactionNfc() and RunOneTransactionBleUwb()) express the transaction sequence itself: initiation, the expedited-fast attempt with fallback to expedited-standard, the exchange, the optional step-up phase, and termination. The example includes a commented-out optional exchange before step-up precisely to show that the sequence is an implementation choice within what the specification allows, not a fixed path.
The result-handling block decides what a completed transaction means. It distinguishes a plain authorized tap from a step-up tap, checks that the Access Document actually validated before treating a step-up as provisionable, populates the AliroAccessGrant, and calls the hook. It also treats a transaction that was unwound by a configuration change as a no-op rather than a failed access, so a reconfiguration mid-tap does not surface as a denial.
Because the loop rebuilds the reader only on configuration changes and re-reads credentials on every tap, credential updates take effect immediately while reader-identity changes are applied atomically at a well-defined point. Preserve that property if you restructure the loop.
Reader Configuration#
Reader configuration follows a deliberate producer/consumer model rather than a direct setter interface. AliroReaderConfig (aliro/reader_task/include/AliroReaderConfig.h) holds the active configuration snapshot in random-access memory (RAM): the signing and verification keys, the reader group and sub-group identifiers, the optional issuer key and reader certificate, and for BLE builds the group resolving key and Service Protocol and Service Multiplexer (SPSM) value. Producers call Update() or Clear(); every write bumps a generation counter. The reader loop is the consumer and polls ConsumeDirty(), which is why a configuration change from any source converges to a single, well-ordered reader rebuild.
The example provides three producers, and the model is designed so that adding a fourth requires no changes to the reader loop:
The Matter Door Lock cluster delegate (
LockEndpoint::SetAliroReaderConfig()/ClearAliroReaderConfig()) on AliroMatterLock.The Aliro shell, for bring-up and certification workflows — see CLI Setup and Commands.
The persisted record loaded at boot.
Persistence is decoupled from the in-RAM model. AliroConfigStore loads a saved snapshot into AliroReaderConfig at boot and persists writes from any producer, and it allows partial writes so that incremental provisioning (for example, a field at a time over the shell) can make progress. Nothing is seeded on boot: until a producer supplies a signing key, a verification key, and a group identifier, the reader idles instead of running with placeholder identity. A product that provisions in manufacturing should write the record through this store rather than compiling keys into firmware.
Read a snapshot with GetSnapshot() and act on that copy, not on repeated queries of the singleton — another task can change the configuration between two calls.
Access Credentials and Issuer Keys#
Enforcement is separated from the protocol implementation through a small authorizer interface. AccessManager (aliro/access_manager/include/AccessManager.h) models the Aliro Access Manager: it owns the provisioned per-device endpoint public keys and answers the authorization question at tap time. The reader loop injects it into the reader with SetCredentialAuthorizer(), so the protocol layer never needs to know how credentials are stored or who provisioned them.
AccessManager intentionally keeps no authoritative copy in RAM. Every add, remove, read, and tap-time lookup goes to non-volatile memory (NVM), which is what makes Matter bookkeeping (SetCredential / GetCredential on the Door Lock cluster), shell provisioning, and reader enforcement agree with each other and survive a reboot. Credentials are typed as evictable or non-evictable to match the corresponding Matter credential types, and capacity is a compile-time configuration value so you can size the credential array to your product.
Credential issuer keys are managed separately by CredentialIssuerManager (aliro/credential_issuer_manager/include/CredentialIssuerManager.h). These are the trust anchors the step-up phase validates Access Documents against; provisioning one is what enables the step-up path in the example loop. If your product does not support step-up, leaving the issuer store empty is sufficient — the loop leaves step-up disabled.
Implementing your own authorizer is a supported customization: any class satisfying the credential-authorizer interface can be injected in BuildReader() if your product keeps credentials in a system of record other than the Aliro NVM layer.
Persistent Storage#
Underneath both managers is aliro::storage (aliro/aliro_actuator/storage/aliro_nvm_storage.h), a typed, framework-agnostic wrapper over an NVM3 key region reserved for Aliro. It exposes explicit store and retrieve calls per logical item — reader identity and trust material, BLE and Ultra-Wideband (UWB) parameters, access policy and validity iteration counters, reader descriptor fields, and slot-indexed credential and issuer records — rather than a generic key/value surface.
The design intent is that storage layout is owned by the extension and stable across releases, while ownership of NVM initialization stays with the platform, so the same storage layer works whether or not a Matter stack is present. Call Init() once after the system NVM backend is ready (the example loop calls Init() before starting) and use the typed accessors instead of writing to the Aliro key region directly. Records are validated by magic and version bytes, so a partially written or stale record reads as absent rather than as corrupt data.
Matter Integration#
On AliroMatterLock, the boundary between Aliro and Matter is drawn at exactly one application file. AliroLockBridge.cpp is the only place where Aliro types and Matter types meet: it maps the framework-agnostic credential type byte to the Matter credential enumeration, resolves the Matter user that owns a credential, links a step-up-provisioned endpoint key to that user, and drives the lock through the same request path a remote Matter unlock uses — so the lock state attribute, the lock operation event with OperationSource = Aliro, and the local indicators all move together.
The remaining Matter-side pieces are conventional Matter application code. LockEndpoint is the Door Lock cluster delegate: it receives reader configuration from the cluster, reports the Aliro attributes and supported key capacities, and forwards writes into the configuration model in Reader Configuration. AppTask and AppTaskImpl implement the lock behavior, with CustomerAppTask providing product-specific overrides. The aliro_matter_integration component supplies the glue that lets an Aliro BLE reader coexist with Matter's use of the BLE radio — see BLE Side Channel.
The example uses a simulated lock state driven by buttons, light-emitting diodes (LEDs), and the display; it does not include motor or actuator control. Replacing that simulation with real actuation is a product task, and the lock request path in AppTask is the seam to do it at.
Note that the Matter application task and the Aliro reader task are separate contexts. Cross from the reader task to Matter by posting an event, and hold the Matter stack lock only around data-model access — the example scopes those locks narrowly and releases them before re-entering the lock request path.
Code Organization Reference#
The reader implementation is organized by concern under aliro/:
Area | Contents |
|---|---|
| Reader, access protocol, step-up, device requests, Application Protocol Data Unit (APDU) and Basic Encoding Rules Tag-Length-Value (BER-TLV) encoding |
| NFC and BLE transport layers |
| Vendor-neutral NFC frontend interface, ST25R frontend driver, and EFR32 cryptography, BLE, and serial support |
| Key, key slot, and certificate handling |
| Status codes and utility helpers |
| NVM storage layer |
| Logging configuration |
| Reader configuration model, config store, shell command handlers, access hook contract |
| Credential and issuer key stores |
| Matter-side glue for BLE coexistence |
| Board hardware abstraction layer (HAL) |
| Optional shell engine and streamer |
Entry points most often referenced during customization:
Header | What it gives you |
|---|---|
|
|
| Active reader configuration snapshot and change notification |
| Load, save, and erase the persisted reader configuration |
| Credential store and tap-time authorizer |
| Credential issuer key store for step-up |
| Typed NVM accessors and record layout |
| Reader construction and transaction sequence |
| Step-up result snapshot |
| Vendor-neutral NFC frontend interface |
SLC Components#
Key components in the aliro package:
Component | Role |
|---|---|
| Core reader / access protocol |
| Reader configuration model, config store, and shell command handlers |
| Transport selection |
| Application and transport configuration headers |
| Vendor-neutral NFC frontend interface |
| Reference ST25R driver |
| ST RF Abstraction Layer (RFAL) library |
| Access credential store and authorizer |
| Credential issuer key store |
| Board hardware abstraction layer (HAL) |
| Optional command-line interface (CLI) — see CLI Setup and Commands |
| Optional certification test scenarios |
| Matter and Aliro glue (AliroMatterLock) |
Related Topics
NFC Hardware and Porting — Replace or keep the reference ST25R NFC stack
Project Configuration — SLC components, BLE side channel, and low-power mode
CLI Setup and Commands — Enable the CLI and use Aliro shell commands