@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.
- package/build/__tests__/AbstractPackageTest.d.ts +1 -0
- package/build/__tests__/AbstractPackageTest.js +1 -0
- package/build/__tests__/AbstractPackageTest.js.map +1 -1
- package/build/__tests__/impl/BleGattController.test.d.ts +1 -0
- package/build/__tests__/impl/BleGattController.test.js +9 -0
- package/build/__tests__/impl/BleGattController.test.js.map +1 -1
- package/build/__tests__/impl/BleObserverController.test.d.ts +12 -1
- package/build/__tests__/impl/BleObserverController.test.js +77 -5
- package/build/__tests__/impl/BleObserverController.test.js.map +1 -1
- package/build/__tests__/impl/LslStreamInlet.test.d.ts +4 -5
- package/build/__tests__/impl/LslStreamInlet.test.js +37 -58
- package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
- package/build/impl/LslStreamInlet.d.ts +9 -39
- package/build/impl/LslStreamInlet.js +23 -151
- package/build/impl/LslStreamInlet.js.map +1 -1
- package/build/impl/clocks/WindowedClockRegressor.d.ts +2 -2
- package/build/impl/clocks/WindowedClockRegressor.js.map +1 -1
- package/build/impl/controllers/BleGattController.d.ts +4 -3
- package/build/impl/controllers/BleGattController.js +6 -0
- package/build/impl/controllers/BleGattController.js.map +1 -1
- package/build/impl/controllers/BleObserverController.d.ts +11 -3
- package/build/impl/controllers/BleObserverController.js +25 -2
- package/build/impl/controllers/BleObserverController.js.map +1 -1
- package/package.json +3 -4
- package/src/__tests__/AbstractPackageTest.ts +2 -0
- package/src/__tests__/impl/BleGattController.test.ts +13 -0
- package/src/__tests__/impl/BleObserverController.test.ts +111 -6
- package/src/__tests__/impl/LslStreamInlet.test.ts +45 -86
- package/src/impl/LslStreamInlet.ts +38 -218
- package/src/impl/clocks/WindowedClockRegressor.ts +1 -1
- package/src/impl/controllers/BleGattController.ts +12 -2
- 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.
|
|
75
|
+
await this.startPulling()
|
|
76
|
+
const inletHandle = this.inletHandle
|
|
105
77
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
115
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
351
|
-
|
|
298
|
+
protected static async passesEachPulledSampleToOnData() {
|
|
299
|
+
await this.runChunkSizeOne()
|
|
352
300
|
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
361
|
-
FakeLiblsl.
|
|
362
|
-
|
|
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
|
|
36
|
-
private
|
|
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
|
|
299
|
-
this.handleLslErrorIfPresent()
|
|
128
|
+
const pulled = this.pullMethod()
|
|
300
129
|
|
|
301
|
-
if (
|
|
302
|
-
this.onData(samples, timestamps)
|
|
130
|
+
if (pulled) {
|
|
131
|
+
this.onData(pulled.samples, pulled.timestamps)
|
|
303
132
|
}
|
|
304
133
|
}
|
|
305
134
|
|
|
306
|
-
private
|
|
307
|
-
|
|
308
|
-
|
|
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.
|
|
338
|
-
this.freeNativePointers()
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
private doDestroyInlet() {
|
|
342
|
-
this.lsl.destroyInlet({ inletHandle: this.inletHandle })
|
|
176
|
+
this.destroyInletIfCreated()
|
|
343
177
|
}
|
|
344
178
|
|
|
345
|
-
private
|
|
346
|
-
this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
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
|
|