iOS SDK: Getting started
This guide takes an app from installing the Cartrack BLE Lock SDK for iOS to locking and unlocking a vehicle, in the order the calls are made. It describes version 3.0.4; where an earlier version behaves differently, it says so.
Before you start
- iOS 15.0 or later, on an iPhone (3.0.1 supports iOS 11.0). The simulator has no Bluetooth: there,
connect()reportsBleError.bluetoohUnsupported(3). NSBluetoothAlwaysUsageDescriptionin the app's Info.plist. The SDK creates its Bluetooth manager on the firstBleService.getTerminal(terminalID:)call, and iOS stops an app that uses Bluetooth without that key. That first call also shows the Bluetooth permission prompt, and iOS shows its own alert when Bluetooth is off.- The app on screen when connecting, or from 3.0.4
bluetooth-centralin the app'sUIBackgroundModesto connect from the background too. On screen the SDK looks for every Bluetooth device nearby. In the background iOS reports only devices that advertise a service the app names, so there the SDK looks for the terminal's MiKey service. Up to 3.0.2, a connection started in the background never found the terminal. - The terminal's key:
bluetooth_keyfrom the Fleet API (GET /mikey/{registration}), 64 hexadecimal characters.
Install
The SDK package is provided by Cartrack on request. If you do not have it yet, see Request SDK access.
- Add the CartrackBleLockSDK.xcframework folder, in
Binary Frameworkin the package, to your Xcode project. - In Project Settings → Frameworks, Libraries, and Embedded Content, set CartrackBleLockSDK.xcframework to Embed & Sign.
- Add
import CartrackBleLockSDKto the files that use the SDK.
The package also holds the demo app, an Xcode project that makes every call below.
Get the terminal
let terminal = BleService.getTerminal(terminalID: "CARPOOL01") // the Bluetooth name the terminal advertises
terminal.delegate = self // conforms to BleTerminalDelegate
The name is trimmed and upper-cased. The same name always returns the same BleTerminal, which has a single delegate: setting it replaces the previous one. Every callback comes on the main thread, and calls made on another thread are moved to it. In 3.0.1, a connect() made on another thread never completed.
Save the key
terminal.saveAuthKey(authKey: bluetoothKey)
The answer comes straight away in bleTerminalDidSavedKey(terminal:error:). The SDK keeps one key for the app, not one per terminal: saving replaces it.
terminal.hasKeysays whether a key is saved, andterminal.authKeyreturns it as lowercase hexadecimal.- Anything but pairs of hexadecimal digits fails with
saveAuthKeyFailed(4); spaces around the key are trimmed. A save that fails keeps the key saved before. In 3.0.1, other characters were skipped, which shifted the key. - The key can be read while the phone is locked, once the phone was unlocked after starting, and it is never restored onto another phone from a backup: on a new phone, fetch the key from the Fleet API and save it again. A key saved by 3.0.1 moves to this protection the first time it is read.
- A key of the wrong length is accepted here and fails at connect time with
BleError.keyInvalid(6). terminal.removeAuthKey()deletes the key, without a callback. Call it when the rental ends.
Connect
terminal.connect(timeout: 20) // seconds, 10 by default
bleTerminalDidConnect(terminal:error:) comes once the terminal has accepted the key and the SDK has read the lock state into terminal.lockState. terminal.connectionState is already .connected once the key is accepted.
The timeout applies to each step in turn: the scan, the Bluetooth link, the authentication, then the lock state read, so an attempt can take up to four times as long. If the lock state read gets no answer, the connection is reported anyway, with lockState .unknown. connect() while connected reports the connection again.
A failed attempt reports one of:
keyNotFound(5) orkeyInvalid(6), straight away, when no valid key is saved;bluetoohUnauthorized(1), also from 3.0.4 when the Bluetooth prompt is still unanswered at the timeout,bluetoothDisabled(2) orbluetoohUnsupported(3);terminalNotFound(7) when the terminal did not advertise during the attempt;ctgError(45, 22)when the link or the authentication timed out;ctgError(56, 21)when the terminal refused the key.
Send actions
terminal.sendAction(.unlock)
terminal.getVehicleStats()
| Call | Answers in | Updates |
|---|---|---|
sendAction(.lock), sendAction(.unlock) | bleTerminalDidAction(terminal:action:error:) | lockState |
sendAction(.lockState) | bleTerminalDidAction(terminal:action:error:) | lockState |
sendAction(.ignitionState) | bleTerminalDidAction(terminal:action:error:) | ignitionState |
sendAction(.headlight), .horn, .unlockNoKeyFob | bleTerminalDidAction(terminal:action:error:) | nothing |
getVehicleStats() | bleTerminalDidGetVehicleStats(terminal:vehicleStats:error:) | nothing |
Before answering .lock and .unlock, the SDK reads the lock state. It reports success when the terminal accepted the command, whatever the lock state reads, because the vehicle's lock state input can lag. When not connected, the answer is notConnected (8), straight away.
Each command gets exactly one answer. When the terminal says nothing within the timeout passed to connect(timeout:), the answer is ctgError(actionCode, 22), with the command's action code. In 3.0.1, a command the terminal did not answer never called back.
Handle every callback
| Callback | When |
|---|---|
bleTerminalDidSavedKey(terminal:error:) | Right after saveAuthKey(authKey:). |
bleTerminalDidConnect(terminal:error:) | Once per connect(): success once the lock state is read (or its read timed out), or the error that ended the attempt. Also with ctgError(45, 23) when the terminal refuses the keep-alive the SDK sends every 7 seconds; bleTerminalDisconnected follows. |
bleTerminalDidAction(terminal:action:error:) | Once per sendAction(_:). |
bleTerminalDidGetVehicleStats(terminal:vehicleStats:error:) | Once per getVehicleStats(). |
bleTerminalSignalUpdate(rssi:strength:) | Every second while connected: .strong above -60 dBm, .good above -90 dBm, .weak below. |
bleTerminalDisconnected(terminal:) | When a connection reported by bleTerminalDidConnect ends, including after disconnect(), and once when disconnect() cancels an attempt. Never for an attempt that failed: that one reports through bleTerminalDidConnect. |
Handle errors
Every error is a BleError. Codes 1 to 8 come from the phone and the SDK; see BleError.
Terminal errors come as BleError.ctgError(actionCode, errorCode, description). errorCode is the CtgError code, 21 to 28, and actionCode is the command it answered:
| actionCode | Command |
|---|---|
| 32 | Ignition state |
| 33 | Vehicle stats |
| 45 | Connect |
| 56 | Authentication |
| 57 | Lock |
| 58 | Unlock |
| 59 | Headlight |
| 60 | Horn |
| 61 | Unlock without key fob |
| 63 | Lock state |
For the other errors, actionCode is -1.
Disconnect
terminal.disconnect()
terminal.removeAuthKey() // when the rental ends
When connected, disconnect() sends the disconnect command and drops the link, also when the terminal refuses or does not answer, and bleTerminalDisconnected(terminal:) comes once the link is down. During an attempt, it cancels the attempt and reports bleTerminalDisconnected(terminal:) once. With nothing connected, it does nothing and reports nothing.
If something does not behave as described here, see Troubleshooting.
API reference
- BleService: the entry point
- BleTerminal: connection, key and commands
- BleTerminalDelegate: the callbacks
- VehicleStats: the vehicle stats model
- Enumerations: BleAction, BleConnectionState, BleSignalStrength, BleError, CtgError, LockState, IgnitionState, IndicatorState