HomeKit Accessory Protocol (HAP)
README.md

hap #

HomeKit Accessory Protocol (HAP) controller for OCaml.

Smart plugs, lights and sensors sold for Apple Home speak HAP on the local network, so a program that wants to switch them without going through an iPhone has to implement the controller side itself. hap finds accessories by mDNS, pairs with one once using its setup code (SRP-6a pair setup), and on every later connection opens an encrypted session (Curve25519 pair verify, then ChaCha20-Poly1305) to read and write its characteristics. SRP-6a (RFC 5054) is a password-authenticated key exchange: the controller proves it knows the accessory's 8-digit setup code without sending it, both ends derive a shared key, and under that key they exchange long-term Ed25519 public keys. Pair verify then runs an ephemeral Curve25519 Diffie-Hellman exchange signed with those long-term keys and derives the ChaCha20-Poly1305 session keys from it. A characteristic is one readable or writable value of an accessory, such as an outlet's On state. Pairings are stored under ~/.hap/pairings/. The library runs on Eio. Outlets have shortcut functions, and other accessory types go through the generic characteristic calls.

Installation #

Install with opam:

$ opam install hap

If opam cannot find the package, it may not yet be released in the public opam-repository. Add the overlay repository, then install it:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install hap

Usage #

let run () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run @@ fun sw ->
  let net = Eio.Stdenv.net env in
  let clock = Eio.Stdenv.clock env in
  let fs = Eio.Stdenv.fs env in
  (* Discover HomeKit accessories on the network. *)
  match Hap.discover ~sw ~net ~clock ~timeout:5.0 () with
  | [] -> ()
  | (a : Hap.accessory_info) :: _ ->
      (* Pair with an accessory (one-time setup). *)
      (match
         Hap.pair_setup ~net ~sw ~clock ~ip:a.ip ~port:a.port ~pin:"031-45-154"
       with
      | Ok _pairing -> ()
      | Error (`Msg m) -> failwith m);
      (* Control an outlet (uses the saved pairing). *)
      match Hap.turn_on_outlet ~net ~sw ~clock ~fs a.ip with
      | Ok () -> ()
      | Error (`Msg m) -> failwith m

API #

  • Discovery: Hap.discover finds accessories by mDNS, Hap.accessory_info reads one accessory's advertisement, and Hap.category_name names its category.
  • Pairing: Hap.pair_setup pairs once with the setup code, and Hap.pair_verify opens an encrypted session from a stored pairing.
  • Pairing storage: Hap.save_pairing_by_id writes a pairing, Hap.pairing_by_id reads one back by accessory id, and Hap.pairing_for_ip finds the one for an address.
  • Control: Hap.accessories lists services and characteristics, Hap.characteristics reads values and Hap.put_characteristic writes one; Hap.turn_on_outlet, Hap.turn_off_outlet and Hap.toggle_outlet are shortcuts for outlets.
  • Encoding: Hap.Tlv encodes and decodes HAP's TLV8 format, with its type constants in Hap.Tlv_type and error codes in Hap.Hap_error.

License #

ISC. See LICENSE.md.