Library to implement a HAP (HomeKit) controller
npm install hap-controller

This library allows you to build a HomeKit controller, capable of discovering and controlling both Wi-Fi and BLE devices.
Use npm to install the package:
``bash
npm install hap-controller
yarn add hap-controller
`
The IP and BLE APIs are very similar and only differ where it makes sense, given protocol differences.
`javascript
const {BLEDiscovery, IPDiscovery} = require('hap-controller');
const ipDiscovery = new IPDiscovery();
ipDiscovery.on('serviceUp', (service) => {
// ...
});
ipDiscovery.start();
const bleDiscovery = new BLEDiscovery();
bleDiscovery.on('serviceUp', (service) => {
// ...
});
bleDiscovery.start(); // pass true if disconnected events are needed
`
`javascript
const {GattClient, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port);
ipClient.identify().then(() => {
// ...
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral);
bleClient.identify().then(() => {
// ...
}).catch((e) => console.error(e));
`
`javascript
const {GattClient, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port);
ipClient.pairSetup(pin).then(() => {
// keep this data
console.log(JSON.stringify(ipClient.getLongTermData(), null, 2));
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral);
bleClient.pairSetup(pin).then(() => {
// keep this data
console.log(JSON.stringify(bleClient.getLongTermData(), null, 2));
}).catch((e) => console.error(e));
`
`javascript
const {GattClient, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port, pairingData);
ipClient.listPairings().then(() => {
// ...
}).catch((e) => console.error(e));
ipClient.removePairing(identifier).then(() => {
// ...
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral, pairingData);
bleClient.listPairings().then(() => {
// ...
}).catch((e) => console.error(e));
bleClient.removePairing(identifier).then(() => {
// ...
}).catch((e) => console.error(e));
`
`javascript
const {GattClient, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port, pairingData);
ipClient.getAccessories().then((accessories) => {
// ...
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral, pairingData);
bleClient.getAccessories().then((accessories) => {
// ...
}).catch((e) => console.error(e));
`
`javascript
const {GattClient, GattUtils, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port, pairingData);
ipClient.getCharacteristics(
['1.10'],
{
meta: true,
perms: true,
type: true,
ev: true,
}
).then((characteristics) => {
// ...
}).catch((e) => console.error(e));
ipClient.setCharacteristics({'1.10': true}).then(() => {
// ...
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral, pairingData);
bleClient.getCharacteristics(
[
{
serviceUuid: '...', // the "type" property
characteristicUuid: '...', // the "type" property
iid: 10,
format: 'bool', // if known
},
],
{
meta: true,
perms: true,
type: true,
ev: true,
}
).then((characteristics) => {
// ...
}).catch((e) => console.error(e));
bleClient.setCharacteristics(
[
{
serviceUuid: '...', // the "type" property
characteristicUuid: '...', // the "type" property
iid: 10,
value: GattUtils.valueToBuffer(true, 'bool'),
},
]
).then(() => {
// ...
}).catch((e) => console.error(e));
`
`javascript
const {GattClient, HttpClient} = require('hap-controller');
const ipClient = new HttpClient(id, address, port, pairingData);
ipClient.on('event', (ev) => {
// ...
});
ipClient.on('event-disconnect', (subscribedList) => {
// ...
});
let connection;
ipClient.subscribeCharacteristics(['1.10']).then((conn) => {
connection = conn;
// ...
}).catch((e) => console.error(e));
ipClient.unsubscribeCharacteristics(['1.10'], connection).then(() => {
// ...
}).catch((e) => console.error(e));
const bleClient = new GattClient(id, peripheral, pairingData);
bleClient.on('event', (ev) => {
// ...
});
bleClient.on('event-disconnect', (subscribedList) => {
// ...
});
bleClient.subscribeCharacteristics(
[
{
serviceUuid: '...', // the "type" property
characteristicUuid: '...', // the "type" property
iid: 10,
format: 'bool', // if known
},
]
).then(() => {
// ...
}).catch((e) => console.error(e));
bleClient.unsubscribeCharacteristics(
[
{
serviceUuid: '...', // the "type" property
characteristicUuid: '...', // the "type" property
},
]
).then(() => {
// ...
}).catch((e) => console.error(e));
`
Examples of all of the APIs can be found in the GitHub repo.
This is currently that way for some Tado Door Locks as example. They need to be paired using the Tado App which is somehow registering the device into Apple Home, but not via an official pair process.
Additional also Nuki 3 Locks (BLE) are not possible to pair because they use Hardware Authentication components that are not publicly documented by Apple.
For Netatmo a user found out how pairing could be possible when it had issue. See https://github.com/Apollon77/ioBroker.homekit-controller/issues/233#issuecomment-1311983379
* Try to reset the relevant BLE device with e.g. sudo hciconfig hci0 reset
* For issues also provide the output of uname -a and lsusb
* Low level BLE device log can be obtained using sudo hcidump -t -x >log.txt (in a second shell additionally to run the script)$3
* Basically if the error "pair-setup characteristic not found" pops up while trying to pair then the device do not support pairing via Homekit in it's current state. The adapter cn not do anything then!
* Please make sure to enter the PIN mit Dashes in the form "XXX-XX-XXX". Other formats should be declined by the library already by an error, but just to make sureDebugging
When you have issues and want to report an Issue (see below) then enhanced debug log is always helpful.Please start your application using
DEBUG=hap* node myprocess.jsand post the console log also in the issue. This will generate a log on protocol level.
Contributing
Please feel free to open an issue or a pull request if you find something that could use improvement.
For Issues please consider to directly provide debug loggins (see above).
Changelog
$3
* (dnicolson/Apollon77) Change noble to @stoprocent fork
* (Apollon77) prevent crash on import/require when no BLE device is not connected$3
* (Apollon77) Remove duplicate entries in characteristic list$3
* (Apollon77) Also return all found addresses and not just the first one
* (Apollon77) Update dependencies$3
* (Apollon77) Update Noble to fix CPU/Memory issue$3
* (Apollon77) Adjust some more places to finalize BigInt support$3
* (Apollon77) Return response body for getCharacteristics and subscribe/unsubscribe also when return code was 207 in IP mode$3
* (Apollon77) BREAKING: Returned data objects partially contain BigInt values (represented by bignumber.js instances) for iid/aid fields!$3
* (Apollon77) Upgrade noble$3
* (Apollon77) Downgrade noble again$3
* (Apollon77) Upgrade noble package$3
* (Apollon77) Make HTTP Client options optional$3
* (Apollon77) Initialize transaction ID randomly for BLE
* (Apollon77) Add experimental flag options.subscriptionsUseSameConnection for HTTP Client to use the same connection for subscriptions and for all other calls to only have one connection from controller to the device.$3
* (Apollon77) Add Host header to all HTTP calls because some devices seem to require it
* (Apollon77) Check that client was initialized before accessing the connected state$3
* (Apollon77) Add method "closePersistentConnection" to HTTPClient to allow a close of this connection (e.g. when commands get timeouts or such)$3
* (Apollon77) Introduce close method to tear down all connections that are potentially open
* (Apollon77) Use a persistent connection by default to prevent the need to verify the pairing for each call. Can be disabled using a new options parameter in constructor. You must call close()` if you do not need the instance any longer