iOS AutoAPI

The AutoAPI framework is meant as a helper for constructing and parsing capabilities/commands using the Auto API data protocol.

For every capability, there is a class with the same name, that exposes the identifier, state properties, message types and outbound commands when applicable.

Parsing

When receiving AutoAPI data, it should be handled using parseBinary(_:). The returned AACapability, if it was valid, can then be cast into a desired type and its values inspected.

static func parseBinary<C>(_ binary: C) -> AACapability? where C : Collection, C.Element == UInt8

Using the Command

There are a few ways to handle the parsed AACapability:

  • check the identifier property against a known one
  • try to cast the parsed value to a desired type (capability)

The latter is more useful, as it already produces a usable value.

It is worth remembering that a vehicle might not support all properties of a given capability.
Meaning all properties of the parsed capability might not be present – hence the reason for their optionality.

// Example #1
if let charging = AAAutoAPI.parseBinary(bytes) as? AACharging {
    let isCharging = charging.status?.value == .charging
}

// Example #2
switch AAAutoAPI.parseBinary(bytes) {
case let rooftopControl as AARooftopControl:
    let isDimmed = rooftopControl.dimming?.value == 1.0

case let vehicleStatus as AAVehicleStatus:
    let isElectric = vehicleStatus.powertrain?.value == .allElectric

default:
    break
}

Capabilities

Handling Capabilities goes the same way as for other commands. First one needs to get the AACapabilities object, and then access its array of supported capabilities.

if let capabilities = AAAutoAPI.parseBinary(bytes) as? AACapabilities {
    let supportedCapabilities = capabilities.capabilities?.compactMap { $0.value }

}

A single AACapabilityValue consists of:
.capabilityID it is referring to
.supportedPropertyIDs are available properties in the vehicle

struct AASupportedCapability {
    let supportedPropertyIDs: [UInt8]
    let capabilityID: UInt16
    ...
}

State

State is sent from the vehicle to the device – either inside a Vehicle Status capability, after a change to the state's values, or as a reply to a request. The state is represented by the returned AACapability subclass (i.e. Lights, Engine, Fueling) which has the corresponding properties of the capability.

// Returned AALights-capability subclass example
class AALights: AACapability {
    var ambientLightColour: AAProperty<AARGBColour>? { get }
    var frontExteriorLight: AAProperty<FrontExteriorLight>? { get }
    let var interiorLights: [AAProperty<AALight>]? { get }
    ...
}

To request all the capabilities, one should send the -getVehicleStatus() bytes. The response of type AAVehicleStatus has an array of states [AACapability].

let bytes: [UInt8] = AAVehicleStatus.getVehicleStatus()

Vehicle Status should be accessed sparingly – i.e. after login or boot. Commonly one should request a specific capability, which is done in a similar manner.

// For requesting Rooftop Control state
let bytes: [UInt8] = AARooftopControl.getRooftopState()

When multiple states are required, but not as many as in Vehicle Status, the AAMultiCommand should be used.

let cmdA = AACharging.getChargingState()
let cmdB = AAClimate.getClimateState()

let bytes: [UInt8] = AAMultiCommand.multiCommand(multiCommands: [cmdA, cmdB])

Sending Commands

Majority of capabilities are able to send data to a vehicle.
One can always combine the bytes itself. This can be done to have full control of the outgoing data.
Easier would be to access the chosen capabilty's type-methods for getting the needed command to send to the vehicle (through bluetooth or telematics).

Some "subcommands" are simple, and don't need any additional input – others are more complex, requiring extra parameters. Those extra parameters can be in the form of a type that contains those values, or just a simple one like UInt8 or an enum.

// Subcommands
class AARooftopControl: AACapability {
    ...

    static func getRooftopState() -> [UInt8]

    static func controlRooftop(dimming: AAPercentage?,
                               position: AAPercentage?,
                               convertibleRoofState: ConvertibleRoofState?,
                               sunroofTiltState: SunroofTiltState?,
                               sunroofState: SunroofState?) -> [UInt8]?
}

// Usage
let getStateBytes = AARooftopControl.getRooftopState()

let controlBytes = AARooftopControl.controlRooftop(dimming: 1.0,
                                                   openClose: 0.0,
                                                   convertibleRoofState: nil,
                                                   sunroofTiltState: nil,
                                                   sunroofState: nil)

Unit Types

All properties/types that expose a measurable value, do this using the native Dimension type.
Measurement types consist of different units - meaning a single measurement can be done different units.
Every "type" of measurement can be transmitted in any of it's units and the desired unit accessed.
Most of Apple's units are supported, with a few additions added for AutoAPI.

// Climate state received beforehand
let batteryTemp = climate.insideTemperature?.value?.value
let batteryTempUnit = climate.insideTemperature?.value?.unit
let batteryTempF = climate.insideTemperature?.value?.converted(to: .fahrenheit).value

// Sending measurements
let cmdBytes = AAClimate.setTemperatureSettings(driverTemperatureSetting: .init(value: 21.0, unit: .celsius))

Availability

Every property (with exeptions for "Fundamental" capabilities) has availability information tied to it.
This contains the update rate, rate limit and wheter the limits are for an app or a vehicle.
To access that information, capabilities expose an "availability" method to generate the command to send to the vehicle.
The received response will be the capability with it's properties having a populated availability component.

// Availability command
let command = AADoors.getDoorsStateAvailability()

// Command sent, received and parsed
let doors: AADoors = ...
let propAvailability: AAAvailability? = doors.locksState?.availability

let isUpdatedOnChange = propAvailability?.updateRate == .onChange
let rateLimit = propAvailability?.rateLimit.value
let rateLimitUnit = propAvailability?.rateLimit.unit