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