-
-
Notifications
You must be signed in to change notification settings - Fork 630
Using HAP NodeJS as a library
Before reading this guide, you should already have read the wiki page about HomeKit Terminology.
In this guide, we are going to create a NodeJS module using HAP-NodeJS as a library to expose a simple HomeKit Light accessory.
It basically resembles the light example provided in the examples
repository. So feel free to have a look at that as well, especially if you want an example written in Typescript, as this
guide will only show an example written in JavaScript for the sake of a simpler project setup.
First, we need to set up our toolchain. For this, we will install Node.js and the Node Package Manager (npm)
to manage all dependencies for our project. This guide does currently not provide any installation instructions
for Node.js for every platform. A good starting point is to just navigate to the Node.js website.
You may want to grab the Long-Term Support (LTS) version.
You may want to ensure that you install the minimum required node version required by HAP-NodeJS
.
After that, node
and npm
should be installed on your computer. You can verify that by running the following commands.
A similar output should be printed.
$ node -v
v13.13.0
$ npm -v
6.14.4
To write our code, you can use an IDE like Visual Studio Code, WebStorm or just a simple text editor like Atom or Notepad++.
Now we want to create a new project by creating a new folder and opening your IDE in it. Some IDEs give prompts to create
new projects and will open it automatically. In that case make sure you create an empty node.js project.
You may additionally want to open your project folder in the terminal.
We will create two files for our project: package.json
and light.js
.
Below are two sections where the complete content of each file is presented and then explained below line for line.
So feel free to just copy the content, read through the explanations below and maybe follow additional instructions.
The package.json
is a file describing your npm module and must be present in every project.
It defines stuff like the entry point in your app or dependencies. For more details visit the
official documentation for the package.json
file.
It is written in JavaScript Object Notation.
{
"name": "your-project-name",
"version": "1.0.0",
"description": "An example description for your example project",
"main": "light.js",
"author": "Example Name <mail@example.com>",
"license": "ISC",
"dependencies": {
"hap-nodejs": "latest"
}
}
An explanation for the relevant fields used above:
-
name
: this property sets the name of your module. It is used if you want to publish your package to the npm registry -
version
: this property is used to define the current version and should use Semantic Versioning -
description
: just a short descriptive text of your module -
main
: this is the main entry point for your module. This is where execution begins. We set it here to thelight.js
file we will create later. -
author
: in this property you should put in your name or username to mark your ownership -
license
: define the license the project is published with. For our examples the homebridge team uses the ISC license. -
dependencies
: this is one of the more important properties. Here we define dependencies of other npm modules. For our light example we only have a dependency ofhap-nodejs
with the versionlatest
. Fell free to replace that with any specific version ofhap-nodejs
if you require that.
After we created that you want to open some sort of terminal and run the following command in the same directory as
the package.json
. This will install any dependencies (or dependencies of dependencies) required by your module.
npm install
light.js
contains all code need to create our example light accessory.
const hap = require("hap-nodejs");
const Accessory = hap.Accessory;
const Characteristic = hap.Characteristic;
const CharacteristicEventTypes = hap.CharacteristicEventTypes;
const Service = hap.Service;
// optionally set a different storage location with code below
// hap.HAPStorage.setCustomStoragePath("...");
const accessoryUuid = hap.uuid.generate("hap.examples.light");
const accessory = new Accessory("Example Accessory Name", accessoryUuid);
const lightService = new Service.Lightbulb("Example Lightbulb");
let currentLightState = false; // on or off
let currentBrightnessLevel = 100;
// 'On' characteristic is required for the light service
const onCharacteristic = lightService.getCharacteristic(Characteristic.On);
// 'Brightness' characteristic is optional for the light service; 'getCharacteristic' will automatically add it to the service!
const brightnessCharacteristic = lightService.getCharacteristic(Characteristic.Brightness);
// with the 'on' function we can add event handlers for different events, mainly the 'get' and 'set' event
onCharacteristic.on(CharacteristicEventTypes.GET, callback => {
console.log("Queried current light state: " + currentLightState);
callback(undefined, currentLightState);
});
onCharacteristic.on(CharacteristicEventTypes.SET, (value, callback) => {
console.log("Setting light state to: " + value);
currentLightState = value;
callback();
});
brightnessCharacteristic.on(CharacteristicEventTypes.GET, (callback) => {
console.log("Queried current brightness level: " + currentBrightnessLevel);
callback(undefined, currentBrightnessLevel);
});
brightnessCharacteristic.on(CharacteristicEventTypes.SET, (value, callback) => {
console.log("Setting brightness level to: " + value);
currentBrightnessLevel = value;
callback();
});
accessory.addService(lightService); // adding the service to the accessory
// once everything is set up, we publish the accessory. Publish should always be the last step!
accessory.publish({
username: "17:51:07:F4:BC:8A",
pincode: "678-90-876",
port: 47129,
category: hap.Categories.LIGHTBULB, // value here defines the symbol shown in the pairing screen
});
console.log("Accessory setup finished!");
Before I explain the code in more detail you may want to just quickly test it and run it using the following command.
The default pairing code is 678-90-876
.
node light.js
Below we will discuss some parts of the code in more detail.
const hap = require("hap-nodejs");
const Accessory = hap.Accessory;
const Characteristic = hap.Characteristic;
const CharacteristicEventTypes = hap.CharacteristicEventTypes;
const Service = hap.Service;
The first few lines are all about importing the HAP-NodeJS
library. It saves everything exported from the library into
the variable named hap
. The other variables are just there to make the code what follows cleaner. If we want to access
the accessory class we do not need to always write hap.Accessory
instead we will only write Accessory
. We could give
it a totally different name, but using the same name as defined by HAP-NodeJS
improves code readability a lot.
const accessoryUuid = hap.uuid.generate("hap.examples.light");
const accessory = new Accessory("Example Accessory Name", accessoryUuid);
This two lines create a new accessory object from the Accessory
class (saved into the accessory
variable).
The first argument passed is the name of the accessory displayed in the pairing screen, the second argument is the uuid.
One thing to point out is the first line, which generates a Universally Unique Identifier.
In this example it is derived from the generator data "hap-examples.light"
. The same UUID will be generated for the
same input. This UUID is used by HAP-NodeJS
to uniquely identify an accessory and its related data stored on disk.
So it must not change over the lifespan of an accessory, otherwise your configuration of the services exposed by this
accessory may be reset in the Home app (like room assignments, automations and scenes).
const lightService = new Service.Lightbulb("Example Lightbulb");
let currentLightState = false; // on or off
The first line creates a new service, to be specific a Lightbulb
service. The first argument is the name of the service
(this is actually the name displayed in the tile in the Home app). All services supported by HAP-NodeJS
can be accessed
in that way Service.*
.
A list of all services and characteristics can be found in
ServiceDefinitions.ts and CharacteristicDefinitions.ts respectively. Just open them and
search for Lighbulb
and you will find the service definition of the Lighbulb
service with all its required and
optional characteristics, like the On
characteristic (or the optional Brightness
characteristic present in the code above).
We only cover the On
characteristic in the detailed explanation, but you easily should be able to understand the code
above for the Brightness
characteristic as well, as it is pretty similar.
The second line is just a variable (a variable whose value can be changed, thus let
and not const
) where we store
the current on/off state of the lightbulb.
const onCharacteristic = lightService.getCharacteristic(Characteristic.On);
// with the 'on' function we can add event handlers for different events, mainly the 'get' and 'set' event
onCharacteristic.on(CharacteristicEventTypes.GET, callback => {
console.log("Queried current light state: " + currentLightState);
callback(undefined, currentLightState);
});
onCharacteristic.on(CharacteristicEventTypes.SET, (value, callback) => {
console.log("Setting light state to: " + value);
currentLightState = value;
callback();
});
This part is where the real magic is happening. First of all we are using calling the getCharacteristic
method of
our lightService
object to get a reference to the characteristics object for the On
characteristic (Characteristic.On
).
Using the on method we then subscribe two
event listeners, one for the 'get'
event and one for the 'set'
event.
The 'get'
is called whenever a HomeKit controller request the current value of the characteristic. A callback
is passed
to the event handler as the first argument. The callback expects an Error object as the first argument if an error occurred or
undefined as the first argument and the current value as the second argument to return the current value. The callback
should be called as soon as possible.
The 'set'
is called whenever a HomeKit controller sets a new value for the given characteristic. The newly set value
is passed as the first argument. A callback
is passed to the event handler as the second argument.
The callback expects an Error object as the first argument if an error occurred.
accessory.addService(lightService); // adding the service to the accessory
After setting up our service we add it to our accessory by calling the addService
method of the accessory
.
// once everything is set up, we publish the accessory. Publish should always be the last step!
accessory.publish({
username: "17:51:07:F4:BC:8A",
pincode: "678-90-876",
port: 47129,
category: hap.Categories.LIGHTBULB, // value here defines the symbol shown in the pairing screen
});
After everything is set up, the last thing we will do is call the publish
method of the accessory
.
The method takes a PublishInfo
, basically an object full of options, as the first argument. The most important
options are highlighted here:
-
username
: this value is basically a mac address and must be in that format. It is used as an identifier of the HAP server by HomeKit. It can be any random mac address, but must be persistent over the lifespan of the accessory (asHAP-NodeJS
even uses this to identify all stored files associated to this HAP server). -
pincode
: This is the eight digit pairing code. It must be specified in the exact same pattern as shown above. -
port
: This specifies the port the HAP (http) server is running on. Leave it out (or specify a value of0
) to use a random (free) port on start up. -
category
: This defines the category of the HAP server. It basically is just used to display the icon in the pairing menu.
So we basically covered now all important parts of the code. Let's start our accessory with the following command:
node light.js
Now you can navigate into the Home App and add the accessory to your Home with the provided setup code.
Horray you managed to add your first custom HomeKit accessory!
To get you started with HAP-NodeJS development, try to modify the provided example.
As defined by the HomeKit Accessory Protocol (HAP) specification, the Lightbulb
service has three
additional optional characteristics: Hue
and Saturation
, to provide coloring functionality, or
ColorTemperature
, to change the temperature of a white bulb.
Try adding one of those characteristics and play around with it in the Home App.
Additionally, you can check out different types of services and their set of characteristics and
explore how they react in the Home App.