Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject GATT descriptors #29

Merged
merged 1 commit into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions examples/cache-gatt-discovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/** discover a device (here, the first one where the name was resolved),
* for the first device discover all services and characteristics,
* store the collected GATT information into a meta-data object and write to disk.
* Finds a temperature characteristic and registers for data.
* Prints timing information from discovered to connected to reading states.
*/

var noble = require('../index');
const fs = require('fs');

// the sensor value to scan for, number of bits and factor for displaying it
const CHANNEL = process.env['CHANNEL'] ? process.env['CHANNEL'] : 'Temperature'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or just "Unknown"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a working example... at least for parts of the universe ;)

const BITS = process.env['BITS'] ? 1 * process.env['BITS'] : 16
const FACTOR = process.env['FACTOR'] ? 1. * process.env['FACTOR'] : .1

const EXT='.dump'

noble.on('stateChange', function(state) {
if (state === 'poweredOn') {
noble.startScanning();
} else {
noble.stopScanning();
}
});

let tDisco=0; // time when device was discovered
let tConn =0; // time when connection to device was established
let tRead =0; // time when reading data starts.

// collect device meta-data into this object:
let meta = {
services: [], // stores an array of GATT service data objects
characteristics: {} // a map with key service-UUID, stores the array of characteristics
}

noble.on('discover', function(peripheral) {
console.log('peripheral discovered (' + peripheral.id +
' with address <' + peripheral.address + ', ' + peripheral.addressType + '>,' +
' connectable ' + peripheral.connectable + ',' +
' RSSI ' + peripheral.rssi + ':');
console.log('\thello my local name is:');
console.log('\t\t' + peripheral.advertisement.localName);
console.log();

// connect to the first device with a valid name
if (peripheral.advertisement.localName) {
console.log('Connecting to ' + peripheral.address + ' ' + peripheral.advertisement.localName)

tDisco = Date.now()

connectToDevice(peripheral)
}
});

let connectToDevice = function (peripheral) {
// BLE cannot scan and connect in parallel, so we stop scanning here:
noble.stopScanning()

peripheral.connect((error) => {
// noble.startScanning([], true)
if (error) {
console.log('Connect error: ' + error)
noble.startScanning([], true)
return
}
tConn = Date.now()
console.log('Connected!')

findServices(noble, peripheral)
})
}


let servicesToRead = 0;

let findServices = function (noble, peripheral) {
meta.uuid = peripheral.uuid
meta.address = peripheral.address
meta.name = peripheral.advertisement.localName // not needed but nice to have

meta.characteristics = {}

// callback triggers with GATT-relevant data
peripheral.on('servicesDiscovered', (peripheral, services) => {

console.log('servicesDiscovered: Found '+ services.length + ' services! ')
meta.services = services
for (let i in services) {
const service = services[i]
console.log('\tservice ' + i + ' : ' + JSON.stringify(service))
//meta.services[ service.uuid ] = service
}
})

peripheral.discoverServices([], (error, services) => {

let sensorCharacteristic

servicesToRead = services.length
// we found the list of services, now trigger characteristics lookup for each of them:

for (let i = 0; i < services.length; i++) {
let service = services[i]

service.on('characteristicsDiscovered', (characteristics) => {
// store the list of characteristics per service
meta.characteristics[service.uuid] = characteristics

console.log('SRV\t' + service.uuid + ' characteristic GATT data: ')
for (let i = 0; i < characteristics.length; i++) {
console.log('\t' + service.uuid + ' chara.\t ' + ' ' + i + ' ' + JSON.stringify(characteristics[i]))
}
})

service.discoverCharacteristics([], function (error, characteristics) {
console.log('SRV\t' + service.uuid + ' characteristic decoded data: ' )
for (let j = 0; j< characteristics.length; j++) {
let ch = characteristics[j]
console.log('\t' + service.uuid + ' chara.\t ' + ' ' + j + ' ' + ch)

if ( ch.name === CHANNEL) {
console.log('found ' + CHANNEL + ' characteristic!')
sensorCharacteristic = ch
}
}

servicesToRead--
if (!servicesToRead) {
console.log('----------------- FINISHED')
console.log(JSON.stringify(meta, null, 4))
// write to file
fs.writeFile(meta.uuid + EXT, JSON.stringify(meta,null,2), function(err) {
if(err) {
return console.log(err);
}
console.log("The data was saved to " , meta.uuid + EXT);
});

if (sensorCharacteristic) {
console.log('Listening for temperature data...')

tRead = Date.now()

sensorCharacteristic.on('data', (data) => {
if (BITS === 16 ) {
console.log(' new ' + CHANNEL + ' ' + (data.readUInt16LE() * FACTOR) )
} else if (BITS === 32) {
console.log(' new ' + CHANNEL + ' ' + (data.readUInt32LE() * FACTOR) )
} else {
console.log(' Cannot cope with BITS value '+ BITS)
}
})
sensorCharacteristic.read()
}

console.log('Timespan from discovery to connected: ' + (tConn -tDisco) + ' ms')
console.log('Timespan from connected to reading : ' + (tRead -tConn) + ' ms')
}
})
}
})
}
146 changes: 146 additions & 0 deletions examples/cache-gatt-reconnect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/** reconnect to a device that has been discovered earlier on using cache-gatt-discovery:
* If a device is discovered and a dump file exists, load it and connect to it, re-initializing service
* and characteristic objects in the noble stack.
* Finds a temperature characteristic and registers for data.
* Prints timing information from discovered to connected to reading states.
*/

var noble = require('../index');
const fs = require('fs');

// the sensor value to scan for, number of bits and factor for displaying it
const CHANNEL = process.env['CHANNEL'] ? process.env['CHANNEL'] : 'Temperature'
const BITS = process.env['BITS'] ? 1 * process.env['BITS'] : 16
const FACTOR = process.env['FACTOR'] ? 1. * process.env['FACTOR'] : .1

const EXT='.dump'

noble.on('stateChange', function(state) {
if (state === 'poweredOn') {
noble.startScanning();
} else {
noble.stopScanning();
}
});

let tDisco=0; // time when device was discovered
let tConn =0; // time when connection to device was established
let tRead =0; // time when reading data starts.

// collect device meta-data into this object:
let meta = {
services: {}, // a map indexted by service-UUID -> contains service data
characteristics: {} // an map with key service-UUID, stores the array of characteristics
}

noble.on('discover', function(peripheral) {
console.log('peripheral discovered (' + peripheral.id +
' with address <' + peripheral.address + ', ' + peripheral.addressType + '>,' +
' connectable ' + peripheral.connectable + ',' +
' RSSI ' + peripheral.rssi + ':');
console.log('\thello my local name is:');
console.log('\t\t' + peripheral.advertisement.localName);
console.log();


// Check if a dump exists in the current directory.
fs.access(peripheral.uuid + EXT, fs.constants.F_OK, (err) => {
if (!err) {
console.log('found dump file for ' + peripheral.uuid )

tDisco=Date.now()

quickConnect(peripheral)
}
});
});


let quickConnect = function (peripheral) {
// BLE cannot scan and connect in parallel, so we stop scanning here:
noble.stopScanning()


peripheral.connect((error) => {
if (error) {
console.log('Connect error: ' + error)
noble.startScanning([], true)
return
}
tConn = Date.now()
console.log('Connected!')

// load stored data. This needs to be done when connected, as we need a handle at GATT level
meta = loadData(peripheral)

// initialize the service and charateristics objects in Noble; return a temperature characteristic, if found
let sensorCharacteristic = setData(peripheral, meta)

if (!sensorCharacteristic) {
console.log('Warning - no temperature characteristic found.')
} else {
console.log('Listening for temperature data...')

tRead = Date.now()

sensorCharacteristic.on('data', (data) => {
if (BITS === 16 ) {
console.log(' new ' + CHANNEL + ' ' + (data.readUInt16LE() * FACTOR) )
} else if (BITS === 32) {
console.log(' new ' + CHANNEL + ' ' + (data.readUInt32LE() * FACTOR) )
} else {
console.log(' Cannot cope with BITS value '+ BITS)
}
})
sensorCharacteristic.read()

console.log('Timespan from discovery to connected: ' + (tConn -tDisco) + ' ms')
console.log('Timespan from connected to reading : ' + (tRead -tConn) + ' ms')
}
})
}

let loadData = function(peripheral) {
const dump = fs.readFileSync(peripheral.uuid + EXT)
const data = JSON.parse(dump)

// verify data: console.log(JSON.stringify(data,null,2))
return data
}

let setData = function(peripheral, meta) {
// first, create the service objects:
console.log('initializing services... ')

// addServices returns an array of initialized service objects
let services = noble.addServices(peripheral.uuid, meta.services)

console.log('initialized services: ')
for (let i in services) {
const service = services[i]
console.log('\tservice ' + i + ' ' + service)
}
console.log()

let sensorCharacteristic

console.log('initializing characteristics... ')
// now, for each service, set the characteristics:
for (let i in services) {
const service = services[i]
const charas = meta.characteristics[service.uuid]
console.log('\tservice ' + i + ' ' + service + ' ' + JSON.stringify(charas))

let characteristics = noble.addCharacteristics(peripheral.uuid, service.uuid, charas)

for (let j in characteristics) {
let characteristic = characteristics[j]
console.log('\t\tcharac ' + service.uuid + ' ' + j + ' ' + characteristic + ' ' + characteristic.rawProps)
if (characteristic.name === CHANNEL) {
console.log('\t\t\t-->found ' + CHANNEL + ' characteristic!')
sensorCharacteristic = characteristic
}
}
}
return sensorCharacteristic
}
35 changes: 35 additions & 0 deletions lib/hci-socket/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ NobleBindings.prototype.onLeConnComplete = function(status, handle, role, addres

this._gatts[handle].on('mtu', this.onMtu.bind(this));
this._gatts[handle].on('servicesDiscover', this.onServicesDiscovered.bind(this));
this._gatts[handle].on('servicesDiscovered', this.onServicesDiscoveredEX.bind(this));
this._gatts[handle].on('includedServicesDiscover', this.onIncludedServicesDiscovered.bind(this));
this._gatts[handle].on('characteristicsDiscover', this.onCharacteristicsDiscovered.bind(this));
this._gatts[handle].on('characteristicsDiscovered', this.onCharacteristicsDiscoveredEX.bind(this));
this._gatts[handle].on('read', this.onRead.bind(this));
this._gatts[handle].on('write', this.onWrite.bind(this));
this._gatts[handle].on('broadcast', this.onBroadcast.bind(this));
Expand Down Expand Up @@ -287,6 +289,16 @@ NobleBindings.prototype.onAclDataPkt = function(handle, cid, data) {
}
};

NobleBindings.prototype.addService = function(peripheralUuid, service) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];

if (gatt) {
gatt.addService(service);
} else {
console.warn('noble warning: unknown peripheral ' + peripheralUuid);
}
};

NobleBindings.prototype.discoverServices = function(peripheralUuid, uuids) {
var handle = this._handles[peripheralUuid];
Expand All @@ -305,6 +317,12 @@ NobleBindings.prototype.onServicesDiscovered = function(address, serviceUuids) {
this.emit('servicesDiscover', uuid, serviceUuids);
};

NobleBindings.prototype.onServicesDiscoveredEX = function(address, services) {
var uuid = address.split(':').join('').toLowerCase();

this.emit('servicesDiscovered', uuid, services);
};

NobleBindings.prototype.discoverIncludedServices = function(peripheralUuid, serviceUuid, serviceUuids) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
Expand All @@ -322,6 +340,17 @@ NobleBindings.prototype.onIncludedServicesDiscovered = function(address, service
this.emit('includedServicesDiscover', uuid, serviceUuid, includedServiceUuids);
};

NobleBindings.prototype.addCharacteristics = function(peripheralUuid, serviceUuid, characteristics) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];

if (gatt) {
gatt.addCharacteristics(serviceUuid, characteristics);
} else {
console.warn('noble warning: unknown peripheral ' + peripheralUuid);
}
};

NobleBindings.prototype.discoverCharacteristics = function(peripheralUuid, serviceUuid, characteristicUuids) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
Expand All @@ -339,6 +368,12 @@ NobleBindings.prototype.onCharacteristicsDiscovered = function(address, serviceU
this.emit('characteristicsDiscover', uuid, serviceUuid, characteristics);
};

NobleBindings.prototype.onCharacteristicsDiscoveredEX = function(address, serviceUuid, characteristics) {
var uuid = address.split(':').join('').toLowerCase();

this.emit('characteristicsDiscovered', uuid, serviceUuid, characteristics);
};

NobleBindings.prototype.read = function(peripheralUuid, serviceUuid, characteristicUuid) {
var handle = this._handles[peripheralUuid];
var gatt = this._gatts[handle];
Expand Down
Loading