This is a Rust (no-alloc, no_std) implementation of the HomeKit Accessory Protocol. Specifically aimed at running within embassy, currently it only implements the BLE transport, building on the trouble Bluetooth stack. There are many HAP implementations available, a few implement the logic needed to create an IP peripheral but I could not find any that implemented a BLE one, so I wrote one to do just that. This is useful if an WiFi network is not available, for example for a moving sensor like in a car.
- It roughly follows the reference implementation, and thus has the same license.
- It uses trouble for interacting with Bluetooth.
- It was originally developed in this repo, but seemed worthwhile to split out.
- Pairing fully works, including pair resume.
- Enumeration of the device works.
- Read and Writes over the HAP session work.
- Services & Characeristics can be defined out-of-crate.
- Status number updates and advertising.
While this is a (relatively?) feature-complete implementation, it has not been thoroughly tested or checked for correctness and security.
The main test for the BLE transport right now is in ./micro_hap/src/ble/test.rs. This contains a full pairing procedure, pair verify and toggling of the lightbulb. This tests pretty much everything except pairing storage, session invalidation and of course any messages that were not encountered in the recording. This data was captured using this repo which used the reference implementation to create a BLE peripheral from my desktop. Actions in this main test are; pair, toggle a few times, disable & enable bluetooth, toggle a few more times.
Many parts were only tackled when I encountered a new concept in building out the logic against the recording.
To help people understand the code and the concepts, here's an information dump:
- An accessory is comprised of Services, the
accessory_information,protocolandpairingservices are required. - A service has attributes (in the BLE transport moddeled as characteristics).
- HomeKit's pairing, session and permission management has nothing to do with BLE's equivalents.
- On the BLE level, all characteristics are read/write without a secure BLE session.
- Attributes are interacted with through a BLE write AND read, together they form a request.
- For example, toggling the lightbulb performs a BLE write on the
Onattribute of theLightbulbService, the response of this request is verified with a BLE Read on the same characteristic. - The HAP protocol is merely transported over the BLE gatt write/reads.
- The Trouble GATT server is merely a facade to provide the correct characteristics & services.
- The
HapPeripheralContext::gatt_events_taskis the entry point for the bluetooth transport. - The
PlatformSupportis the platform interface / key-value store and auxiliary function support like random bytes. - The
AccessoryInterfaceis the interface the accessory's endpoints, so for example the actual lightbulb. - A pairing is effectively an exchange of public keys, after which a session is established through pair verify.
- The 'entry point' to all the logic is the
process_gatt_eventmethod of theHapPeripheralContext. - On error handling. The pairing handling returns
PairingErrorwhich provide relevant information what went wrong. Theblelayer changes this into appropriateble::pdu::HapBleStatusErrorerrors that are responded to the client, only real trouble errors are bubbled up to the calling code. - The
InternalErroris internal, some of its values end up being HAP protocol status results, others end up bubbling through to the user application likeInterfaceError. - Only a single controller can be connected at a time.
Clean up error handling.n (snafu / thiserror?)Correctly return HAP errors, instead of failing the BLE request.Any errors currently drop the request instead of returning the correct HAP error code.Figure out whenLooks like a sentinel?MaxProceduresshould be returned..Figure out how values that proactively change work (like temperature sensor), how to notify?When the state on the accessory changes, it is supposed to increment the global state number.The global state number is in the advertisement, this is how iOS knows it should connect to retrieve the state.Add periodic 'service' method to handle global state counter, advertisement and expiring timed writes to free slots.How do the advertisements actually work?Broadcasts of changed values still result in a reconnect, withYes: Disconnected events should only be used to reflect important changes in the accessory, and when the characteristic changes while no controllers are connected, will reconnect, see extensive comment inGenerateBroadcastEncryptionKeyandCharacteristicConfigurationRequest, why? Checked the program flow, seems to match. This also updates the GSN expiry counter and sets up a new key, is this just intentional if you don't have aHome Hub? Or perhaps the intent is that the controller reconnects to the accessory after a broadcast because other characteristics may have also changed?example_temp_sensor.And what about notify while a connection is active?Send indicate over BLEClear the session, pair_verify and pair_setup on disconnect, currently it requires a powercycle to reset state.Can pair numerous times now.- Numerous comments starting with
// NONCOMPLIANCEwhere I ignored something that should probably be handled. Did a pass, in general most of them are fine or behaviour is covered by the tests. How much is shared between BLE & IP? Can we implement IP as well with minimal work? It's not a trivial amount of work.Make the accessory interface async.it is now, the RPi Pico 2w example uses the built-in led, toggling requires an async function.Modify/add second example to show how to add a service, ensure common stuff is shared.Perhaps a commissioning binary to create the salt & verifier, using thePairingCodetype that now exists.Make theAsync now, butPlatformSupportmethods async.PlatformSupport: SendbecauseSendis on all futures, this likely needs some changes in the future as we probably can'tSendperipherals? Maybe just drop the bound?Build outcharacteristic_signature_requestto support range and step, probably needed for hue.Verify pair resume actually works, keep a list of sessions...Makepairingandpair_verifymodules crate-private? They are now, and refactored, error is still public.Implement TimedWrite request.ImplementCharacteristicExecuteWrite.Do we ever need to support interleaved requests? So write on characteristic 1, write on characteristic 2, read on 1, read on 2. -> Probably not.ImplementDoneSetupInfo's serialize/deserialize, this issue is helpful.Bluetooth session cache for session resume.Cache exists, use during initial setup works, works for lightbulb, not for thermometer between restarts. Issue was that the device id shouldn't change!The services made withThere's a builder now for the attribute table and HAP services that avoids the trouble macro's.#[gatt_service(..have aStaticCellin them, as such they can't be instantiated twice. This makes the mutually exclusive lightbulb example cumbersome.File PR into trouble to addMerged, consuming this commit.indicatefunctionality, becausenotify != indicate.Go through all the log / defmt prints and ensure the level makes sense.The CharacteristicDoesn't seem to exist, only in relation to target temperatures?current_temperature(0x0011) always displays temperatures in 0.5 increments, can a manual temperature actually show digits?
This example is intended to run a Linux host, similar to trouble's linux examples.
This is the main binary used for debugging & development of the actual interaction with iOS.
Build this with cargo b, it has to be ran as root to bind the linux HCI interface.
It also requires freeing that interface, usually by disabling your bluetooth service with service bluetooth stop.
This contains multiple examples:
example_lightbulbThe default lightbulb example with just an on-off toggle.example_lightbulb_builderThis uses the builder to create the services and characteristics.example_rgbThis contains two lightbulbs, one that allows modifying the color temperature, and one that facilitates hue, staturation and brightness configuration.example_temp_sensor: This fakes a temperature sensor and broadcasts its value.example_temp_sensor_builderThis uses the builder to create the services and characteristics.example_mutually_exclusive: This contains two lightbulbs, which uses indicate to disable the other lightbulb if you try to switch both on.example_mutually_exclusive_builder: This uses the builder to deduplicate the previous example.
This example is a gutted version of the project I'm originally developing this for. It contains a bunch of stuff that is not really relevant for the HAP example, but the current state at least puts a working bare metal example in the repo. It toggles the default LED on the pcb through the cyw43 chip.
This example is independent from the workspace, such that cargo b at the workspace level doesn't result in both log and defmt being enabled.
License is LICENSE-APACHE since it is based on HomeKitADK.