Skip to content

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.

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.

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.

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.

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.

The Android implementation uses the WITTE Mobile Library for Android, specifically BoxCommandBuilder for building command data and BoxFeedbackV3Parser for parsing responses.

boolean readNfc = true;
byte[] commandData = BoxCommandBuilder.buildUnlockCarUnlockBox(readNfc);
TriggerLockCommand command = new DefaultTriggerLockCommandBuilder()
.setCustomCommandData(commandData)
.build();

Use when vehicle and box should both become accessible.

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
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();
}
}
});

The iOS implementation uses the WITTE Mobile Library for Objective-C, with BoxCommandBuilder for building command data.

let readNfc = true
let commandData = BoxCommandBuilder.buildUnlockCarUnlockBox(readNfc: readNfc)
let command = TriggerLockCommandBuilder()
.setCustomCommandData(commandData)
.build()

Use when vehicle and box should both become accessible.

let boxId = "{{BOX_ID_FROM_MOBILE_ACCESS_CONTEXT}}"
let physicalLockId = WDBoxIdConverter().toPhysicalLockId(withBoxId: boxId)
let bleLockScanner = TapkeyMobileSdk.serviceFactory.bleLockScanner
let bleLockCommunicator = TapkeyMobileSdk.serviceFactory.bleLockCommunicator
let commandExecutionFacade = TapkeyMobileSdk.serviceFactory.commandExecutionFacade
guard let lock = bleLockScanner.locks.first(where: { $0.physicalLockId == physicalLockId }) else {
return
}
let peripheralId = lock.peripheralId
let 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).

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
}

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.

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 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

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.

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