device.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. let noble = require('noble');
  2. let Rx = require('rx');
  3. const ACCEL_OFF = Buffer.from([0x00]);
  4. const ACCEL_ON = Buffer.from([0x01]);
  5. // MAGIC CONSTANTS FOLLOW
  6. // these might be different for other devices, I wouldn't know
  7. const IDENTIFIER = 'CHSLEEV_00';
  8. const DEVICE_INFO_UUID = '180a';
  9. const ACCEL_UUID = 'ffa0';
  10. const RATIO = 14;
  11. // MAGIC CONSTANTS END
  12. noble.on('stateChange', function(state) {
  13. if(state === 'poweredOn') {
  14. noble.startScanning();
  15. }
  16. else {
  17. noble.stopScanning();
  18. }
  19. });
  20. let button$ = new Rx.Subject();
  21. let discover$ = Rx.Observable.fromEvent(noble, 'discover');
  22. let peripheral$ = discover$
  23. .filter(p => p.advertisement.localName === IDENTIFIER)
  24. .do(_ => noble.stopScanning())
  25. .take(1)
  26. .shareReplay(1)
  27. let connection$ = peripheral$
  28. .flatMapFirst(peripheral => {
  29. let disconnect$ = Rx.Observable.fromEvent(peripheral, 'disconnect')
  30. .map(_ => { return {state: 'disconnected'}})
  31. .take(1);
  32. let connect$ = Rx.Observable.fromEvent(peripheral, 'connect')
  33. .map(_ => { return {state: 'connected', peripheral}})
  34. .take(1);
  35. peripheral.connect();
  36. return disconnect$.merge(connect$);
  37. })
  38. .repeat()
  39. .share()
  40. let services$ = connection$
  41. .filter(s => s && s.state === 'connected')
  42. .flatMap(s => {
  43. let peripheral = s.peripheral;
  44. let discoverServices = Rx.Observable.fromNodeCallback(
  45. peripheral.discoverSomeServicesAndCharacteristics,
  46. peripheral
  47. );
  48. return discoverServices([DEVICE_INFO_UUID, ACCEL_UUID], []);
  49. })
  50. .filter(s => s.length == 2 && s[0].length == 2)
  51. .map(s => s[0])
  52. .share()
  53. let serial$ = services$
  54. .flatMap(s => {
  55. let info = s[0].characteristics[2];
  56. return Rx.Observable.fromNodeCallback(info.read, info)();
  57. })
  58. let connStatus$ = connection$.combineLatest(serial$, (conn, serial) => {
  59. if(conn.state == 'connected') {
  60. return Object.assign({serial}, conn);
  61. }
  62. else {
  63. return conn;
  64. }
  65. }).share()
  66. let xAccel$ = services$
  67. .do(s => {
  68. s[1].characteristics[0].write(ACCEL_ON);
  69. s[1].characteristics[2].subscribe();
  70. })
  71. .flatMap(s => {
  72. return Rx.Observable.fromEvent(s[1].characteristics[2], 'data');
  73. })
  74. .map(d => d[1] * 256 + d[0])
  75. .share()
  76. function item(type, value) {
  77. return { type, value }
  78. }
  79. let tareFromButton$ = button$
  80. .withLatestFrom(xAccel$, (_, accel) => item('reset', accel))
  81. let tareFromXAccel$ = xAccel$
  82. .map(accel => item('value', accel))
  83. let tare$ = tareFromButton$.merge(tareFromXAccel$)
  84. .scan((acc, item) => {
  85. if(item.type === 'reset') {
  86. return item.value;
  87. }
  88. else {
  89. return Math.max(acc, item.value);
  90. }
  91. }, 0)
  92. let weight$ = xAccel$
  93. .combineLatest(tare$, (accel, tare) => {
  94. return (tare - accel) / RATIO;
  95. })
  96. .map(Math.floor)
  97. module.exports = {
  98. button$,
  99. connStatus$,
  100. weight$
  101. }