More reliable Homekit alternative using a simple nodejs bridge

A number of us have been finding the openHAB homekit support unreliable, with devices vanishing or becoming non-response on a regular basis. I’ve fixed this with a quick hacked-together bridge using nodejs (running on the same Ubuntu PC as openHAB). No reason this shouldn’t also work on a pi

I have a simple use case. I want to set up scenes (e.g. “good morning”) which we can speak into Siri and will then trigger openHAB switches which then run the scene. I don’t need to set temperatures (openHAB does that automatically) or read anything (a display is easier). The approach below could be extended to do all of that too, but you’d need more nodejs skill than I have (basically none at all).

Quick summary:

Step 1: install npm

sudo apt-get install nodejs

Step 2: install HAP-NodeJS and dependencies

follow the instructions here

you should be able to test it with:

node BridgedCore.js and see the server running. If you then go to a homekit device, you can add the server as a new accessory. The PIN will be 031-45-154

Stop the server

Step 3

You can then go into the accessories folder and modify the examples that are in there to create one accessory file for each device. An example accessory file for me, throwing the TestSwitch device in openHAB, is as follows, based on the ‘outlet’ example:

var Accessory = require('../').Accessory;
var Service = require('../').Service;
var Characteristic = require('../').Characteristic;
var uuid = require('../').uuid;
var err = null; // in case there were any problems
const http = require('http')

// here's a fake hardware device that we'll expose to HomeKit
var FAKE_OUTLET = {
    setPowerOn: function(on) {
    console.log("Turning the outlet %s!...", on ? "on" : "off");
    if (on) {
          FAKE_OUTLET.powerOn = true;
          if(err) { return console.log(err); }
          console.log("...test outlet is now on. go!");
          http.get('http://localhost:8080/classicui/CMD?TestSwitch=ON');
    } else {
          FAKE_OUTLET.powerOn = false;
          if(err) { return console.log(err); }
          console.log("...test outlet is now off.");
          http.get('http://localhost:8080/classicui/CMD?TestSwitch=OFF');
    }
  },
    identify: function() {
    console.log("Identify the outlet.");
    }
}

// Generate a consistent UUID for our outlet Accessory that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the accessory name.
var outletUUID = uuid.generate('hap-nodejs:accessories:Outlet');

// This is the Accessory that we'll return to HAP-NodeJS that represents our fake light.
var outlet = exports.accessory = new Accessory('Outlet', outletUUID);

// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
outlet.username = "1A:2B:3C:4D:5D:FF";
outlet.pincode = "031-45-154";

// set some basic properties (these values are arbitrary and setting them is optional)
outlet
  .getService(Service.AccessoryInformation)
  .setCharacteristic(Characteristic.Manufacturer, "Oltica")
  .setCharacteristic(Characteristic.Model, "Rev-1")
  .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW");

// listen for the "identify" event for this Accessory
outlet.on('identify', function(paired, callback) {
  FAKE_OUTLET.identify();
  callback(); // success
});

// Add the actual outlet Service and listen for change events from iOS.
// We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js`
outlet
  .addService(Service.Outlet, "test switch") // services exposed to the user should have "names" like "Fake Light" for us
  .getCharacteristic(Characteristic.On)
  .on('set', function(value, callback) {
    FAKE_OUTLET.setPowerOn(value);
    callback(); // Our fake Outlet is synchronous - this value has been successfully set
  });

// We want to intercept requests for our current power state so we can query the hardware itself instead of
// allowing HAP-NodeJS to return the cached Characteristic.value.
outlet
  .getService(Service.Outlet)
  .getCharacteristic(Characteristic.On)
  .on('get', function(callback) {

    // this event is emitted when you ask Siri directly whether your light is on or not. you might query
    // the light hardware itself to find this out, then call the callback. But if you take longer than a
    // few seconds to respond, Siri will give up.

    var err = null; // in case there were any problems

    if (FAKE_OUTLET.powerOn) {
      console.log("Are we on? Yes.");
      callback(err, true);
    }
    else {
      console.log("Are we on? No.");
      callback(err, false);
    }
  }); 


For each device you add you will need to change:

  • the name of the file - obviously. It must end _accessory
  • the subject of the http.get command - here it’s my openHAB item TestSwitch
  • the console logging, to reflect the actual item
  • the UUID - currently hap-nodejs:accessories:Outlet. For mine I simply increment a number at the end of this, i.e. hap-nodejs:accessories:Outlet2, hap-nodejs:accessories:Outlet3 and so on
  • the name of the device - this goes into the .addservice line, where it currently says “Test Switch”

then once you’ve finished setting your accessories files you should be able to start node BridgedCore.js and control everything via homekit and Siri.

I found after amending the accessories files I would get errors when running BridgedCore.js - the solution to this was to rm HAP-NodeJS/accessories/._*

Once it’s all working you can have BridgedCore.js autostart - I did this using pm2 (which elegantly turns nodejs scripts into services).

Thanks for sharing, but please note that the CMD servlet is not official and could be removed someday. It would be better to adopt your solution to use the REST API.

Thanks - yes, it’s on my radar that at some point I need to go through all my various servers/apps and update them to use REST properly.

I just mentioned this cause other users will use your solution, so it would be better to only post solutions that might not be deprecated someday without pre notice.

Fair point - although right now this is all I know how to do!

Googled a bit, what about using XMLHttpRequest

var destination = "http://192.168.0.xx:8080/rest/items/test";

var xhr = new XMLHttpRequest();
xhr.open("POST", destination, true);

var command = "ON";
xhr.send(command);

to send command ON to Item test

1 Like