@neurodevs/node-lsl 23.1.1 → 23.2.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 (32) hide show
  1. package/build/__tests__/AbstractPackageTest.d.ts +1 -0
  2. package/build/__tests__/AbstractPackageTest.js +1 -0
  3. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  4. package/build/__tests__/impl/BleGattController.test.d.ts +1 -0
  5. package/build/__tests__/impl/BleGattController.test.js +9 -0
  6. package/build/__tests__/impl/BleGattController.test.js.map +1 -1
  7. package/build/__tests__/impl/BleObserverController.test.d.ts +12 -1
  8. package/build/__tests__/impl/BleObserverController.test.js +77 -5
  9. package/build/__tests__/impl/BleObserverController.test.js.map +1 -1
  10. package/build/__tests__/impl/LslStreamInlet.test.d.ts +4 -5
  11. package/build/__tests__/impl/LslStreamInlet.test.js +37 -58
  12. package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
  13. package/build/impl/LslStreamInlet.d.ts +9 -39
  14. package/build/impl/LslStreamInlet.js +23 -151
  15. package/build/impl/LslStreamInlet.js.map +1 -1
  16. package/build/impl/clocks/WindowedClockRegressor.d.ts +2 -2
  17. package/build/impl/clocks/WindowedClockRegressor.js.map +1 -1
  18. package/build/impl/controllers/BleGattController.d.ts +4 -3
  19. package/build/impl/controllers/BleGattController.js +6 -0
  20. package/build/impl/controllers/BleGattController.js.map +1 -1
  21. package/build/impl/controllers/BleObserverController.d.ts +11 -3
  22. package/build/impl/controllers/BleObserverController.js +25 -2
  23. package/build/impl/controllers/BleObserverController.js.map +1 -1
  24. package/package.json +3 -4
  25. package/src/__tests__/AbstractPackageTest.ts +2 -0
  26. package/src/__tests__/impl/BleGattController.test.ts +13 -0
  27. package/src/__tests__/impl/BleObserverController.test.ts +111 -6
  28. package/src/__tests__/impl/LslStreamInlet.test.ts +45 -86
  29. package/src/impl/LslStreamInlet.ts +38 -218
  30. package/src/impl/clocks/WindowedClockRegressor.ts +1 -1
  31. package/src/impl/controllers/BleGattController.ts +12 -2
  32. package/src/impl/controllers/BleObserverController.ts +37 -4
@@ -1,13 +1,5 @@
1
1
  import { FakeLiblsl } from '@neurodevs/ndx-native'
2
2
  import { test, assert } from '@neurodevs/node-tdd'
3
- import {
4
- createPointer,
5
- DataType,
6
- FieldType,
7
- JsExternal,
8
- PointerType,
9
- unwrapPointer,
10
- } from 'ffi-rs'
11
3
 
12
4
  import LslStreamInlet, { LslInletOptions } from '../../impl/LslStreamInlet.js'
13
5
  import SpyLslInlet from '../../testDoubles/LslInlet/SpyLslInlet.js'
@@ -21,27 +13,6 @@ export default class LslStreamInletTest extends AbstractPackageTest {
21
13
  timestamps: number[]
22
14
  }[]
23
15
 
24
- private static readonly sampleBufferPtr = unwrapPointer(
25
- createPointer({
26
- paramsType: [DataType.U8Array],
27
- paramsValue: [new Float32Array(this.channelCount * this.chunkSize)],
28
- })
29
- )[0]
30
-
31
- private static readonly timestampBufferPtr = unwrapPointer(
32
- createPointer({
33
- paramsType: [DataType.U8Array],
34
- paramsValue: [new Float64Array(this.chunkSize)],
35
- })
36
- )[0]
37
-
38
- private static readonly errorCodePtr = unwrapPointer(
39
- createPointer({
40
- paramsType: [DataType.U8Array],
41
- paramsValue: [new Int32Array(1)],
42
- })
43
- )[0]
44
-
45
16
  protected static async beforeAll() {
46
17
  assert.isEqual(
47
18
  LslStreamInlet.waitAfterOpenStreamMs,
@@ -60,7 +31,6 @@ export default class LslStreamInletTest extends AbstractPackageTest {
60
31
 
61
32
  LslStreamInlet.waitAfterOpenStreamMs = 0
62
33
  LslStreamInlet.lsl = this.fakeLiblsl
63
- LslStreamInlet.freePointer = () => {}
64
34
 
65
35
  this.instance = await this.LslStreamInlet()
66
36
  }
@@ -81,6 +51,7 @@ export default class LslStreamInletTest extends AbstractPackageTest {
81
51
  {
82
52
  infoHandle: this.inletHandle,
83
53
  maxBufferedMs: this.maxBufferedMs,
54
+ chunkSize: this.chunkSize,
84
55
  },
85
56
  'Should have called createInlet!'
86
57
  )
@@ -101,46 +72,39 @@ export default class LslStreamInletTest extends AbstractPackageTest {
101
72
 
102
73
  @test()
103
74
  protected static async destroyCallsLslBinding() {
104
- await this.startPullingThenDestroy()
75
+ await this.startPulling()
76
+ const inletHandle = this.inletHandle
105
77
 
106
- assert.isEqualDeep(
107
- this.fakeLiblsl.lastDestroyInletOptions,
108
- { inletHandle: this.inletHandle },
78
+ this.destroy()
79
+
80
+ assert.isTrue(
81
+ inletHandle !== undefined &&
82
+ this.fakeLiblsl.lastDestroyInletOptions?.inletHandle ===
83
+ inletHandle,
109
84
  'Did not destroy inlet!'
110
85
  )
111
86
  }
112
87
 
113
88
  @test()
114
- protected static async destroyFreesAllPointers() {
115
- interface FreePointerParams {
116
- paramsType: FieldType[]
117
- paramsValue: JsExternal[]
118
- pointerType: PointerType
119
- }
120
-
121
- let calls: FreePointerParams[] = []
89
+ protected static async destroyDoesNotDestroyInletThatWasNeverCreated() {
90
+ this.destroy()
122
91
 
123
- LslStreamInlet.freePointer = (params: FreePointerParams) => {
124
- calls.push(params)
125
- }
92
+ assert.isEqual(
93
+ this.fakeLiblsl.destroyInletHitCount,
94
+ 0,
95
+ 'Should not have destroyed an inlet that was never created!'
96
+ )
97
+ }
126
98
 
99
+ @test()
100
+ protected static async destroyingTwiceDestroysInletOnce() {
127
101
  await this.startPullingThenDestroy()
102
+ this.destroy()
128
103
 
129
- const { paramsType, pointerType } = calls[0]
130
-
131
- assert.isEqualDeep(
132
- { paramsType, pointerType },
133
- {
134
- paramsType: [
135
- DataType.U8Array,
136
- DataType.U8Array,
137
- DataType.U8Array,
138
- DataType.U8Array,
139
- ],
140
- pointerType: PointerType.CPointer,
141
- },
142
-
143
- 'Destroy did not free all pointers!'
104
+ assert.isEqual(
105
+ this.fakeLiblsl.destroyInletHitCount,
106
+ 1,
107
+ 'Should have destroyed inlet only once!'
144
108
  )
145
109
  }
146
110
 
@@ -207,7 +171,6 @@ export default class LslStreamInletTest extends AbstractPackageTest {
207
171
  {
208
172
  inletHandle: this.inletHandle,
209
173
  timeoutMs: aboutOneYearInMs,
210
- errorCodePtr: this.errorCodePtr,
211
174
  },
212
175
  'Did not open inlet stream!'
213
176
  )
@@ -230,7 +193,6 @@ export default class LslStreamInletTest extends AbstractPackageTest {
230
193
  {
231
194
  inletHandle: instance['inletHandle'],
232
195
  timeoutMs: openStreamTimeoutMs,
233
- errorCodePtr: this.errorCodePtr,
234
196
  },
235
197
  'Did not open inlet stream with passed timeout!'
236
198
  )
@@ -316,13 +278,7 @@ export default class LslStreamInletTest extends AbstractPackageTest {
316
278
 
317
279
  assert.isEqualDeep(
318
280
  this.fakeLiblsl.lastPullSampleOptions,
319
- {
320
- inletHandle: this.inletHandle,
321
- sampleBufferPtr: this.sampleBufferPtr,
322
- sampleBufferElements: this.channelCount,
323
- timeoutMs: 0,
324
- errorCodePtr: this.errorCodePtr,
325
- },
281
+ { inletHandle: this.inletHandle, timeoutMs: 0 },
326
282
  'Should have called pullSample!'
327
283
  )
328
284
  }
@@ -333,33 +289,36 @@ export default class LslStreamInletTest extends AbstractPackageTest {
333
289
 
334
290
  assert.isEqualDeep(
335
291
  this.fakeLiblsl.lastPullChunkOptions,
336
- {
337
- inletHandle: this.inletHandle,
338
- sampleBufferPtr: this.sampleBufferPtr,
339
- timestampBufferPtr: this.timestampBufferPtr,
340
- sampleBufferElements: this.chunkSize * this.channelCount,
341
- timestampBufferElements: this.chunkSize,
342
- timeoutMs: 0,
343
- errorCodePtr: this.errorCodePtr,
344
- },
292
+ { inletHandle: this.inletHandle, timeoutMs: 0 },
345
293
  'Should have called pullChunk!'
346
294
  )
347
295
  }
348
296
 
349
297
  @test()
350
- protected static async pushSampleHandlesErrorCode() {
351
- let passedErrorCode: number | undefined
298
+ protected static async passesEachPulledSampleToOnData() {
299
+ await this.runChunkSizeOne()
352
300
 
353
- LslStreamInlet.handleLslError = (errorCode: number) => {
354
- passedErrorCode = errorCode
355
- }
301
+ assert.isEqualDeep(
302
+ this.callsToOnData,
303
+ FakeLiblsl.fakeSamples.map((sample) => ({
304
+ samples: Array.from(sample),
305
+ timestamps: [FakeLiblsl.fakeSampleTimestampSec],
306
+ })),
307
+ 'Did not pass each pulled sample to onData!'
308
+ )
309
+ }
356
310
 
311
+ @test()
312
+ protected static async passesEachPulledChunkToOnData() {
357
313
  await this.startThenStop()
358
314
 
359
315
  assert.isEqualDeep(
360
- passedErrorCode,
361
- FakeLiblsl.fakeErrorCode,
362
- 'Did not pass the expected error code to handleLslError!'
316
+ this.callsToOnData,
317
+ FakeLiblsl.fakeChunks.map((chunk, i) => ({
318
+ samples: Array.from(chunk),
319
+ timestamps: Array.from(FakeLiblsl.fakeTimestamps[i]),
320
+ })),
321
+ 'Did not pass each pulled chunk to onData!'
363
322
  )
364
323
  }
365
324
 
@@ -1,24 +1,9 @@
1
- import {
2
- handleLslError,
3
- InfoHandle,
4
- InletHandle,
5
- LiblslAdapter,
6
- } from '@neurodevs/ndx-native'
7
- import {
8
- JsExternal,
9
- unwrapPointer,
10
- createPointer,
11
- DataType,
12
- freePointer,
13
- PointerType,
14
- } from 'ffi-rs'
1
+ import { InfoHandle, InletHandle, LiblslAdapter } from '@neurodevs/ndx-native'
15
2
 
16
3
  export default class LslStreamInlet implements LslInlet {
17
4
  public static Class?: LslInletConstructor
18
5
  public static waitAfterOpenStreamMs = 100
19
6
  public static lsl = LiblslAdapter.getInstance()
20
- public static handleLslError = handleLslError
21
- public static freePointer = freePointer
22
7
 
23
8
  public isRunning = false
24
9
 
@@ -32,37 +17,12 @@ export default class LslStreamInlet implements LslInlet {
32
17
  private onData: OnDataCallback
33
18
 
34
19
  private infoHandle!: InfoHandle
35
- private inletHandle!: InletHandle
36
- private channelCount!: number
37
-
38
- private pullMethod!: () => {
39
- samples: number[] | undefined
40
- timestamps: number[] | undefined
41
- }
42
-
43
- private sampleBuffer!: Buffer<ArrayBuffer>
44
- private sampleBufferRef!: JsExternal[]
45
- private sampleBufferPtr!: JsExternal
46
-
47
- private timestampBuffer!: Buffer<ArrayBuffer>
48
- private timestampBufferRef!: JsExternal[]
49
- private timestampBufferPtr!: JsExternal
50
-
51
- private pullErrorBuffer!: Buffer<ArrayBuffer>
52
- private pullErrorBufferRef!: JsExternal[]
53
- private pullErrorBufferPtr!: JsExternal
54
-
55
- private openStreamErrorBuffer!: Buffer<ArrayBuffer>
56
- private openStreamErrorBufferRef!: JsExternal[]
57
- private openStreamErrorBufferPtr!: JsExternal
20
+ private inletHandle?: InletHandle
21
+ private pullMethod: () => PulledData | undefined
58
22
 
59
23
  private readonly sixMinutesInMs = 360 * 1000
60
24
  private readonly aboutOneYearInMs = 32000000 * 1000
61
25
 
62
- private readonly bytesPerFloat = 4
63
- private readonly bytesPerDouble = 8
64
- private readonly bytesPerI32 = 4
65
-
66
26
  protected constructor(options: LslInletOptions, onData: OnDataCallback) {
67
27
  const {
68
28
  sourceId,
@@ -82,6 +42,10 @@ export default class LslStreamInlet implements LslInlet {
82
42
  this.waitBetweenPullsMs = waitBetweenPullsMs ?? 1
83
43
  this.flushInletOnStop = flushInletOnStop ?? true
84
44
  this.onData = onData
45
+
46
+ this.pullMethod = (
47
+ chunkSize === 1 ? this.pullSample : this.pullChunk
48
+ ).bind(this)
85
49
  }
86
50
 
87
51
  public static async Create(
@@ -106,10 +70,6 @@ export default class LslStreamInlet implements LslInlet {
106
70
  private async createInlet() {
107
71
  this.infoHandle = this.resolveInfoHandle()
108
72
  this.inletHandle = this.doCreateInlet()
109
- this.channelCount = this.getChannelCount()
110
-
111
- this.allocateWritableBuffers()
112
- this.setPullMethod()
113
73
 
114
74
  this.openStream()
115
75
  }
@@ -146,144 +106,14 @@ export default class LslStreamInlet implements LslInlet {
146
106
  return this.lsl.createInlet({
147
107
  infoHandle: this.infoHandle,
148
108
  maxBufferedMs: this.maxBufferedMs,
109
+ chunkSize: this.chunkSize,
149
110
  })
150
111
  }
151
112
 
152
- private getChannelCount() {
153
- return this.lsl.getChannelCount({ infoHandle: this.infoHandle })
154
- }
155
-
156
- private allocateWritableBuffers() {
157
- this.allocateDataBuffer()
158
- this.allocateTimestampBuffer()
159
- this.allocatePullErrorBuffer()
160
- this.allocateOpenStreamErrorBuffer()
161
- }
162
-
163
- private allocateDataBuffer() {
164
- this.sampleBuffer = Buffer.alloc(
165
- this.channelCount * this.chunkSize * this.bytesPerFloat
166
- )
167
-
168
- this.sampleBufferRef = createPointer({
169
- paramsType: [DataType.U8Array],
170
- paramsValue: [this.sampleBuffer],
171
- })
172
-
173
- this.sampleBufferPtr = unwrapPointer(this.sampleBufferRef)[0]
174
- }
175
-
176
- private allocateTimestampBuffer() {
177
- this.timestampBuffer = Buffer.alloc(
178
- this.chunkSize * this.bytesPerDouble
179
- )
180
-
181
- this.timestampBufferRef = createPointer({
182
- paramsType: [DataType.U8Array],
183
- paramsValue: [this.timestampBuffer],
184
- })
185
-
186
- this.timestampBufferPtr = unwrapPointer(this.timestampBufferRef)[0]
187
- }
188
-
189
- private allocatePullErrorBuffer() {
190
- this.pullErrorBuffer = Buffer.alloc(this.bytesPerI32)
191
-
192
- this.pullErrorBufferRef = createPointer({
193
- paramsType: [DataType.U8Array],
194
- paramsValue: [this.pullErrorBuffer],
195
- })
196
-
197
- this.pullErrorBufferPtr = unwrapPointer(this.pullErrorBufferRef)[0]
198
- }
199
-
200
- private allocateOpenStreamErrorBuffer() {
201
- this.openStreamErrorBuffer = Buffer.alloc(this.bytesPerI32)
202
-
203
- this.openStreamErrorBufferRef = createPointer({
204
- paramsType: [DataType.U8Array],
205
- paramsValue: [this.openStreamErrorBuffer],
206
- })
207
-
208
- this.openStreamErrorBufferPtr = unwrapPointer(
209
- this.openStreamErrorBufferRef
210
- )[0]
211
- }
212
-
213
- private setPullMethod() {
214
- this.pullMethod =
215
- this.chunkSize === 1 ? this.pullSample : this.pullChunk
216
- }
217
-
218
- private pullSample = () => {
219
- const timestampSec = this.doPullsample()
220
-
221
- if (timestampSec > 0) {
222
- return {
223
- samples: this.readSamplesFromBuffer(),
224
- timestamps: [timestampSec],
225
- }
226
- }
227
- return { samples: undefined, timestamps: undefined }
228
- }
229
-
230
- private doPullsample() {
231
- return this.lsl.pullSample({
232
- inletHandle: this.inletHandle,
233
- sampleBufferPtr: this.sampleBufferPtr,
234
- sampleBufferElements: this.channelCount,
235
- timeoutMs: this.pullTimeoutMs,
236
- errorCodePtr: this.pullErrorBufferPtr,
237
- })
238
- }
239
-
240
- private readSamplesFromBuffer() {
241
- const floats = new Float32Array(
242
- this.sampleBuffer.buffer,
243
- this.sampleBuffer.byteOffset,
244
- this.chunkSize * this.channelCount
245
- )
246
- return Array.from(floats)
247
- }
248
-
249
- private pullChunk = () => {
250
- const firstTimestampSec = this.doPullChunk()
251
-
252
- if (firstTimestampSec > 0) {
253
- return {
254
- samples: this.readSamplesFromBuffer(),
255
- timestamps: this.readTimestampsFromBuffer(),
256
- }
257
- }
258
- return { samples: undefined, timestamps: undefined }
259
- }
260
-
261
- private doPullChunk() {
262
- return this.lsl.pullChunk({
263
- inletHandle: this.inletHandle,
264
- sampleBufferPtr: this.sampleBufferPtr,
265
- sampleBufferElements: this.chunkSize * this.channelCount,
266
- timestampBufferPtr: this.timestampBufferPtr,
267
- timestampBufferElements: this.chunkSize,
268
- timeoutMs: this.pullTimeoutMs,
269
- errorCodePtr: this.pullErrorBufferPtr,
270
- })
271
- }
272
-
273
- private readTimestampsFromBuffer() {
274
- const doubles = new Float64Array(
275
- this.timestampBuffer.buffer,
276
- this.timestampBuffer.byteOffset,
277
- this.chunkSize
278
- )
279
- return Array.from(doubles)
280
- }
281
-
282
113
  private openStream() {
283
114
  this.lsl.openStream({
284
- inletHandle: this.inletHandle,
115
+ inletHandle: this.inletHandle!,
285
116
  timeoutMs: this.openStreamTimeoutMs,
286
- errorCodePtr: this.openStreamErrorBufferPtr,
287
117
  })
288
118
  }
289
119
 
@@ -295,17 +125,26 @@ export default class LslStreamInlet implements LslInlet {
295
125
  }
296
126
 
297
127
  private async pullDataOnce() {
298
- const { samples, timestamps } = this.pullMethod()
299
- this.handleLslErrorIfPresent()
128
+ const pulled = this.pullMethod()
300
129
 
301
- if (samples && timestamps) {
302
- this.onData(samples, timestamps)
130
+ if (pulled) {
131
+ this.onData(pulled.samples, pulled.timestamps)
303
132
  }
304
133
  }
305
134
 
306
- private handleLslErrorIfPresent() {
307
- const errorCode = this.pullErrorBuffer.readInt32LE()
308
- this.handleLslError(errorCode)
135
+ private pullSample() {
136
+ return this.lsl.pullSample(this.pullOptions)
137
+ }
138
+
139
+ private pullChunk() {
140
+ return this.lsl.pullChunk(this.pullOptions)
141
+ }
142
+
143
+ private get pullOptions() {
144
+ return {
145
+ inletHandle: this.inletHandle!,
146
+ timeoutMs: this.pullTimeoutMs,
147
+ }
309
148
  }
310
149
 
311
150
  private async waitBetweenPulls() {
@@ -313,7 +152,7 @@ export default class LslStreamInlet implements LslInlet {
313
152
  }
314
153
 
315
154
  public flushInlet() {
316
- this.lsl.flushInlet({ inletHandle: this.inletHandle })
155
+ this.lsl.flushInlet({ inletHandle: this.inletHandle! })
317
156
  }
318
157
 
319
158
  public stopPulling() {
@@ -326,7 +165,7 @@ export default class LslStreamInlet implements LslInlet {
326
165
  }
327
166
 
328
167
  private closeStream() {
329
- this.lsl.closeStream({ inletHandle: this.inletHandle })
168
+ this.lsl.closeStream({ inletHandle: this.inletHandle! })
330
169
  }
331
170
 
332
171
  public destroy() {
@@ -334,43 +173,19 @@ export default class LslStreamInlet implements LslInlet {
334
173
  this.stopPulling()
335
174
  }
336
175
 
337
- this.doDestroyInlet()
338
- this.freeNativePointers()
339
- }
340
-
341
- private doDestroyInlet() {
342
- this.lsl.destroyInlet({ inletHandle: this.inletHandle })
176
+ this.destroyInletIfCreated()
343
177
  }
344
178
 
345
- private freeNativePointers() {
346
- this.freePointer({
347
- paramsType: [
348
- DataType.U8Array,
349
- DataType.U8Array,
350
- DataType.U8Array,
351
- DataType.U8Array,
352
- ],
353
- paramsValue: [
354
- this.openStreamErrorBufferPtr,
355
- this.sampleBufferPtr,
356
- this.timestampBufferPtr,
357
- this.pullErrorBufferPtr,
358
- ],
359
- pointerType: PointerType.CPointer,
360
- })
179
+ private destroyInletIfCreated() {
180
+ if (this.inletHandle) {
181
+ this.lsl.destroyInlet({ inletHandle: this.inletHandle })
182
+ delete this.inletHandle
183
+ }
361
184
  }
362
185
 
363
186
  private get lsl() {
364
187
  return LslStreamInlet.lsl
365
188
  }
366
-
367
- private get handleLslError() {
368
- return LslStreamInlet.handleLslError
369
- }
370
-
371
- private get freePointer() {
372
- return LslStreamInlet.freePointer
373
- }
374
189
  }
375
190
 
376
191
  export interface LslInlet {
@@ -396,4 +211,9 @@ export interface LslInletOptions {
396
211
  flushInletOnStop?: boolean
397
212
  }
398
213
 
214
+ export interface PulledData {
215
+ samples: number[]
216
+ timestamps: number[]
217
+ }
218
+
399
219
  export type OnDataCallback = (samples: number[], timestamps: number[]) => void
@@ -41,7 +41,7 @@ export default class WindowedClockRegressor implements ClockRegressor {
41
41
 
42
42
  const anchor = this.anchorTimeFor(deviceTime)
43
43
 
44
- const timestamps = Array(chunkSize).fill(0)
44
+ const timestamps: number[] = Array(chunkSize).fill(0)
45
45
 
46
46
  for (let i = 0; i <= chunkSize - 1; i++) {
47
47
  const stepsFromLast = chunkSize - 1 - i
@@ -24,6 +24,10 @@ export default class BleGattController implements BleGatt {
24
24
  rssiIntervalMs,
25
25
  } = options
26
26
 
27
+ if (deviceUuid && deviceNamePrefix) {
28
+ this.throwTooManyParams()
29
+ }
30
+
27
31
  this.state = { status: 'disconnected', uuid: deviceUuid }
28
32
  this.deviceNamePrefix = deviceNamePrefix
29
33
  this.charCallbacks = charCallbacks
@@ -35,6 +39,12 @@ export default class BleGattController implements BleGatt {
35
39
  return new (this.Class ?? this)(options)
36
40
  }
37
41
 
42
+ private throwTooManyParams() {
43
+ throw new Error(
44
+ 'Cannot pass both deviceUuid and deviceNamePrefix! Please pass only one.'
45
+ )
46
+ }
47
+
38
48
  public async connect() {
39
49
  if (!this.state.uuid) {
40
50
  await this.discoverUuid()
@@ -191,8 +201,8 @@ export default class BleGattController implements BleGatt {
191
201
  }
192
202
 
193
203
  export interface BleGatt {
194
- uuid: string
195
- name: string
204
+ readonly uuid: string
205
+ readonly name: string
196
206
 
197
207
  connect(): Promise<void>
198
208
 
@@ -3,14 +3,21 @@ import { LibndxAdapter, NativeAdvertisement } from '@neurodevs/ndx-native'
3
3
  export default class BleObserverController implements BleObserver {
4
4
  public static Class?: BleObserverConstructor
5
5
 
6
- private readonly deviceUuid: string
6
+ private readonly deviceNamePrefix?: string
7
7
  private readonly onAdvertisement?: OnAdvertisement
8
8
  private readonly ndx = LibndxAdapter.getInstance()
9
9
 
10
+ private deviceUuid: string
11
+
10
12
  protected constructor(options: BleObserverOptions) {
11
- const { deviceUuid, onAdvertisement } = options
13
+ const { deviceUuid, deviceNamePrefix, onAdvertisement } = options
14
+
15
+ if (deviceUuid && deviceNamePrefix) {
16
+ this.throwTooManyParams()
17
+ }
12
18
 
13
19
  this.deviceUuid = deviceUuid ?? ''
20
+ this.deviceNamePrefix = deviceNamePrefix
14
21
  this.onAdvertisement = onAdvertisement
15
22
  }
16
23
 
@@ -18,11 +25,35 @@ export default class BleObserverController implements BleObserver {
18
25
  return new (this.Class ?? this)(options)
19
26
  }
20
27
 
28
+ private throwTooManyParams() {
29
+ throw new Error(
30
+ 'Cannot pass both deviceUuid and deviceNamePrefix! Please pass only one.'
31
+ )
32
+ }
33
+
21
34
  public async startObserving() {
35
+ if (!this.deviceUuid && this.deviceNamePrefix) {
36
+ await this.discoverUuid(this.deviceNamePrefix)
37
+ }
38
+
22
39
  this.createBleObserverBackend()
23
40
  this.startBleObserverBackend()
24
41
  }
25
42
 
43
+ private discoverUuid(namePrefix: string) {
44
+ return new Promise<void>((resolve) => {
45
+ const { status, error } = this.ndx.discoverBleUuid({
46
+ namePrefix,
47
+ onDiscovered: (uuid: string) => {
48
+ this.deviceUuid = uuid
49
+ resolve()
50
+ },
51
+ })
52
+
53
+ this.throwIfError(status, error)
54
+ })
55
+ }
56
+
26
57
  private createBleObserverBackend() {
27
58
  const { status, error } = this.ndx.createBleObserverBackend({
28
59
  deviceUuid: this.deviceUuid,
@@ -70,9 +101,11 @@ export type BleObserverConstructor = new (
70
101
  ) => BleObserver
71
102
 
72
103
  export type BleObserverOptions = {
73
- deviceUuid: string
74
104
  onAdvertisement?: OnAdvertisement
75
- }
105
+ } & (
106
+ | { deviceUuid: string; deviceNamePrefix?: string }
107
+ | { deviceUuid?: string; deviceNamePrefix: string }
108
+ )
76
109
 
77
110
  export type BleAdvertisement = NativeAdvertisement
78
111