Box Commands
Box Commands extend the standard local access trigger with custom command data. They give the mobile app more control over local box behavior than a simple lock trigger provides.
Box Commands are an optional refinement of an already authenticated BLE communication. They are not REST API endpoints and they do not replace assignments. The Tapkey Mobile SDK must have valid mobile access context (a digital key from an active assignment) before any command — including a Box Command — can be transmitted to the box.
Default behavior without Box Commands
Section titled “Default behavior without Box Commands”If no custom command data is provided during a trigger, the box operates in toggle mode:
- If currently locked → unlock box + car
- If currently unlocked → lock box + car
This is the same behavior as older boxes (pre–Box 3.3) that do not support the Box Command protocol. These older boxes simply ignore any customCommandData attached to the trigger and continue to toggle.
When to use Box Commands
Section titled “When to use Box Commands”Use Box Commands when the partner app needs to control or query specific box behavior during local BLE access.
Typical use cases:
- Unlock the vehicle and unlock the box
- Unlock the vehicle while keeping the box locked
- Lock the vehicle and lock the box
- Query current box status
- Read NFC tag UIDs (if part of the integration scope)
- Improve support diagnostics with structured box feedback
The exact available command behavior depends on box firmware, setup and integration scope.
Simple trigger vs. Box Command
Section titled “Simple trigger vs. Box Command”The simplest local access action is a standard trigger command. A standard trigger changes the lock state according to the default box behavior.
A Box Command provides additional command data to the box. This allows the app to request more specific behavior — for example unlocking the vehicle while keeping the box locked, or requesting a status response without changing the vehicle or box lock state.
In Tapkey terminology, a Box Command is a TriggerLockCommand that includes custom command data as a byte array.
Supported commands
Section titled “Supported commands”| Command | Builder function | Purpose | State change |
|---|---|---|---|
| Unlock car + unlock box | buildUnlockCarUnlockBox(readNfc) |
Unlock vehicle and unlock box | Vehicle unlocked, box unlocked |
| Unlock car + lock box | buildUnlockCarLockBox(readNfc) |
Unlock vehicle, keep box locked | Vehicle unlocked, box locked |
| Lock car + lock box | buildLockCarLockBox(readNfc) |
Lock vehicle and lock box | Vehicle locked, box locked |
| Status | buildStatus(readNfc) |
Query current box status | No state change |
| Read NFC | buildReadNfc() |
Read NFC tag UIDs | No state change |
The readNfc flag requests NFC UID reading during command execution where supported.
Android implementation
Section titled “Android implementation”The Android implementation uses the WITTE Mobile Library for Android, specifically BoxCommandBuilder for building command data and BoxFeedbackV3Parser for parsing responses.
Building commands
Section titled “Building commands”boolean readNfc = true;byte[] commandData = BoxCommandBuilder.buildUnlockCarUnlockBox(readNfc);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();Use when vehicle and box should both become accessible.
boolean readNfc = true;byte[] commandData = BoxCommandBuilder.buildUnlockCarLockBox(readNfc);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();Use when the vehicle should be unlocked but the key drawer should remain protected.
boolean readNfc = true;byte[] commandData = BoxCommandBuilder.buildLockCarLockBox(readNfc);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();Use when vehicle and box should both be locked.
boolean readNfc = false;byte[] commandData = BoxCommandBuilder.buildStatus(readNfc);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();Query status without requesting a lock-state change.
byte[] commandData = BoxCommandBuilder.buildReadNfc();
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();Only use when NFC UID reading is part of the agreed integration flow.
Executing a Box Command
Section titled “Executing a Box Command”bleLockCommunicator.executeCommandAsync( bluetoothAddress, physicalLockId, tlcpConnection -> { byte[] commandData = BoxCommandBuilder.buildStatus(false);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder() .setCustomCommandData(commandData) .build();
return commandExecutionFacade.executeStandardCommandAsync( tlcpConnection, command, timeout ); }, timeout);Before executing the command, the Android app must have:
- Authenticated the customer against the partner backend
- Received valid mobile access context
- Initialized the Tapkey Mobile SDK and logged the SDK user in
- Resolved the physical lock ID
- Found or connected to the relevant flinkey BLE Box
Parsing box feedback
Section titled “Parsing box feedback”commandPromise.continueOnUi(commandResult -> { if (commandResult.getCommandResultCode() == CommandResult.CommandResultCode.Ok) { Object response = commandResult.getResponseData();
if (response instanceof byte[]) { BoxFeedbackV3 feedback = BoxFeedbackV3Parser.parse((byte[]) response);
int batteryStateOfCharge = feedback.getBatteryStateOfCharge(); boolean batteryIsCharging = feedback.isBatteryIsCharging(); boolean batteryChargerIsConnected = feedback.isBatteryChargerIsConnected(); boolean drawerState = feedback.isDrawerState(); boolean drawerAccessibility = feedback.isDrawerAccessibility(); byte[] nfcTag1Uid = feedback.getNfcTag1Uid(); byte[] nfcTag2Uid = feedback.getNfcTag2Uid(); byte[] nfcTag3Uid = feedback.getNfcTag3Uid(); } }});iOS implementation
Section titled “iOS implementation”The iOS implementation uses the WITTE Mobile Library for Objective-C, with BoxCommandBuilder for building command data.
Building commands
Section titled “Building commands”let readNfc = truelet commandData = BoxCommandBuilder.buildUnlockCarUnlockBox(readNfc: readNfc)
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()Use when vehicle and box should both become accessible.
let readNfc = truelet commandData = BoxCommandBuilder.buildUnlockCarLockBox(readNfc: readNfc)
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()Use when the vehicle should be unlocked but the key drawer should remain protected.
let readNfc = truelet commandData = BoxCommandBuilder.buildLockCarLockBox(readNfc: readNfc)
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()Use when vehicle and box should both be locked.
let readNfc = falselet commandData = BoxCommandBuilder.buildStatus(readNfc: readNfc)
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()Query status without requesting a lock-state change.
let commandData = BoxCommandBuilder.buildReadNfc()
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()Only use when NFC UID reading is part of the agreed integration flow.
Executing a Box Command
Section titled “Executing a Box Command”let boxId = "{{BOX_ID_FROM_MOBILE_ACCESS_CONTEXT}}"let physicalLockId = WDBoxIdConverter().toPhysicalLockId(withBoxId: boxId)
let bleLockScanner = TapkeyMobileSdk.serviceFactory.bleLockScannerlet bleLockCommunicator = TapkeyMobileSdk.serviceFactory.bleLockCommunicatorlet commandExecutionFacade = TapkeyMobileSdk.serviceFactory.commandExecutionFacade
guard let lock = bleLockScanner.locks.first(where: { $0.physicalLockId == physicalLockId }) else { return}
let peripheralId = lock.peripheralIdlet timeout = TKMCancellationTokens.fromTimeout(timeoutMs: 15000)
bleLockCommunicator.executeCommandAsync( peripheralId: peripheralId, physicalLockId: physicalLockId, commandFunc: { tlcpConnection in let commandData = BoxCommandBuilder.buildStatus(readNfc: false)
let command = TriggerLockCommandBuilder() .setCustomCommandData(commandData) .build()
return commandExecutionFacade.executeStandardCommandAsync( tlcpConnection, triggerLockCommand: command, cancellationToken: timeout ) }, cancellationToken: timeout).continueOnUi { commandResult in // Evaluate command result and parse box feedback if available.}.catchOnUi { error in return nil}Before executing the command, the iOS app must have the same prerequisites as Android (authentication, mobile access context, SDK login, physical lock ID, BLE connection).
Parsing box feedback
Section titled “Parsing box feedback”if commandResult.code == .ok, let responseData = commandResult.responseData as? Data {
let feedback = BoxFeedbackV3Parser.parse(responseData)
let batteryStateOfCharge = feedback.batteryStateOfCharge let batteryIsCharging = feedback.batteryIsCharging let batteryChargerIsConnected = feedback.batteryChargerIsConnected let drawerState = feedback.drawerState let drawerAccessibility = feedback.drawerAccessibility let nfcTag1Uid = feedback.nfcTag1Uid let nfcTag2Uid = feedback.nfcTag2Uid let nfcTag3Uid = feedback.nfcTag3Uid}BoxFeedbackV3 fields
Section titled “BoxFeedbackV3 fields”The parsed BoxFeedbackV3 object exposes structured status values. Accessor style differs by platform (Java getters vs. Swift properties).
| Field | Meaning |
|---|---|
batteryStateOfCharge |
Battery state of charge in percent |
batteryIsCharging |
Whether the battery is currently charging |
batteryChargerIsConnected |
Whether a charger is connected |
drawerState |
Physical drawer state |
drawerAccessibility |
Whether the drawer is accessible |
nfcTag1Uid |
UID of the first NFC tag (if available) |
nfcTag2Uid |
UID of the second NFC tag (if available) |
nfcTag3Uid |
UID of the third NFC tag (if available) |
NFC tag UID values are only expected if a command requested NFC reading and the box returned NFC data.
Recommended user-facing states
Section titled “Recommended user-facing states”Convert command and feedback results into user-facing states:
- Access in progress
- Vehicle unlocked / Vehicle locked
- Box unlocked / Box locked
- Box status received
- NFC tag read successfully / NFC tag not found
- Box not found
- Bluetooth disabled
- Permission missing
- Local authorization failed
- Command failed
- Unexpected box feedback
Keep low-level diagnostics available for support, but avoid presenting protocol-level values to the customer.
Error handling
Section titled “Error handling”| Error category | Recommended handling |
|---|---|
| Missing mobile access context | Stop the flow and request access context from the partner backend |
| SDK user not logged in | Complete Tapkey Mobile SDK login before executing a command |
| Box not found | Ask the user to move closer to the vehicle and retry |
| Bluetooth disabled | Ask the user to enable Bluetooth |
| Permission missing | Request the required platform permission |
| Local authorization failed | Retry once if appropriate, then escalate |
| Command result not OK | Show a clear failure message and log sanitized diagnostics |
| Unexpected feedback payload | Do not assume success; store sanitized diagnostics for support |
Security and logging
Section titled “Security and logging”Do not log:
- API Manager credentials, flinkey-API-Key, backend bearer tokens
- SDK Keys, IdTokens, Tapkey access tokens, digital keys
- Raw command data or raw response payloads (unless needed for support)
- NFC tag UIDs (unless the partner use case explicitly requires storing them)
If NFC tag UIDs are processed, define why they are needed, where they are stored, how long they are retained, who can access them and how they are deleted.
Use sanitized diagnostics for support tickets, logs and AI-assisted troubleshooting.
Extending Box Commands
Section titled “Extending Box Commands”When a new command is introduced, document:
- Command name and purpose
- Supported platforms and required firmware/box setup
- Command builder function and parameters
- Expected feedback and error cases
- User-facing state and logging restrictions
- Rollout notes
Source references
Section titled “Source references”- WITTE Digital GitHub organization
- Android: BoxFeedbackV3.java
- iOS: WDBoxFeedbackV3Parser.h
- iOS: WDBoxFeedbackV3Parser.m
