Developing HomeKit accessories without Homebridge

Homebridge is a really nice piece of software, written in Node.js, with which you can emulate HomeKit accessories.

In other words: with Homebridge you can add devices without native Homekit support to Homekit. All you need is a plugin for the device. From the Homebridge website:

Homebridge allows you to integrate with smart home devices that do not natively support HomeKit. There are over 2,000 Homebridge plugins supporting thousands of different smart accessories.

Anyeone with Node.js knowledge, can create plugins for Homebridge. Either for personal use, or to share with other Homebridge enthusiasts via NPM.

HomeKit Accessory Protocol Specification

Homebridge implements the HomeKit Accessory Protocol Specification (HAP). Thanks to Apple releasing the HAP specification, developers can create their own non-commercial HomeKit accessories.

A difference with commercial HomeKit accessories is that the latter need certification from Apple under the MFI Program. If you add an uncertified accessory (for example via a Homebridge plugin), you’ll see a message in Homekit that te device is not certified. But beyond that, uncertified devices work perfectly fine in Homekit.

Working with HAP directly is not a trivial task. So the usual approach is to use a framework for all the HAP specific stuff. For Node.js for example you can use HAP-NodeJS, on which Homebridge is build.

Developing Homebridge plugins

So yes, Homebridge is a great way to run uncertified Homekit accessories “out-of-the-box”, thanks to the large number of available plugins, and a big community of Homebridge enthusiasts.

But there are also definitely some downsides, especially when developing you’re own plugins:

  • Homebridge plugins need to be installed globally, e.g. with npm install -g. If you want to migrate your Homebridge instance to another system, you have to manually go through your Homebridge config, and make a list of plugins you need to install on the new system.
  • Testing plugins while developing is relatively cumbersome. You need a local Homebridge (development) instance that runs your plugin. Every time you want to check a change, you need to restart the Homebridge instance, and check the Homebridge logs. There is no easy way of directly executing parts of a plugin for testing, without extensive mocking.

Also there are some considerations when running Homebridge:

  • Resources: Since Homebridge runs on Node.js, naturally you need to have working Node.js environment. This is fine for, for example, a home server or a Raspberry Pi. But can be problematic for embedded systems or other devices with limited resources, such as OpenWRT routers. Also in terms of memory, Homebridge is relatively resource intensive compared to compiled binaries.
  • Complex configuration: Homebridge act as a “bridge” in Homekit, which means it can host many child accessories/plugins. If you have many, the Homebridge config.json can get rather complex quickly.

Although, it’s fair to say that some of these issues are less of a problem if you use Homebridge Config UI X

Homekit development in Go

Because of these downsides to Homebridge (for me at least), for my own custom developed Homekit accessories, I stepped away from using Homebridge (though I still use Homebridge with a limited number of public plugins, like the excellent Homebridge Hue plugin).

As an alternative to Homebridge, I now mostly use the excellent Go framework Hc. HC is not a stand-alone server with plugins like Homebridge, but it’s a lightweight HAP framework, comparable to HAP-NodeJS. It abstracts the HomeKit Accessory Protocol and supports all HomeKit services and characteristics.

Creating a basic Homekit accessory with HC is as simple as:

package main

import (
    "log"
    "github.com/brutella/hc"
    "github.com/brutella/hc/accessory"
)

func main() {
    // create an accessory
    info := accessory.Info{Name: "My Lamp"}
    ac := accessory.NewSwitch(info)
    
    // configure the ip transport
    config := hc.Config{Pin: "00102003"}
    t, err := hc.NewIPTransport(config, ac.Accessory)
    if err != nil {
        log.Panic(err)
    }
    
    hc.OnTermination(func(){
        <-t.Stop()
    })
    
    t.Start()
}

The result of building an accessory with HC is a single binary without any dependencies. This is great for systems with limited resources (although Go binaries are relatively large, they are still a lot smaller than a complete Node.js environment).

Also in terms of memory, HC accessories are resource friendly. Memory foodprint is usually around 12 MB physical memory, versus 100 MB used by Homebridge in my configuration with just a few plugins.

But also if resources are not a problem, it’s really nice to be able to work with a single binary that’s easily portable between systems, without any additional installation steps on different machines (like setting up Homebridge).

updated_at 04-08-2020