Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jasoncodes committed Sep 15, 2016
0 parents commit cc0e5da
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
26 changes: 26 additions & 0 deletions README.markdown
@@ -0,0 +1,26 @@
# homebridge-bluetooth-tag

A Homebridge plugin for exposing Bluetooth Low Energy button tags as switches in HomeKit.

## Supported Devices

This plugin has been tested with "iTag" bluetooth trackers which can be found on eBay for about AU$10.
Any device which exposes alert (1802:2A06) and button (FFE0:FFE1) characteristics should work.
These are typically listed to be used with the "iTracing" mobile app.

## Example Homebridge Configuration

```json
"accessories": [
{
"accessory": "Bluetooth Tag",
"name": "Bedroom Light Switch",
"address": "0123456789ab"
}
]
```

The `address` is optional. If omitted, the first device found will be used.
It is however recommended to set the address to avoid inadvertent connection to the wrong device.
The easiest way to find the address is to start the plugin without an address and then check the logs.
You should see output like `connecting iTAG (0123456789ab)`.
119 changes: 119 additions & 0 deletions index.js
@@ -0,0 +1,119 @@
var _ = require('lodash');

var Service, Characteristic;

module.exports = function(homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory('homebridge-bluetooth-tag', 'Bluetooth Tag', TagAccessory);
};

function TagAccessory(log, config) {
this.log = log;

this.address = config.address;

this.noble = require('noble');
this.noble.on('stateChange', this.onStateChange.bind(this));
this.noble.on('discover', this.onDiscoverPeripheral.bind(this));
}

TagAccessory.prototype.getServices = function() {
this.service = new Service.StatelessProgrammableSwitch();

return [this.service];
};

TagAccessory.prototype.onStateChange = function(state) {
if (state == 'poweredOn') {
this.discoverTag();
}
};

TagAccessory.prototype.discoverTag = function() {
this.log('scanning');
this.noble.startScanning(['1802'], false);
};

TagAccessory.prototype.onDiscoverPeripheral = function(peripheral) {
var address = peripheral.address;
if (address == 'unknown') {
address = peripheral.id;
}

var canConnect = !this.address || address == this.address;
this.log((canConnect ? 'connecting' : 'ignoring') + ' ' + peripheral.advertisement.localName + ' (' + address + ')');
if (!canConnect) return;

this.peripheral = peripheral;

this.noble.stopScanning();
this.peripheral.once('disconnect', this.onDisconnect.bind(this));
this.peripheral.connect(this.onConnect.bind(this));
};

TagAccessory.prototype.onConnect = function(error) {
if (error) {
this.log('failed to connect: ' + error);
this.discoverTag();
return;
}

this.log('connected');
this.peripheral.discoverSomeServicesAndCharacteristics([], ['ffe1', '2a06'], this.onDiscoverServicesAndCharacteristics.bind(this));
};

TagAccessory.prototype.onDisconnect = function(error) {
this.log('disconnected');
this.peripheral = null;
this.discoverTag();
};

TagAccessory.prototype.onDiscoverServicesAndCharacteristics = function(error, services, characteristics) {
if (error) {
this.log('failed to discover characteristics: ' + error);
return;
}

characteristics = _.keyBy(characteristics, function(characteristic) {
return (characteristic._serviceUuid + ':' + characteristic.uuid).toLowerCase();
});

this.alertCharacteristic = characteristics['1802:2a06'];

this.keyPressCharacteristic = characteristics['ffe0:ffe1'];
if (!this.keyPressCharacteristic) {
this.log('could not find key press characteristic');
} else {
this.keyPressCharacteristic.on('data', this.onKeyPress.bind(this));
this.keyPressCharacteristic.subscribe(function (error) {
if (error) {
this.log('failed to subscribe to key presses');
} else {
this.log('subscribed to key presses');
}
}.bind(this));
}
};

TagAccessory.prototype.identify = function(callback) {
this.log('identify');
if (this.peripheral) {
this.alertCharacteristic.write(Buffer.from([0x02]), true);
setTimeout(function() {
this.alertCharacteristic.write(Buffer.from([0x00]), true);
}.bind(this), 250);
callback();
} else {
callback(new Error('not connected'));
}
};

TagAccessory.prototype.onKeyPress = function() {
this.log('key press');
var characteristic = this.service.getCharacteristic(Characteristic.ProgrammableSwitchEvent);
characteristic.setValue(true);
setTimeout(function() {
characteristic.setValue(false);
}, 1000);
};
41 changes: 41 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions package.json
@@ -0,0 +1,15 @@
{
"name": "homebridge-bluetooth-tag",
"version": "0.0.1",
"main": "index.js",
"keywords": [
"homebridge-plugin"
],
"engines": {
"homebridge": ">=0.2.0"
},
"dependencies": {
"lodash": "^4.15.0",
"noble": "^1.6.0"
}
}

0 comments on commit cc0e5da

Please sign in to comment.