iOS Bluetooth Tutorial

In addition to interacting with vehicles by using Telematics (through an Internet connection), it's possible to use Bluetooth 4.0 to send and receive commands. This has the benefit of being able to receive new data from the vehicle pushed directly to the device as soon as states change.

Requirements

Bluetooth 4.0 Low Energy is supported on all devices running iOS 10 or newer.
The emulator uses Bluetooth connectivity through the Web Bluetooth initiative. At the moment it's only supported in Google Chrome on macOS, Linux and Chromebooks.

Bluetooth Broadcasting

The Bluetooth broadcasting component of HMKit is used to create a Bluetooth link with the vehicle.

The main access point is the class HMLocalDevice, which is used to set up your device to be visible to vehicles and accept incoming connections called HMLinks. A link handles the authorisation and other functionalities between the device and the vehicle.

The access to HMLocalDevice is provided via the singleton .shared.

To receive a link or device state change events, an HMLocalDeviceDelegate object has to be set. After the delegate has been set and the device initialised-configured, broadcasting can be initiated.

HMLocalDevice.shared.delegate = self as? HMLocalDeviceDelegate

do {
    try HMLocalDevice.shared.startBroadcasting()
}
catch {
    print("Start Broadcasting error: \(error)")
}

Delegate method localDevice(didLoseLink:) is invoked when the link has disconnected.

func localDevice(didLoseLink link: HMLink) {
    // Application code after a link has disconnected from the device
}

Interacting With a Vehicle

After an HMLink has been received via HMLocalDeviceDelegate, it is possible to start managing the incoming events and send messages to the vehicle. To receive HMLink events, an HMLinkDelegate object has to be set.

func localDevice(didReceiveLink link: HMLink) {
    // "Register" for callbacks from the link (super necessary)
    link.delegate = self as? HMLinkDelegate
}

After a link has been lost, you have to take care to clear the delegate.

func localDevice(didLoseLink link: HMLink) {
    // Stop observing HMLink events
    link.delegate = nil
}

Connecting from the Emulator

To connect to the iOS device using the emulator, use the bottom left Bluetooth button. This will drop down the Bluetooth connect panel in Google Chrome and you should choose the device starting with "HM".

Responding to an Authorisation Request

It is possible for a vehicle to initiate authorisation of the device. If there's an incoming authorisation request from the vehicle, the SDK queries for confirmation from the delegate. This can be handled directly in code or more commonly, displayed to the user for confirmation.

If the user does not respond before the timeout interval, the request fails with a timeout error.

If the user tries to respond after the timeout interval, an exception is thrown.

func link(_ link: HMLink, authorisationRequestedBy serialNumber: [UInt8], approve: @escaping Approve, timeout: TimeInterval) {
    do {
        // Approving without user input
        try approve()
    }
    catch {
        print("Authorisation request timed out")
    }
}

Receive a Command

If a link receives a command from the vehicle, a response is usually expected for it. The response bytes are queried from the delegate.

The content of the command should correspond to the Auto API.

Read more about the Auto API library in iOS.

func link(_ link: HMLink, commandReceived bytes: [UInt8], contentType: HMContainerContentType, requestID: [UInt8]) {
    guard let doors = AAAutoAPI.parseBinary(bytes) as? AADoors else {
        return print("Failed to parse data")
    }

    print("Got the doors capability \(doors).")
}

Send a Command

It's also possible to send commands to the vehicle, which is achieved only if the link is in an authenticated state.

Sending a command is done with the .send(command:completion:) method. Note that it's only allowed to send one command at a time. Queueing of several commands in a sequence has to be done on the application level.

The command bytes should correspond to the Auto API. To help, every Auto API capability has helper methods to construct it.

Read more about the Auto API library in iOS.

let bytes = AADoors.getDoorsState()

do {
    try link.send(command: bytes) { result in
        switch result {
        case .failure(let error):
            print("Error sending command: \(error)")

        case .success:
            print("Command sent")

            // Bluetooth response is received async in the link's delegate
        }
    }
}
catch {
    print("Failed to send command: \(error)")
}

Handling errors

Most methods exposed to the user throw and the error can be handled directly.

Sometimes an HMLink might encounter an error async. In these cases the error is returned to it's delegate.

func link(_ link: HMLink, receivedError error: HMProtocolError) {
    // Handle the error
}