device.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. function makeAccelStream(characteristic) {
  67. return Rx.Observable.fromEvent(characteristic, 'data')
  68. .map(d => d[3] * 256 * 256 * 256 + d[2] * 256 * 256 + d[1] * 256 + d[0])
  69. .startWith(-1)
  70. }
  71. let accel$ = services$
  72. .do(s => {
  73. s[1].characteristics[0].write(ACCEL_ON);
  74. s[1].characteristics[2].subscribe();
  75. s[1].characteristics[3].subscribe();
  76. s[1].characteristics[4].subscribe();
  77. })
  78. .flatMap(s => {
  79. let characteristics = s[1].characteristics;
  80. return Rx.Observable.combineLatest(
  81. makeAccelStream(characteristics[2]),
  82. makeAccelStream(characteristics[3]),
  83. makeAccelStream(characteristics[4])
  84. )
  85. })
  86. .share()
  87. let xAccel$ = accel$
  88. .map(r => r[0])
  89. .filter(v => v >= 0)
  90. function item(type, value) {
  91. return { type, value }
  92. }
  93. let tareFromButton$ = button$
  94. .withLatestFrom(xAccel$, (_, accel) => item('reset', accel))
  95. let tareFromXAccel$ = xAccel$
  96. .map(accel => item('value', accel))
  97. let tare$ = tareFromButton$.merge(tareFromXAccel$)
  98. .scan((acc, item) => {
  99. if(item.type === 'reset') {
  100. return item.value;
  101. }
  102. else {
  103. return Math.max(acc, item.value);
  104. }
  105. }, 0)
  106. let weight$ = xAccel$
  107. .combineLatest(tare$, (accel, tare) => {
  108. return (tare - accel) / RATIO;
  109. })
  110. .map(Math.floor)
  111. module.exports = {
  112. button$,
  113. connStatus$,
  114. weight$,
  115. accel$
  116. }