@neurodevs/node-lsl 18.0.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.vscode/launch.json +54 -58
  2. package/.vscode/settings.json +61 -66
  3. package/.vscode/tasks.json +98 -129
  4. package/README.md +14 -12
  5. package/build/__tests__/AbstractPackageTest.d.ts +16 -12
  6. package/build/__tests__/AbstractPackageTest.js +48 -20
  7. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  8. package/build/__tests__/impl/BleDeviceController.test.d.ts +59 -0
  9. package/build/__tests__/impl/BleDeviceController.test.js +364 -0
  10. package/build/__tests__/impl/BleDeviceController.test.js.map +1 -0
  11. package/build/__tests__/impl/LslEventMarkerEmitter.test.js +1 -1
  12. package/build/__tests__/impl/LslEventMarkerEmitter.test.js.map +1 -1
  13. package/build/__tests__/impl/LslStreamInlet.test.js +2 -2
  14. package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
  15. package/build/__tests__/impl/LslStreamOutlet.test.js +11 -13
  16. package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
  17. package/build/__tests__/impl/LslWebSocketBridge.test.js +3 -3
  18. package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
  19. package/build/__tests__/impl/UsbDeviceController.test.d.ts +17 -0
  20. package/build/__tests__/impl/UsbDeviceController.test.js +81 -0
  21. package/build/__tests__/impl/UsbDeviceController.test.js.map +1 -0
  22. package/build/__tests__/impl/WindowedClockRegressor.test.d.ts +19 -0
  23. package/build/__tests__/impl/WindowedClockRegressor.test.js +172 -0
  24. package/build/__tests__/impl/WindowedClockRegressor.test.js.map +1 -0
  25. package/build/impl/LslStreamInlet.d.ts +1 -0
  26. package/build/impl/LslStreamInlet.js +2 -1
  27. package/build/impl/LslStreamInlet.js.map +1 -1
  28. package/build/impl/LslStreamOutlet.d.ts +3 -2
  29. package/build/impl/LslStreamOutlet.js +4 -3
  30. package/build/impl/LslStreamOutlet.js.map +1 -1
  31. package/build/impl/LslWebSocketBridge.js +2 -2
  32. package/build/impl/LslWebSocketBridge.js.map +1 -1
  33. package/build/impl/WindowedClockRegressor.d.ts +24 -0
  34. package/build/impl/WindowedClockRegressor.js +73 -0
  35. package/build/impl/WindowedClockRegressor.js.map +1 -0
  36. package/build/impl/controllers/BleDeviceController.d.ts +60 -0
  37. package/build/impl/controllers/BleDeviceController.js +153 -0
  38. package/build/impl/controllers/BleDeviceController.js.map +1 -0
  39. package/build/impl/controllers/UsbDeviceController.d.ts +23 -0
  40. package/build/impl/controllers/UsbDeviceController.js +40 -0
  41. package/build/impl/controllers/UsbDeviceController.js.map +1 -0
  42. package/build/impl/workers/inlet/StreamInletWorker.d.ts +4 -4
  43. package/build/impl/workers/inlet/StreamInletWorker.js +12 -12
  44. package/build/impl/workers/inlet/StreamInletWorker.js.map +1 -1
  45. package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +3 -3
  46. package/build/impl/workers/outlet/LslStreamOutlet.worker.js +7 -7
  47. package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +1 -1
  48. package/build/index.d.ts +14 -4
  49. package/build/index.js +17 -5
  50. package/build/index.js.map +1 -1
  51. package/build/scripts/discoverMuse.d.ts +1 -0
  52. package/build/scripts/discoverMuse.js +40 -0
  53. package/build/scripts/discoverMuse.js.map +1 -0
  54. package/build/scripts/runMuse.d.ts +1 -0
  55. package/build/scripts/runMuse.js +40 -0
  56. package/build/scripts/runMuse.js.map +1 -0
  57. package/build/scripts/runUsb.d.ts +1 -0
  58. package/build/scripts/runUsb.js +14 -0
  59. package/build/scripts/runUsb.js.map +1 -0
  60. package/build/testDoubles/BleController/FakeBleController.d.ts +26 -0
  61. package/build/testDoubles/BleController/FakeBleController.js +47 -0
  62. package/build/testDoubles/BleController/FakeBleController.js.map +1 -0
  63. package/build/testDoubles/BleController/SpyBleController.d.ts +14 -0
  64. package/build/testDoubles/BleController/SpyBleController.js +48 -0
  65. package/build/testDoubles/BleController/SpyBleController.js.map +1 -0
  66. package/build/testDoubles/ClockRegressor/FakeClockRegressor.d.ts +17 -0
  67. package/build/testDoubles/ClockRegressor/FakeClockRegressor.js +23 -0
  68. package/build/testDoubles/ClockRegressor/FakeClockRegressor.js.map +1 -0
  69. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +2 -2
  70. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +5 -2
  71. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
  72. package/build/testDoubles/UsbController/FakeUsbController.d.ts +12 -0
  73. package/build/testDoubles/UsbController/FakeUsbController.js +25 -0
  74. package/build/testDoubles/UsbController/FakeUsbController.js.map +1 -0
  75. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +1 -1
  76. package/build/testDoubles/generateRandomOutletOptions.js +1 -1
  77. package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
  78. package/eslint.config.js +3 -0
  79. package/package.json +19 -57
  80. package/prettier.config.js +3 -0
  81. package/src/__tests__/AbstractPackageTest.ts +86 -29
  82. package/src/__tests__/impl/BleDeviceController.test.ts +535 -0
  83. package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +2 -1
  84. package/src/__tests__/impl/LslStreamInlet.test.ts +2 -2
  85. package/src/__tests__/impl/LslStreamOutlet.test.ts +15 -14
  86. package/src/__tests__/impl/LslWebSocketBridge.test.ts +4 -3
  87. package/src/__tests__/impl/UsbDeviceController.test.ts +109 -0
  88. package/src/__tests__/impl/WindowedClockRegressor.test.ts +262 -0
  89. package/src/impl/LslStreamInlet.ts +4 -1
  90. package/src/impl/LslStreamOutlet.ts +5 -4
  91. package/src/impl/LslWebSocketBridge.ts +2 -2
  92. package/src/impl/WindowedClockRegressor.ts +136 -0
  93. package/src/impl/controllers/BleDeviceController.ts +240 -0
  94. package/src/impl/controllers/UsbDeviceController.ts +65 -0
  95. package/src/impl/workers/inlet/StreamInletWorker.ts +12 -12
  96. package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +8 -8
  97. package/src/index.ts +24 -5
  98. package/src/scripts/discoverMuse.ts +50 -0
  99. package/src/scripts/runMuse.ts +50 -0
  100. package/src/scripts/runUsb.ts +20 -0
  101. package/src/testDoubles/BleController/FakeBleController.ts +81 -0
  102. package/src/testDoubles/BleController/SpyBleController.ts +61 -0
  103. package/src/testDoubles/ClockRegressor/FakeClockRegressor.ts +46 -0
  104. package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +6 -3
  105. package/src/testDoubles/UsbController/FakeUsbController.ts +34 -0
  106. package/src/testDoubles/WebSocketBridge/FakeWebSocketBridge.ts +1 -2
  107. package/src/testDoubles/generateRandomOutletOptions.ts +2 -1
  108. package/tsconfig.json +23 -27
  109. package/.regressionproof.json +0 -7
  110. package/build/.spruce/settings.json +0 -15
  111. package/build/__tests__/impl/LslBackpressureMonitor.test.d.ts +0 -7
  112. package/build/__tests__/impl/LslBackpressureMonitor.test.js +0 -26
  113. package/build/__tests__/impl/LslBackpressureMonitor.test.js.map +0 -1
  114. package/build/impl/LslBackpressureMonitor.d.ts +0 -8
  115. package/build/impl/LslBackpressureMonitor.js +0 -8
  116. package/build/impl/LslBackpressureMonitor.js.map +0 -1
  117. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.d.ts +0 -6
  118. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js +0 -10
  119. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js.map +0 -1
  120. package/eslint.config.mjs +0 -3
  121. package/src/.spruce/settings.json +0 -15
  122. package/src/__tests__/impl/LslBackpressureMonitor.test.ts +0 -25
  123. package/src/impl/LslBackpressureMonitor.ts +0 -13
  124. package/src/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.ts +0 -13
@@ -0,0 +1,240 @@
1
+ import { LibndxAdapter, Libndx, NativePeripheral } from '@neurodevs/ndx-native'
2
+
3
+ export default class BleDeviceController implements BleController {
4
+ public static Class?: BleControllerConstructor
5
+ public static setTimeout = setTimeout
6
+ public static waitAfterMs = 1000
7
+
8
+ private static _ndx?: Libndx
9
+
10
+ public static get ndx() {
11
+ if (!this._ndx) {
12
+ this._ndx = LibndxAdapter.getInstance()
13
+ }
14
+ return this._ndx
15
+ }
16
+
17
+ public static set ndx(value: Libndx) {
18
+ this._ndx = value
19
+ }
20
+
21
+ protected charCallbacks: CharacteristicCallbacks
22
+ protected rssiIntervalMs?: number
23
+ protected connected = false
24
+ protected log = console
25
+
26
+ private deviceUuid?: string
27
+ private deviceNamePrefix?: string
28
+ private deviceName?: string
29
+ private onConnected?: (peripheral: NativePeripheral) => void
30
+
31
+ protected constructor(options: BleControllerOptions) {
32
+ const {
33
+ deviceUuid,
34
+ deviceNamePrefix,
35
+ charCallbacks,
36
+ onConnected,
37
+ rssiIntervalMs,
38
+ } = options
39
+
40
+ this.deviceUuid = deviceUuid
41
+ this.deviceNamePrefix = deviceNamePrefix
42
+ this.charCallbacks = charCallbacks
43
+ this.onConnected = onConnected
44
+ this.rssiIntervalMs = rssiIntervalMs
45
+ }
46
+
47
+ public static async Create(options: BleControllerOptions) {
48
+ return new (this.Class ?? this)(options)
49
+ }
50
+
51
+ public async connect() {
52
+ if (!this.deviceUuid) {
53
+ await this.discoverUuid()
54
+ }
55
+
56
+ this.createBleBackend()
57
+ this.startBleBackend()
58
+
59
+ await this.waitForOnConnected()
60
+ await this.waitToDiscoverServices()
61
+
62
+ this.setBleRssiInterval()
63
+ }
64
+
65
+ private async discoverUuid() {
66
+ const { status, error } = this.ndx.discoverBleUuid({
67
+ namePrefix: this.deviceNamePrefix as string,
68
+ onDiscovered: (uuid: string) => {
69
+ this.deviceUuid = uuid
70
+ },
71
+ })
72
+
73
+ this.throwIfError(status, error)
74
+
75
+ await this.waitForDiscoveredUuid()
76
+ }
77
+
78
+ private async waitForDiscoveredUuid() {
79
+ await new Promise<void>((resolve) => {
80
+ const checkDiscovered = () => {
81
+ if (this.deviceUuid) {
82
+ resolve()
83
+ } else {
84
+ BleDeviceController.setTimeout(checkDiscovered, 100)
85
+ }
86
+ }
87
+ checkDiscovered()
88
+ })
89
+ }
90
+
91
+ private createBleBackend() {
92
+ const { status, error } = this.ndx.createBleBackend({
93
+ deviceUuid: this.uuid,
94
+ })
95
+
96
+ this.throwIfError(status, error)
97
+ }
98
+
99
+ private throwIfError(status: number, error: string | undefined) {
100
+ if (status !== 200) {
101
+ throw new Error(`${status} error: ${error ?? 'Unknown error'}`)
102
+ }
103
+ }
104
+
105
+ private setBleRssiInterval() {
106
+ if (this.rssiIntervalMs) {
107
+ this.ndx.setBleRssiInterval({
108
+ deviceUuid: this.uuid,
109
+ intervalMs: this.rssiIntervalMs,
110
+ onRssi: (rssi: number) => {
111
+ this.log.info(`[RSSI=${rssi}]`)
112
+ },
113
+ })
114
+ }
115
+ }
116
+
117
+ private startBleBackend() {
118
+ const { status, error } = this.ndx.startBleBackend({
119
+ deviceUuid: this.uuid,
120
+ onConnected: (peripheral: NativePeripheral) => {
121
+ const { name } = peripheral
122
+ this.deviceName = name
123
+ this.connected = true
124
+ this.log.info(`Connected to device ${this.uuid}!`)
125
+ this.onConnected?.(peripheral)
126
+ },
127
+ charCallbacks: this.charCallbacks,
128
+ })
129
+
130
+ this.throwIfError(status, error)
131
+ }
132
+
133
+ private async waitForOnConnected() {
134
+ await new Promise<void>((resolve) => {
135
+ const checkConnected = () => {
136
+ if (this.connected) {
137
+ resolve()
138
+ } else {
139
+ BleDeviceController.setTimeout(checkConnected, 100)
140
+ }
141
+ }
142
+ checkConnected()
143
+ })
144
+ }
145
+
146
+ private async waitToDiscoverServices() {
147
+ await new Promise<void>((resolve) =>
148
+ BleDeviceController.setTimeout(
149
+ resolve,
150
+ BleDeviceController.waitAfterMs
151
+ )
152
+ )
153
+ }
154
+
155
+ public async writeCharacteristic(
156
+ charUuid: CharacteristicUuid,
157
+ value: string
158
+ ) {
159
+ const { status, error } = this.ndx.writeBleCharacteristic({
160
+ deviceUuid: this.uuid,
161
+ charUuid,
162
+ value,
163
+ })
164
+
165
+ this.throwIfError(status, error)
166
+ }
167
+
168
+ public async subscribeCharacteristics(
169
+ charCallbacks: CharacteristicCallbacks
170
+ ) {
171
+ const { status, error } = this.ndx.addBleCharCallbacks({
172
+ deviceUuid: this.uuid,
173
+ charCallbacks,
174
+ })
175
+
176
+ this.throwIfError(status, error)
177
+
178
+ this.charCallbacks = [...this.charCallbacks, ...charCallbacks]
179
+ }
180
+
181
+ public async disconnect() {
182
+ const { status, error } = this.ndx.stopBleBackend({
183
+ deviceUuid: this.uuid,
184
+ })
185
+
186
+ this.throwIfError(status, error)
187
+ this.connected = false
188
+ }
189
+
190
+ public get uuid() {
191
+ return this.deviceUuid ?? ''
192
+ }
193
+
194
+ public get name() {
195
+ return this.deviceName ?? 'N/A'
196
+ }
197
+
198
+ private get ndx() {
199
+ return BleDeviceController.ndx
200
+ }
201
+ }
202
+
203
+ export interface BleController {
204
+ uuid: string
205
+ name: string
206
+
207
+ connect(): Promise<void>
208
+
209
+ subscribeCharacteristics(
210
+ charCallbacks: CharacteristicCallbacks
211
+ ): Promise<void>
212
+
213
+ writeCharacteristic(
214
+ charUuid: CharacteristicUuid,
215
+ value: string
216
+ ): Promise<void>
217
+
218
+ disconnect(): Promise<void>
219
+ }
220
+
221
+ export type BleControllerOptions = {
222
+ charCallbacks: CharacteristicCallbacks
223
+ onConnected?: (peripheral: NativePeripheral) => void
224
+ rssiIntervalMs?: number
225
+ } & (
226
+ | { deviceUuid: string; deviceNamePrefix?: string }
227
+ | { deviceUuid?: string; deviceNamePrefix: string }
228
+ )
229
+
230
+ export type BleControllerConstructor = new (
231
+ options: BleControllerOptions
232
+ ) => BleController
233
+
234
+ export type CharacteristicCallbacks = {
235
+ charUuid: string
236
+ charName?: string
237
+ onData: (data: Buffer, length: number, timestampSec: number) => void
238
+ }[]
239
+
240
+ export type CharacteristicUuid = string
@@ -0,0 +1,65 @@
1
+ import { LibndxAdapter } from '@neurodevs/ndx-native'
2
+
3
+ export default class UsbDeviceController implements UsbController {
4
+ public static Class?: UsbControllerConstructor
5
+
6
+ private onData: (data: Buffer, length: number, timestampSec: number) => void
7
+ private serialNumber: string
8
+
9
+ private ndx = LibndxAdapter.getInstance()
10
+
11
+ protected constructor(options: UsbControllerOptions) {
12
+ const { onData, serialNumber } = options ?? {}
13
+
14
+ this.onData = onData
15
+ this.serialNumber = serialNumber ?? ''
16
+ }
17
+
18
+ public static Create(options: UsbControllerOptions) {
19
+ return new (this.Class ?? this)(options)
20
+ }
21
+
22
+ public async connect() {
23
+ this.ndx.createUsbBackend(this.usbControllerOptions)
24
+ this.ndx.startUsbBackend(this.startUsbControllerOptions)
25
+ }
26
+
27
+ private get usbControllerOptions() {
28
+ return {
29
+ serialNumber: this.serialNumber,
30
+ }
31
+ }
32
+
33
+ private get startUsbControllerOptions() {
34
+ return {
35
+ ...this.usbControllerOptions,
36
+ onData: this.onData,
37
+ }
38
+ }
39
+
40
+ public async writeUsb(value: string) {
41
+ this.ndx.writeUsbBackend({
42
+ ...this.usbControllerOptions,
43
+ value,
44
+ })
45
+ }
46
+
47
+ public async disconnect() {
48
+ this.ndx.stopUsbBackend(this.usbControllerOptions)
49
+ }
50
+ }
51
+
52
+ export interface UsbController {
53
+ connect(): Promise<void>
54
+ writeUsb(value: string): Promise<void>
55
+ disconnect(): Promise<void>
56
+ }
57
+
58
+ export interface UsbControllerOptions {
59
+ onData: (data: Buffer, length: number, timestampSec: number) => void
60
+ serialNumber?: string
61
+ }
62
+
63
+ export type UsbControllerConstructor = new (
64
+ options?: UsbControllerOptions
65
+ ) => UsbController
@@ -1,5 +1,5 @@
1
1
  import {
2
- handleError,
2
+ handleLslError,
3
3
  InfoHandle,
4
4
  InletHandle,
5
5
  LiblslAdapter,
@@ -18,7 +18,7 @@ import { CreateInletPayload } from './LslStreamInlet.worker.js'
18
18
 
19
19
  export default class StreamInletWorker {
20
20
  public static lsl = LiblslAdapter.getInstance()
21
- public static handleError = handleError
21
+ public static handleLslError = handleLslError
22
22
  public static freePointer = freePointer
23
23
 
24
24
  private sourceId!: string
@@ -179,12 +179,12 @@ export default class StreamInletWorker {
179
179
  }
180
180
 
181
181
  private pullSample = () => {
182
- const timestamp = this.doPullsample()
182
+ const timestampSec = this.doPullsample()
183
183
 
184
- if (timestamp > 0) {
184
+ if (timestampSec > 0) {
185
185
  return {
186
186
  samples: this.readSamplesFromBuffer(),
187
- timestamps: [timestamp],
187
+ timestamps: [timestampSec],
188
188
  }
189
189
  }
190
190
  return { samples: undefined, timestamps: undefined }
@@ -210,9 +210,9 @@ export default class StreamInletWorker {
210
210
  }
211
211
 
212
212
  private pullChunk = () => {
213
- const firstTimestamp = this.doPullChunk()
213
+ const firstTimestampSec = this.doPullChunk()
214
214
 
215
- if (firstTimestamp > 0) {
215
+ if (firstTimestampSec > 0) {
216
216
  return {
217
217
  samples: this.readSamplesFromBuffer(),
218
218
  timestamps: this.readTimestampsFromBuffer(),
@@ -274,16 +274,16 @@ export default class StreamInletWorker {
274
274
 
275
275
  private async pullDataOnce() {
276
276
  const { samples, timestamps } = this.pullMethod()
277
- this.handleErrorIfPresent()
277
+ this.handleLslErrorIfPresent()
278
278
 
279
279
  if (samples && timestamps) {
280
280
  this.onData(samples, timestamps)
281
281
  }
282
282
  }
283
283
 
284
- private handleErrorIfPresent() {
284
+ private handleLslErrorIfPresent() {
285
285
  const errorCode = this.pullErrorBuffer.readInt32LE()
286
- this.handleError(errorCode)
286
+ this.handleLslError(errorCode)
287
287
  }
288
288
 
289
289
  private async waitBetweenPulls() {
@@ -338,8 +338,8 @@ export default class StreamInletWorker {
338
338
  return StreamInletWorker.lsl
339
339
  }
340
340
 
341
- private get handleError() {
342
- return StreamInletWorker.handleError
341
+ private get handleLslError() {
342
+ return StreamInletWorker.handleLslError
343
343
  }
344
344
 
345
345
  private get freePointer() {
@@ -4,7 +4,7 @@ import {
4
4
  LiblslAdapter,
5
5
  Liblsl,
6
6
  OutletHandle,
7
- handleError,
7
+ handleLslError,
8
8
  LslErrorCode,
9
9
  } from '@neurodevs/ndx-native'
10
10
 
@@ -20,10 +20,10 @@ export function setOutletLiblslAdapter(adapter: Liblsl) {
20
20
  lsl = LiblslAdapter.getInstance()
21
21
  }
22
22
 
23
- let handleErrorFn = handleError
23
+ let handleLslErrorFn = handleLslError
24
24
 
25
- export function setOutletHandleError(fn: typeof handleError) {
26
- handleErrorFn = fn
25
+ export function setOutletHandleLslError(fn: typeof handleLslError) {
26
+ handleLslErrorFn = fn
27
27
  }
28
28
 
29
29
  let info: StreamInfo
@@ -84,7 +84,7 @@ export function pushSample(msg: {
84
84
  payload: PushSamplePayload
85
85
  }) {
86
86
  const { payload } = msg
87
- const { sample, timestamp = lsl.localClock() } = payload
87
+ const { sample, timestampSec = lsl.localClock() } = payload
88
88
 
89
89
  if (!handle || !pushMethod) {
90
90
  throw new Error('Outlet not initialized')
@@ -93,11 +93,11 @@ export function pushSample(msg: {
93
93
  const errorCode = pushMethod({
94
94
  outletHandle: handle,
95
95
  sample,
96
- timestamp,
96
+ timestampSec,
97
97
  })
98
98
 
99
99
  try {
100
- handleErrorFn(errorCode)
100
+ handleLslErrorFn(errorCode)
101
101
  } catch {
102
102
  parentPort?.postMessage({
103
103
  type: 'error',
@@ -140,5 +140,5 @@ export interface CreateOutletPayload {
140
140
 
141
141
  export interface PushSamplePayload {
142
142
  sample: unknown
143
- timestamp?: number
143
+ timestampSec?: number
144
144
  }
package/src/index.ts CHANGED
@@ -1,10 +1,21 @@
1
- // BackpressureMonitor
1
+ // ClockRegressor
2
2
 
3
- export { default as LslBackpressureMonitor } from './impl/LslBackpressureMonitor.js'
4
- export * from './impl/LslBackpressureMonitor.js'
3
+ export { default as WindowedClockRegressor } from './impl/WindowedClockRegressor.js'
4
+ export * from './impl/WindowedClockRegressor.js'
5
5
 
6
- export { default as FakeBackpressureMonitor } from './testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js'
7
- export * from './testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js'
6
+ export { default as FakeClockRegressor } from './testDoubles/ClockRegressor/FakeClockRegressor.js'
7
+ export * from './testDoubles/ClockRegressor/FakeClockRegressor.js'
8
+
9
+ // DeviceController
10
+
11
+ export { default as BleDeviceController } from './impl/controllers/BleDeviceController.js'
12
+ export * from './impl/controllers/BleDeviceController.js'
13
+
14
+ export { default as FakeBleController } from './testDoubles/BleController/FakeBleController.js'
15
+ export * from './testDoubles/BleController/FakeBleController.js'
16
+
17
+ export { default as SpyBleController } from './testDoubles/BleController/SpyBleController.js'
18
+ export * from './testDoubles/BleController/SpyBleController.js'
8
19
 
9
20
  // EventMarkerEmitter
10
21
 
@@ -47,6 +58,14 @@ export * from './impl/LslStreamOutlet.js'
47
58
  export { default as FakeStreamOutlet } from './testDoubles/StreamOutlet/FakeStreamOutlet.js'
48
59
  export * from './testDoubles/StreamOutlet/FakeStreamOutlet.js'
49
60
 
61
+ // UsbController
62
+
63
+ export { default as UsbDeviceController } from './impl/controllers/UsbDeviceController.js'
64
+ export * from './impl/controllers/UsbDeviceController.js'
65
+
66
+ export { default as FakeUsbController } from './testDoubles/UsbController/FakeUsbController.js'
67
+ export * from './testDoubles/UsbController/FakeUsbController.js'
68
+
50
69
  // WebSocketBridge
51
70
 
52
71
  export { default as LslWebSocketBridge } from './impl/LslWebSocketBridge.js'
@@ -0,0 +1,50 @@
1
+ import koffi from 'koffi'
2
+
3
+ import BleDeviceController from '../impl/controllers/BleDeviceController.js'
4
+
5
+ const MUSE_CHARACTERISTIC_UUIDS: Record<string, string> = {
6
+ CONTROL: '273E0001-4C4D-454D-96BE-F03BAC821358',
7
+ TELEMETRY: '273E000B-4C4D-454D-96BE-F03BAC821358',
8
+ GYROSCOPE: '273E0009-4C4D-454D-96BE-F03BAC821358',
9
+ ACCELEROMETER: '273E000A-4C4D-454D-96BE-F03BAC821358',
10
+ PPG_AMBIENT: '273E000F-4C4D-454D-96BE-F03BAC821358',
11
+ PPG_INFRARED: '273E0010-4C4D-454D-96BE-F03BAC821358',
12
+ PPG_RED: '273E0011-4C4D-454D-96BE-F03BAC821358',
13
+ EEG_TP9: '273E0003-4C4D-454D-96BE-F03BAC821358',
14
+ EEG_AF7: '273E0004-4C4D-454D-96BE-F03BAC821358',
15
+ EEG_AF8: '273E0005-4C4D-454D-96BE-F03BAC821358',
16
+ EEG_TP10: '273E0006-4C4D-454D-96BE-F03BAC821358',
17
+ EEG_AUX: '273E0007-4C4D-454D-96BE-F03BAC821358',
18
+ }
19
+
20
+ const CONTROL_UUID = MUSE_CHARACTERISTIC_UUIDS['CONTROL']
21
+
22
+ const charCallbacks = Object.entries(MUSE_CHARACTERISTIC_UUIDS).map(
23
+ ([name, uuid]) => {
24
+ return {
25
+ charUuid: uuid,
26
+ charName: name,
27
+ onData: (data: Buffer, length: number, timestampSec: number) => {
28
+ const bytes = koffi.decode(data, 'uint8', length) as number[]
29
+ console.info(`[${timestampSec}] length=${length}`, bytes)
30
+ },
31
+ }
32
+ }
33
+ )
34
+
35
+ const muse = await BleDeviceController.Create({
36
+ deviceNamePrefix: 'Muse',
37
+ charCallbacks,
38
+ rssiIntervalMs: 1000,
39
+ })
40
+
41
+ await muse.connect()
42
+
43
+ for (const cmd of ['h', 'p50', 's', 'd']) {
44
+ await muse.writeCharacteristic(CONTROL_UUID, cmd)
45
+ await new Promise((resolve) => setTimeout(resolve, 100))
46
+ }
47
+
48
+ await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
49
+
50
+ await muse.disconnect()
@@ -0,0 +1,50 @@
1
+ import koffi from 'koffi'
2
+
3
+ import BleDeviceController from '../impl/controllers/BleDeviceController.js'
4
+
5
+ const MUSE_CHARACTERISTIC_UUIDS: Record<string, string> = {
6
+ CONTROL: '273E0001-4C4D-454D-96BE-F03BAC821358',
7
+ TELEMETRY: '273E000B-4C4D-454D-96BE-F03BAC821358',
8
+ GYROSCOPE: '273E0009-4C4D-454D-96BE-F03BAC821358',
9
+ ACCELEROMETER: '273E000A-4C4D-454D-96BE-F03BAC821358',
10
+ PPG_AMBIENT: '273E000F-4C4D-454D-96BE-F03BAC821358',
11
+ PPG_INFRARED: '273E0010-4C4D-454D-96BE-F03BAC821358',
12
+ PPG_RED: '273E0011-4C4D-454D-96BE-F03BAC821358',
13
+ EEG_TP9: '273E0003-4C4D-454D-96BE-F03BAC821358',
14
+ EEG_AF7: '273E0004-4C4D-454D-96BE-F03BAC821358',
15
+ EEG_AF8: '273E0005-4C4D-454D-96BE-F03BAC821358',
16
+ EEG_TP10: '273E0006-4C4D-454D-96BE-F03BAC821358',
17
+ EEG_AUX: '273E0007-4C4D-454D-96BE-F03BAC821358',
18
+ }
19
+
20
+ const CONTROL_UUID = MUSE_CHARACTERISTIC_UUIDS['CONTROL']
21
+
22
+ const charCallbacks = Object.entries(MUSE_CHARACTERISTIC_UUIDS).map(
23
+ ([name, uuid]) => {
24
+ return {
25
+ charUuid: uuid,
26
+ charName: name,
27
+ onData: (data: Buffer, length: number, timestampSec: number) => {
28
+ const bytes = koffi.decode(data, 'uint8', length) as number[]
29
+ console.info(`[${timestampSec}] length=${length}`, bytes)
30
+ },
31
+ }
32
+ }
33
+ )
34
+
35
+ const muse = await BleDeviceController.Create({
36
+ deviceUuid: 'CA6A61B7-B7A8-AF24-3C9E-04A6A5012554',
37
+ charCallbacks,
38
+ rssiIntervalMs: 1000,
39
+ })
40
+
41
+ await muse.connect()
42
+
43
+ for (const cmd of ['h', 'p50', 's', 'd']) {
44
+ await muse.writeCharacteristic(CONTROL_UUID, cmd)
45
+ await new Promise((resolve) => setTimeout(resolve, 100))
46
+ }
47
+
48
+ await new Promise((resolve) => setTimeout(resolve, 5 * 1000))
49
+
50
+ await muse.disconnect()
@@ -0,0 +1,20 @@
1
+ import UsbDeviceController from '../impl/controllers/UsbDeviceController.js'
2
+
3
+ console.info('\nCreating UsbDeviceController...')
4
+
5
+ const controller = UsbDeviceController.Create({
6
+ serialNumber: 'DP04WG8J',
7
+ onData: (_data: Buffer, length: number, timestampSec: number) => {
8
+ console.info(`[${timestampSec}] Buffer length: ${length}`)
9
+ },
10
+ })
11
+
12
+ console.info('Connecting to UsbDeviceController...')
13
+
14
+ await controller.connect()
15
+
16
+ console.info('Waiting for 10 seconds...')
17
+
18
+ await new Promise((resolve) => setTimeout(resolve, 10000))
19
+
20
+ console.info('Done!')
@@ -0,0 +1,81 @@
1
+ import generateId from '@neurodevs/generate-id'
2
+ import {
3
+ BleController,
4
+ BleControllerOptions,
5
+ CharacteristicCallbacks,
6
+ } from '../../impl/controllers/BleDeviceController.js'
7
+
8
+ export default class FakeBleController implements BleController {
9
+ public static callsToConstructor: (BleControllerOptions | undefined)[] = []
10
+
11
+ public static numCallsToConnect = 0
12
+
13
+ public static callsToWriteCharacteristic: {
14
+ characteristicUuid: string
15
+ value: string
16
+ }[] = []
17
+
18
+ public static callsToSubscribeCharacteristics: CharacteristicCallbacks[] =
19
+ []
20
+
21
+ public static numCallsToDisconnect = 0
22
+
23
+ public static fakeCharacteristics: Record<string, unknown> = {}
24
+ public static fakeName = `fake-${generateId()}`
25
+
26
+ private _uuid: string
27
+ private _name: string
28
+
29
+ public constructor(options?: BleControllerOptions) {
30
+ const { deviceUuid } = options ?? {}
31
+
32
+ this._uuid = deviceUuid ?? generateId()
33
+ this._name = FakeBleController.fakeName
34
+
35
+ FakeBleController.callsToConstructor.push(options)
36
+ }
37
+
38
+ public async connect() {
39
+ FakeBleController.numCallsToConnect++
40
+ }
41
+
42
+ public async writeCharacteristic(
43
+ characteristicUuid: string,
44
+ value: string
45
+ ) {
46
+ FakeBleController.callsToWriteCharacteristic.push({
47
+ characteristicUuid,
48
+ value,
49
+ })
50
+ }
51
+
52
+ public async subscribeCharacteristics(
53
+ charCallbacks: CharacteristicCallbacks
54
+ ) {
55
+ FakeBleController.callsToSubscribeCharacteristics.push(charCallbacks)
56
+ }
57
+
58
+ public async disconnect() {
59
+ FakeBleController.numCallsToDisconnect++
60
+ }
61
+
62
+ public get uuid() {
63
+ return this._uuid
64
+ }
65
+
66
+ public get name() {
67
+ return this._name
68
+ }
69
+
70
+ public static resetTestDouble() {
71
+ this.callsToConstructor = []
72
+ this.numCallsToConnect = 0
73
+ this.callsToWriteCharacteristic = []
74
+ this.callsToSubscribeCharacteristics = []
75
+ this.numCallsToDisconnect = 0
76
+ }
77
+ }
78
+
79
+ export interface CallToBleControllerConstructor {
80
+ options: BleControllerOptions
81
+ }