Skip to content

Commit 398cfb7

Browse files
author
Daisuke Baba
committed
Add Notification support
1 parent f04b11d commit 398cfb7

File tree

1 file changed

+104
-71
lines changed

1 file changed

+104
-71
lines changed

src/generic-ble.js

Lines changed: 104 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,29 @@ function toDetailedObject(peripheral, RED) {
223223

224224
export default function(RED) {
225225

226+
function toCharacteristic(c) {
227+
let obj = {
228+
uuid: c.uuid,
229+
name: c.name || RED._('generic-ble.label.unnamedChr'),
230+
type: c.type || RED._('generic-ble.label.customType'),
231+
notifiable: c.properties.indexOf('notify') >= 0,
232+
readable: c.properties.indexOf('read') >= 0,
233+
writable: c.properties.indexOf('write') >= 0,
234+
writeWithoutResponse: c.properties.indexOf('writeWithoutResponse') >= 0,
235+
object: c,
236+
addDataListener: (func) => {
237+
if (obj.dataListener) {
238+
return false;
239+
}
240+
obj.dataListener = func;
241+
obj.object.removeAllListeners('data');
242+
obj.object.on('data', func);
243+
return true;
244+
},
245+
};
246+
return obj;
247+
}
248+
226249
class GenericBLENode {
227250
constructor(n) {
228251
RED.nodes.createNode(this, n);
@@ -241,6 +264,7 @@ export default function(RED) {
241264
preparePeripheral: () => {
242265
let peripheral = noble._peripherals[this.uuid];
243266
if (!peripheral) {
267+
this.emit('disconnected');
244268
return Promise.resolve();
245269
}
246270
switch (peripheral.state) {
@@ -260,19 +284,7 @@ export default function(RED) {
260284
this.emit('connected');
261285
this.characteristics = services.reduce((prev, curr) => {
262286
return prev.concat(curr.characteristics);
263-
}, []).map((c) => {
264-
let characteristic = {
265-
uuid: c.uuid,
266-
name: c.name || RED._('generic-ble.label.unnamedChr'),
267-
type: c.type || RED._('generic-ble.label.customType'),
268-
notifiable: c.properties.indexOf('notify') >= 0,
269-
readable: c.properties.indexOf('read') >= 0,
270-
writable: c.properties.indexOf('write') >= 0,
271-
writeWithoutResponse: c.properties.indexOf('writeWithoutResponse') >= 0,
272-
object: c
273-
};
274-
return characteristic;
275-
});
287+
}, []).map((c) => toCharacteristic(c));
276288
});
277289
});
278290
peripheral.connect(); // peripheral.state => connecting
@@ -282,19 +294,7 @@ export default function(RED) {
282294
if (peripheral.services) {
283295
this.characteristics = peripheral.services.reduce((prev, curr) => {
284296
return prev.concat(curr.characteristics);
285-
}, []).map((c) => {
286-
let characteristic = {
287-
uuid: c.uuid,
288-
name: c.name || RED._('generic-ble.label.unnamedChr'),
289-
type: c.type || RED._('generic-ble.label.customType'),
290-
notifiable: c.properties.indexOf('notify') >= 0,
291-
readable: c.properties.indexOf('read') >= 0,
292-
writable: c.properties.indexOf('write') >= 0,
293-
writeWithoutResponse: c.properties.indexOf('writeWithoutResponse') >= 0,
294-
object: c
295-
};
296-
return characteristic;
297-
});
297+
}, []).map((c) => toCharacteristic(c));
298298
}
299299
if (!peripheral._disconnectedHandlerSet) {
300300
peripheral._disconnectedHandlerSet = true;
@@ -319,6 +319,7 @@ export default function(RED) {
319319
} else if (retry < 10) {
320320
setTimeout(connectedHandler, 500);
321321
} else {
322+
this.emit('timeout');
322323
return resolve(peripheral.state);
323324
}
324325
};
@@ -449,37 +450,67 @@ export default function(RED) {
449450
});
450451
});
451452
},
452-
subscribe: (uuids='', period=3000) => {
453-
// FIXME
454-
uuids = uuids.split(',').map((uuid) => uuid.trim()).filter((uuid) => uuid);
455-
let notifiables = this.characteristics.filter(c => {
456-
if (c.notifiable) {
457-
if (uuids.length === 0) {
458-
return true;
453+
subscribe: (uuids='', period=0) => {
454+
return this.operations.preparePeripheral().then((state) => {
455+
if (state !== 'connected') {
456+
this.log(`[subscribe] Peripheral:${this.uuid} is NOT ready. state=>${state}`);
457+
return Promise.resolve();
458+
}
459+
uuids = uuids.split(',').map((uuid) => uuid.trim()).filter((uuid) => uuid);
460+
let notifiables = this.characteristics.filter(c => {
461+
if (c.notifiable) {
462+
if (uuids.length === 0) {
463+
return true;
464+
}
465+
return uuids.indexOf(c.uuid) >= 0;
459466
}
460-
return uuids.indexOf(c.uuid) >= 0;
467+
});
468+
if (TRACE) {
469+
this.log(`characteristics => ${JSON.stringify(this.characteristics.map((c) => {
470+
let obj = Object.assign({}, c);
471+
delete obj.obj;
472+
return obj;
473+
}))}`);
474+
this.log(`notifiables.length => ${notifiables.length}`);
461475
}
476+
if (notifiables.length === 0) {
477+
return false;
478+
}
479+
return Promise.all(notifiables.map((r) => {
480+
r.addDataListener((data, isNotification) => {
481+
if (isNotification) {
482+
let readObj = {
483+
notification: true
484+
};
485+
readObj[r.uuid] = data;
486+
this.emit('ble-notify', this.uuid, readObj);
487+
}
488+
});
489+
r.object.subscribe((err) => {
490+
if (err) {
491+
this.emit('error', err);
492+
this.log(`subscription error: ${err.message}`);
493+
} else {
494+
this.emit('subscribed');
495+
}
496+
});
497+
if (period > 0) {
498+
setTimeout(() => {
499+
r.object.unsubscribe((err) => {
500+
if (err) {
501+
this.emit('error', err);
502+
this.log(`unsubscription error: ${err.message}`);
503+
} else {
504+
this.emit('connected');
505+
}
506+
});
507+
}, 5000);
508+
}
509+
}));
462510
});
463-
if (TRACE) {
464-
this.log(`characteristics => ${JSON.stringify(this.characteristics.map((c) => {
465-
let obj = Object.assign({}, c);
466-
delete obj.obj;
467-
return obj;
468-
}))}`);
469-
this.log(`notifiables.length => ${notifiables.length}`);
470-
}
471-
if (notifiables.length === 0) {
472-
return false;
473-
}
474-
// TODO perform subscribe here right now
475-
notifiables.map((r) => {
476-
// {uuid:'characteristic-uuid-to-subscribe', period:subscription period}
477-
return { uuid: r.uuid, period: period };
478-
});
479-
return true;
480511
}
481512
};
482-
['connected', 'disconnected', 'subscribed', 'unsubscribed', 'error', 'timeout'].forEach(ev => {
513+
['connected', 'disconnected', 'subscribed', 'error', 'timeout'].forEach(ev => {
483514
this.on(ev, () => {
484515
try {
485516
Object.keys(this.nodes).forEach(id => {
@@ -505,7 +536,6 @@ export default function(RED) {
505536
this.genericBleNodeId = n.genericBle;
506537
this.genericBleNode = RED.nodes.getNode(this.genericBleNodeId);
507538
if (this.genericBleNode) {
508-
// FIXME
509539
if (this.notification) {
510540
this.genericBleNode.on('ble-notify', (uuid, readObj, err) => {
511541
if (err) {
@@ -531,9 +561,6 @@ export default function(RED) {
531561
this.on('subscribed', () => {
532562
this.status({fill:'green',shape:'dot',text:`generic-ble.status.subscribed`});
533563
});
534-
this.on('unsubscribed', () => {
535-
this.status({fill:'red',shape:'ring',text:`generic-ble.status.unsubscribed`});
536-
});
537564
}
538565
this.on('connected', () => {
539566
this.status({fill:'green',shape:'dot',text:`generic-ble.status.connected`});
@@ -556,22 +583,28 @@ export default function(RED) {
556583
}
557584
} catch (_) {
558585
}
559-
this.genericBleNode.operations.read(msg.topic).then((readObj) => {
560-
if (!readObj) {
561-
this.log(`Nothing to read`);
562-
return;
563-
}
564-
let payload = {
565-
uuid: this.genericBleNode.uuid,
566-
characteristics: readObj
567-
};
568-
if (this.useString) {
569-
payload = JSON.stringify(payload);
570-
}
571-
this.send({
572-
payload: payload
586+
let p;
587+
if (obj.notify) {
588+
p = this.genericBleNode.operations.subscribe(msg.topic, obj.period);
589+
} else {
590+
p = this.genericBleNode.operations.read(msg.topic).then((readObj) => {
591+
if (!readObj) {
592+
this.log(`Nothing to read`);
593+
return;
594+
}
595+
let payload = {
596+
uuid: this.genericBleNode.uuid,
597+
characteristics: readObj
598+
};
599+
if (this.useString) {
600+
payload = JSON.stringify(payload);
601+
}
602+
this.send({
603+
payload: payload
604+
});
573605
});
574-
}).catch((err) => {
606+
}
607+
p.catch((err) => {
575608
this.error(`<${this.uuid}> read: (err:${err})`);
576609
});
577610
});

0 commit comments

Comments
 (0)