Skip to content

Scan for Tapkey-Enabled locks using Bluetooth Low Energy

This guide will show you how to scan for Tapkey-enabled Bluetooth devices using the Tapkey Mobile SDK for iOS. Tapkey SDK relies on Bluetooth Low Energy (BLE) to sense and connect with nearby Tapkey-Enabled lock devices, allowing for optimal battery consumption.

Some of the core concepts of the Tapkey Mobile SDK for iOS are:

  • TKMBleLockScanner used to detect and communicate Tapkey locks nearby.

In order to enable the communication between the device and Tapkey-Enabled locks, you are required to:

  • Set proper permissions from the application to be able to access BLE.
  • Activate scanning via Tapkey API or by a custom implementation (suggested by the tapkey team).
  • Use either Observable methods or Get information from nearby devices as required.
  • Stop/Close scanning when no longer required.

Setup Requisites

It is required to proper configure the project to be able to access the BLE functionality from the device, so that Tapkey SDK can be used to access Tapkey-Enabled locks.

Add Usage Description to Info.plist

Depending on targeted iOS SDK it is required to include a usage description in the Info.plist of the application, otherwise the app will crash.

iOS Version Key in Info.plist
iOS 13 and later NSBluetoothAlwaysUsageDescription
iOS 12 and earlier NSBluetoothPeripheralUsageDescription

Scanning

Custom BLE Scanner gives fine control from implementation perspective. This option allows consumers to make fine grained decisions like when a lock is considered nearby or out of range, add the concept of further BLE devices that need to be discovered and listed at the same time, or when the default implementation does not meet your requirements.

The Tapkey Mobile SDK for iOS provides utilities to filter and detect Tapkey-enabled Bluetooth devices.

Refer to the Apple Documentation for guidance on implementing a custom BLE scanner.

Filter for Tapkey-enabled Locks

To optimize a BLE scan, you can specify serviceUUIDs to filter for. The Tapkey Mobile SDK for iOS provides a list of serviceUUIDs for Tapkey-enabled devices, which can be extended or omitted depending on your use case.

// Retrieve the TKMBleAdvertisingFormat from the TKMServiceFactory instance
let advertisingFormat: TKMBleAdvertisingFormat = tapkeyServiceFactory.bleAdvertisingFormat

// Apply the serviceUUIDs when starting the scan for CBPeripherals
cbCentralManager.scanForPeripherals(withServices: advertisingFormat.serviceUuids)

Detecting Tapkey-enabled locks

Once the CBCentralManager discovers a CBPeripheral, the CBCentralManagerDelegate:didDiscover receives a CBPeripheral and advertisementData. You can use the TKMBleAdvertisingParser to identify whether it's a Tapkey-enabled device and which physicalLockId it has.

// Get the TapkeyBleAdvertisingParser
let advertisingFormat: TKMBleAdvertisingFormat = tapkeyServiceFactory.bleAdvertisingFormat
let advertisingParser: TKMBleAdvertisingParser = advertisingFormat.advertisingParser

// Parse the advertising data of the CBPeripheral
// If the parsed data is not nil, the discovered CBPeripheral is a Tapkey-enabled device
if let data = advertisingParser.parseAdvertisingData(advertisementData: advertisementData) {
    String physicalLockId = data.physicalLockId
}

A sample CBCentralManagerDelegate might look like this:

class CBCentralManagerDelegateImpl: NSObject, CBCentralManagerDelegate {

    @objc func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {

        if let data = self.advertisingParser.parseAdvertisingData(advertisementData: advertisementData) {
            // The TapkeyAdvertisingData contains the physicalLock id,
            // which can be used to map against the keys
            let physicalLockId = data.physicalLockId
        }
    }
}
self.cbCentralManagerDelegate = CBCentralManagerDelegateImpl()
self.cbCentralManaer = CBCentralManager(delegate: self.cbCentralManagerDelegate, queue: nil)

Communicate with discovered Lock

After discovering a Tapkey-enabled device, you can communicate with the lock using the CBPeripheral identifier of the CBPeripheral.

Don't store CBPeripheral identifier

It's important to note that the CBPeripheral identifier may be obfuscated by iOS for privacy reasons, so it's recommended not to store it for later use or on a different smartphone.

let cbPeripheral: CBPeripheral // The discovered CBPeripheral
let physicalLockId: String // The discovered physicalLockId

// Execute a command on the lock using the BleLockCommunicator
return bleLockCommunicator.executeCommandAsync(
    peripheralId: cbPeripheral.identifier,
    physicalLockId: physicalLockId,
    commandFunc: COMMAND_FUNC,
    cancellationToken: CANCELLATIONTOKEN)
    ...

BleLockScanner

The Tapkey Mobile SDK for iOS offers a default implementation of a BluetoothScanner designed to detect Tapkey-Enabled locks. This implementation can be used for a quick start, although it is advisable to utilize a Custom Implementation for better control and flexibility.

TKMBleLockScanner can detect Tapkey-Enabled locks nearby via Bluetooth Low Energy (BLE) by detecting if a Tapkey-Enabled lock is nearby, enters or leaves device's range. Locks that have not been detected within 10 seconds will be assumed as out of range.

Avoid cancelling interactions when Device is out of range

When a connection with a lock is open (e.g. during unlocking), the lock stops advertising. This might cause the TKMBleLockScanner to mark the lock as out of range. Therefore, don't cancel an ongoing command execution when the lock is marked as out of range.

The TKMBleLockScanner allows you to obtain the necessary instance of TKMServiceFactory for scanning Tapkey locks via BLE.

let scanner: TKMBleLockScanner = tapkeyServiceFactory.getBleLockScanner()

There are multiple utilities implemented that allow consumers to:

  • Start / Stop foreground scanner which is required to interact with neadby locks.
  • Observe changes in nearby locks presence.
  • Communicate with nearby locks.

Starting BLE scanning

To start the foreground scan, call startForegroundScan() on the TKMBleLockScanner instance. It returns an observer registration that can be used to stop scanning once finished.

bleScanObserverRegistration = scanner.startForegroundScan()

Stop BLE Scanning (close)

Make sure to stop scanning if the app does not require information about nearby locks anymore, such as when the user logs out or user is not required to see nearby locks by user experience.

// Stop scanning for nearby locks.
self.nearbyLocksObserverRegistration?.close()
self.nearbyLocksObserverRegistration = nil

Observe changes in device range (observable)

To observe locks entering or leaving the range of the device, use the observable property of the TKMBleLockScanner. This property returns an Observable that emits a list of locks whenever there is a change in the nearby locks.

nearbyLocksObserverRegistration = scanner.observable
    .addObserver { locks in
        // Handle nearby locks here, e.g. present them to the user.
    }

Get locks nearby (locks)

The property locks allows you to get a list of nearby locks at the current moment.

// Alternatively, locks can be used to get a list of nearby locks in this moment.
let nearbyLocks: [TKMBleLock] = scanner.locks

Check lock presence nearby (isLockNearby)

The method isLockNearby() checks if a specific lock is in the device's range.

Requirements

Scanning must still be ongoing via startForegroundScan(), otherwise, isLockNearby() will always return false.

let isNearby = scanner.isLockNearby(boundLock.physicalLockId)

One common use scenario for this method is checking for all keys the user holds, the corresponding nearby locks, and display them in the user interface.

Communicate with discovered Lock

When a Tapkey-enabled device was successfully discovered, the CBPeripheral identifier can be used to execute commands on this lock.

Don't store CBPeripheral identifier

It's important to note that the CBPeripheral identifier may be obfuscated by iOS for privacy reasons, so it's recommended not to store it for later use or on a different smartphone.

let tapkeyBleLock: TKMBleLock // The discovered ble lock
let peripheralId: UUID = tapkeyBleLock.peripheralId
let physicalLockId: String = tapkeyBleLock.physicalLockId

// Execute the command on the CBPeripheral with the help of the `BleLockCommunicator`
return bleLockCommunicator.executeCommandAsync(peripheralId: peripheralId, physicalLockId: physicalLockId, commandFunc: COMMAND_FUNC, cancellationToken: CANCELLATIONTOKEN)
    ...

Custom Environments

This feature allows third parties to decouple physical locks from the Tapkey environment. As a result, locks with custom environments are only visible within the same environment and remain inaccessible to apps using default or different environments.

Agreement required

Custom environments require that the lock has been manufactured for this specific environment upfront. Therefore, it requires a fitting use case and a special agreement.

The TKMBleAdvertisingFormatBuilder can be used to configure and build the TKMBleAdvertisingFormat for the environment. The specific values for the configuration will be assigned and provided by Tapkey

let bleAdvertisingFormat: TKMBleAdvertisingFormat = TKMBleAdvertisingFormatBuilder()
    .addV1Format(serviceUuid: ENVIRONMENT_SPECIFIC_SERVICE_UID)
    .addV2Format(domainId: ENVIRONMENT_SPECIFIC_DOMAIN_ID)
    .build()

Once the TKMBleAdvertisingFormat is built, it needs to be registered to the TKMServiceFactoryBuilder during the bootstrap:

let tapkeyServiceFactory: TKMServiceFactory  = TKMServiceFactoryBuilder()
    ...
    .setBluetoothAdvertisingFormat(bleAdvertisingFormat)
    .build()