@hangtime/grip-connect 0.12.0 → 0.13.1
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/README.md +20 -9
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interfaces/command.interface.d.ts +110 -21
- package/dist/cjs/interfaces/command.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/aurora.interface.d.ts +17 -0
- package/dist/cjs/interfaces/device/aurora.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/device/{kilterboard.interface.js → aurora.interface.js} +1 -1
- package/dist/cjs/interfaces/device/aurora.interface.js.map +1 -0
- package/dist/cjs/interfaces/device/cts500.interface.d.ts +96 -0
- package/dist/cjs/interfaces/device/cts500.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/device/cts500.interface.js +3 -0
- package/dist/cjs/interfaces/device/cts500.interface.js.map +1 -0
- package/dist/cjs/interfaces/device/forceboard.interface.d.ts +2 -2
- package/dist/cjs/interfaces/device/forceboard.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/motherboard.interface.d.ts +1 -1
- package/dist/cjs/interfaces/device/pb-700bt.interface.d.ts +53 -0
- package/dist/cjs/interfaces/device/pb-700bt.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/device/pb-700bt.interface.js +3 -0
- package/dist/cjs/interfaces/device/pb-700bt.interface.js.map +1 -0
- package/dist/cjs/interfaces/device/progressor.interface.d.ts +2 -2
- package/dist/cjs/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/index.d.ts +4 -1
- package/dist/cjs/interfaces/index.d.ts.map +1 -1
- package/dist/cjs/interfaces/nordic.interface.d.ts +47 -0
- package/dist/cjs/interfaces/nordic.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/nordic.interface.js +3 -0
- package/dist/cjs/interfaces/nordic.interface.js.map +1 -0
- package/dist/cjs/models/device/{kilterboard.model.d.ts → aurora.model.d.ts} +82 -40
- package/dist/cjs/models/device/aurora.model.d.ts.map +1 -0
- package/dist/cjs/models/device/aurora.model.js +407 -0
- package/dist/cjs/models/device/aurora.model.js.map +1 -0
- package/dist/cjs/models/device/climbro.model.js +1 -1
- package/dist/cjs/models/device/climbro.model.js.map +1 -1
- package/dist/cjs/models/device/cts500.model.d.ts +173 -0
- package/dist/cjs/models/device/cts500.model.d.ts.map +1 -0
- package/dist/cjs/models/device/cts500.model.js +596 -0
- package/dist/cjs/models/device/cts500.model.js.map +1 -0
- package/dist/cjs/models/device/forceboard.model.d.ts +2 -2
- package/dist/cjs/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/forceboard.model.js +9 -16
- package/dist/cjs/models/device/forceboard.model.js.map +1 -1
- package/dist/cjs/models/device/motherboard.model.d.ts +4 -1
- package/dist/cjs/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/motherboard.model.js +26 -10
- package/dist/cjs/models/device/motherboard.model.js.map +1 -1
- package/dist/cjs/models/device/pb-700bt.model.d.ts +2 -1
- package/dist/cjs/models/device/pb-700bt.model.d.ts.map +1 -1
- package/dist/cjs/models/device/pb-700bt.model.js.map +1 -1
- package/dist/cjs/models/device/progressor.model.d.ts +2 -2
- package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
- package/dist/cjs/models/device/progressor.model.js +4 -20
- package/dist/cjs/models/device/progressor.model.js.map +1 -1
- package/dist/cjs/models/device/wh-c06.model.d.ts +2 -0
- package/dist/cjs/models/device/wh-c06.model.d.ts.map +1 -1
- package/dist/cjs/models/device/wh-c06.model.js +45 -34
- package/dist/cjs/models/device/wh-c06.model.js.map +1 -1
- package/dist/cjs/models/device.model.d.ts +25 -5
- package/dist/cjs/models/device.model.d.ts.map +1 -1
- package/dist/cjs/models/device.model.js +94 -24
- package/dist/cjs/models/device.model.js.map +1 -1
- package/dist/cjs/models/index.d.ts +3 -1
- package/dist/cjs/models/index.d.ts.map +1 -1
- package/dist/cjs/models/index.js +8 -4
- package/dist/cjs/models/index.js.map +1 -1
- package/dist/cjs/models/nordic.model.d.ts +128 -0
- package/dist/cjs/models/nordic.model.d.ts.map +1 -0
- package/dist/cjs/models/nordic.model.js +405 -0
- package/dist/cjs/models/nordic.model.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/command.interface.d.ts +110 -21
- package/dist/interfaces/command.interface.d.ts.map +1 -1
- package/dist/interfaces/device/aurora.interface.d.ts +17 -0
- package/dist/interfaces/device/aurora.interface.d.ts.map +1 -0
- package/dist/interfaces/device/aurora.interface.js +2 -0
- package/dist/interfaces/device/aurora.interface.js.map +1 -0
- package/dist/interfaces/device/cts500.interface.d.ts +96 -0
- package/dist/interfaces/device/cts500.interface.d.ts.map +1 -0
- package/dist/interfaces/device/cts500.interface.js +2 -0
- package/dist/interfaces/device/cts500.interface.js.map +1 -0
- package/dist/interfaces/device/forceboard.interface.d.ts +2 -2
- package/dist/interfaces/device/forceboard.interface.d.ts.map +1 -1
- package/dist/interfaces/device/motherboard.interface.d.ts +1 -1
- package/dist/interfaces/device/pb-700bt.interface.d.ts +53 -0
- package/dist/interfaces/device/pb-700bt.interface.d.ts.map +1 -0
- package/dist/interfaces/device/pb-700bt.interface.js +2 -0
- package/dist/interfaces/device/pb-700bt.interface.js.map +1 -0
- package/dist/interfaces/device/progressor.interface.d.ts +2 -2
- package/dist/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/interfaces/index.d.ts +4 -1
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/nordic.interface.d.ts +47 -0
- package/dist/interfaces/nordic.interface.d.ts.map +1 -0
- package/dist/interfaces/nordic.interface.js +2 -0
- package/dist/interfaces/nordic.interface.js.map +1 -0
- package/dist/models/device/{kilterboard.model.d.ts → aurora.model.d.ts} +82 -40
- package/dist/models/device/aurora.model.d.ts.map +1 -0
- package/dist/models/device/aurora.model.js +401 -0
- package/dist/models/device/aurora.model.js.map +1 -0
- package/dist/models/device/climbro.model.js +1 -1
- package/dist/models/device/climbro.model.js.map +1 -1
- package/dist/models/device/cts500.model.d.ts +173 -0
- package/dist/models/device/cts500.model.d.ts.map +1 -0
- package/dist/models/device/cts500.model.js +592 -0
- package/dist/models/device/cts500.model.js.map +1 -0
- package/dist/models/device/forceboard.model.d.ts +2 -2
- package/dist/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/models/device/forceboard.model.js +9 -16
- package/dist/models/device/forceboard.model.js.map +1 -1
- package/dist/models/device/motherboard.model.d.ts +4 -1
- package/dist/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/models/device/motherboard.model.js +26 -10
- package/dist/models/device/motherboard.model.js.map +1 -1
- package/dist/models/device/pb-700bt.model.d.ts +2 -1
- package/dist/models/device/pb-700bt.model.d.ts.map +1 -1
- package/dist/models/device/pb-700bt.model.js.map +1 -1
- package/dist/models/device/progressor.model.d.ts +2 -2
- package/dist/models/device/progressor.model.d.ts.map +1 -1
- package/dist/models/device/progressor.model.js +4 -20
- package/dist/models/device/progressor.model.js.map +1 -1
- package/dist/models/device/wh-c06.model.d.ts +2 -0
- package/dist/models/device/wh-c06.model.d.ts.map +1 -1
- package/dist/models/device/wh-c06.model.js +44 -34
- package/dist/models/device/wh-c06.model.js.map +1 -1
- package/dist/models/device.model.d.ts +25 -5
- package/dist/models/device.model.d.ts.map +1 -1
- package/dist/models/device.model.js +93 -24
- package/dist/models/device.model.js.map +1 -1
- package/dist/models/index.d.ts +3 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +3 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/nordic.model.d.ts +128 -0
- package/dist/models/nordic.model.d.ts.map +1 -0
- package/dist/models/nordic.model.js +393 -0
- package/dist/models/nordic.model.js.map +1 -0
- package/package.json +47 -43
- package/src/index.ts +6 -3
- package/src/interfaces/command.interface.ts +131 -21
- package/src/interfaces/device/aurora.interface.ts +18 -0
- package/src/interfaces/device/cts500.interface.ts +113 -0
- package/src/interfaces/device/forceboard.interface.ts +2 -2
- package/src/interfaces/device/motherboard.interface.ts +1 -1
- package/src/interfaces/device/pb-700bt.interface.ts +61 -0
- package/src/interfaces/device/progressor.interface.ts +2 -2
- package/src/interfaces/index.ts +8 -2
- package/src/interfaces/nordic.interface.ts +47 -0
- package/src/models/device/aurora.model.ts +497 -0
- package/src/models/device/climbro.model.ts +1 -1
- package/src/models/device/cts500.model.ts +709 -0
- package/src/models/device/forceboard.model.ts +9 -16
- package/src/models/device/motherboard.model.ts +51 -9
- package/src/models/device/pb-700bt.model.ts +2 -1
- package/src/models/device/progressor.model.ts +4 -20
- package/src/models/device/wh-c06.model.ts +54 -42
- package/src/models/device.model.ts +104 -24
- package/src/models/index.ts +5 -1
- package/src/models/nordic.model.ts +468 -0
- package/dist/cjs/interfaces/device/kilterboard.interface.d.ts +0 -17
- package/dist/cjs/interfaces/device/kilterboard.interface.d.ts.map +0 -1
- package/dist/cjs/interfaces/device/kilterboard.interface.js.map +0 -1
- package/dist/cjs/models/device/kilterboard.model.d.ts.map +0 -1
- package/dist/cjs/models/device/kilterboard.model.js +0 -327
- package/dist/cjs/models/device/kilterboard.model.js.map +0 -1
- package/dist/interfaces/device/kilterboard.interface.d.ts +0 -17
- package/dist/interfaces/device/kilterboard.interface.d.ts.map +0 -1
- package/dist/interfaces/device/kilterboard.interface.js +0 -2
- package/dist/interfaces/device/kilterboard.interface.js.map +0 -1
- package/dist/models/device/kilterboard.model.d.ts.map +0 -1
- package/dist/models/device/kilterboard.model.js +0 -323
- package/dist/models/device/kilterboard.model.js.map +0 -1
- package/dist/tsconfig.cjs.tsbuildinfo +0 -1
- package/src/interfaces/device/kilterboard.interface.ts +0 -12
- package/src/models/device/kilterboard.model.ts +0 -347
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NordicDfuDevice, createNordicDfuService } from "../nordic.model.js"
|
|
2
2
|
import type { IForceBoard } from "../../interfaces/device/forceboard.interface.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Represents a PitchSix Force Board device.
|
|
6
6
|
* {@link https://pitchsix.com}
|
|
7
7
|
*/
|
|
8
|
-
export class ForceBoard extends
|
|
8
|
+
export class ForceBoard extends NordicDfuDevice implements IForceBoard {
|
|
9
9
|
protected override streamUnit = "lbs" as const
|
|
10
10
|
|
|
11
11
|
constructor() {
|
|
@@ -46,18 +46,7 @@ export class ForceBoard extends Device implements IForceBoard {
|
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
48
|
},
|
|
49
|
-
|
|
50
|
-
name: "Nordic Device Firmware Update (DFU) Service",
|
|
51
|
-
id: "dfu",
|
|
52
|
-
uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
|
|
53
|
-
characteristics: [
|
|
54
|
-
{
|
|
55
|
-
name: "Buttonless DFU",
|
|
56
|
-
id: "dfu",
|
|
57
|
-
uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
49
|
+
createNordicDfuService(),
|
|
61
50
|
{
|
|
62
51
|
name: "",
|
|
63
52
|
id: "",
|
|
@@ -83,7 +72,7 @@ export class ForceBoard extends Device implements IForceBoard {
|
|
|
83
72
|
],
|
|
84
73
|
},
|
|
85
74
|
{
|
|
86
|
-
name: "Temperature
|
|
75
|
+
name: "Temperature Service",
|
|
87
76
|
id: "temperature",
|
|
88
77
|
uuid: "3a90328c-c266-4c76-b05a-6af6104a0b13",
|
|
89
78
|
characteristics: [
|
|
@@ -193,7 +182,7 @@ export class ForceBoard extends Device implements IForceBoard {
|
|
|
193
182
|
this.updateTimestamp()
|
|
194
183
|
if (value.buffer) {
|
|
195
184
|
const receivedTime: number = Date.now()
|
|
196
|
-
const dataArray = new Uint8Array(value.buffer)
|
|
185
|
+
const dataArray = new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
|
|
197
186
|
|
|
198
187
|
const numSamples = (dataArray[0] << 8) | dataArray[1]
|
|
199
188
|
this.currentSamplesPerPacket = numSamples
|
|
@@ -265,7 +254,11 @@ export class ForceBoard extends Device implements IForceBoard {
|
|
|
265
254
|
*/
|
|
266
255
|
stream = async (duration = 0): Promise<void> => {
|
|
267
256
|
this.resetPacketTracking()
|
|
257
|
+
this.resetSessionData()
|
|
268
258
|
await this.write("weight", "tx", this.commands.START_WEIGHT_MEAS, duration)
|
|
259
|
+
if (duration !== 0) {
|
|
260
|
+
await this.stop()
|
|
261
|
+
}
|
|
269
262
|
}
|
|
270
263
|
|
|
271
264
|
/**
|
|
@@ -40,10 +40,13 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
40
40
|
|
|
41
41
|
/** Per-zone peak and running sum for left/center/right (used for distribution stats). */
|
|
42
42
|
private leftPeak = Number.NEGATIVE_INFINITY
|
|
43
|
+
private leftMin = Number.POSITIVE_INFINITY
|
|
43
44
|
private leftSum = 0
|
|
44
45
|
private centerPeak = Number.NEGATIVE_INFINITY
|
|
46
|
+
private centerMin = Number.POSITIVE_INFINITY
|
|
45
47
|
private centerSum = 0
|
|
46
48
|
private rightPeak = Number.NEGATIVE_INFINITY
|
|
49
|
+
private rightMin = Number.POSITIVE_INFINITY
|
|
47
50
|
private rightSum = 0
|
|
48
51
|
|
|
49
52
|
constructor() {
|
|
@@ -295,12 +298,15 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
295
298
|
this.dataPointCount++
|
|
296
299
|
this.mean = this.sum / this.dataPointCount
|
|
297
300
|
|
|
298
|
-
// Per-zone peak and sum for distribution
|
|
301
|
+
// Per-zone peak, min and sum for distribution
|
|
299
302
|
this.leftPeak = Math.max(this.leftPeak, leftClamped)
|
|
303
|
+
this.leftMin = Math.min(this.leftMin, leftClamped)
|
|
300
304
|
this.leftSum += leftClamped
|
|
301
305
|
this.centerPeak = Math.max(this.centerPeak, centerClamped)
|
|
306
|
+
this.centerMin = Math.min(this.centerMin, centerClamped)
|
|
302
307
|
this.centerSum += centerClamped
|
|
303
308
|
this.rightPeak = Math.max(this.rightPeak, rightClamped)
|
|
309
|
+
this.rightMin = Math.min(this.rightMin, rightClamped)
|
|
304
310
|
this.rightSum += rightClamped
|
|
305
311
|
|
|
306
312
|
// Add data to downloadable Array (distribution = per-zone measurements)
|
|
@@ -310,13 +316,24 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
310
316
|
battRaw: packet.battRaw,
|
|
311
317
|
sampleIndex: packet.sampleIndex,
|
|
312
318
|
distribution: {
|
|
313
|
-
left: this.buildZoneMeasurement(
|
|
319
|
+
left: this.buildZoneMeasurement(
|
|
320
|
+
leftClamped,
|
|
321
|
+
this.leftPeak,
|
|
322
|
+
this.leftSum / this.dataPointCount,
|
|
323
|
+
this.leftMin,
|
|
324
|
+
),
|
|
314
325
|
center: this.buildZoneMeasurement(
|
|
315
326
|
centerClamped,
|
|
316
327
|
this.centerPeak,
|
|
317
328
|
this.centerSum / this.dataPointCount,
|
|
329
|
+
this.centerMin,
|
|
330
|
+
),
|
|
331
|
+
right: this.buildZoneMeasurement(
|
|
332
|
+
rightClamped,
|
|
333
|
+
this.rightPeak,
|
|
334
|
+
this.rightSum / this.dataPointCount,
|
|
335
|
+
this.rightMin,
|
|
318
336
|
),
|
|
319
|
-
right: this.buildZoneMeasurement(rightClamped, this.rightPeak, this.rightSum / this.dataPointCount),
|
|
320
337
|
},
|
|
321
338
|
}),
|
|
322
339
|
)
|
|
@@ -324,12 +341,27 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
324
341
|
// Check if device is being used
|
|
325
342
|
this.activityCheck(center)
|
|
326
343
|
|
|
327
|
-
// Notify with weight data (distribution zones have proper peak/mean per zone)
|
|
344
|
+
// Notify with weight data (distribution zones have proper peak/mean/min per zone)
|
|
328
345
|
this.notifyCallback(
|
|
329
346
|
this.buildForceMeasurement(totalCurrent, {
|
|
330
|
-
left: this.buildZoneMeasurement(
|
|
331
|
-
|
|
332
|
-
|
|
347
|
+
left: this.buildZoneMeasurement(
|
|
348
|
+
leftClamped,
|
|
349
|
+
this.leftPeak,
|
|
350
|
+
this.leftSum / this.dataPointCount,
|
|
351
|
+
this.leftMin,
|
|
352
|
+
),
|
|
353
|
+
center: this.buildZoneMeasurement(
|
|
354
|
+
centerClamped,
|
|
355
|
+
this.centerPeak,
|
|
356
|
+
this.centerSum / this.dataPointCount,
|
|
357
|
+
this.centerMin,
|
|
358
|
+
),
|
|
359
|
+
right: this.buildZoneMeasurement(
|
|
360
|
+
rightClamped,
|
|
361
|
+
this.rightPeak,
|
|
362
|
+
this.rightSum / this.dataPointCount,
|
|
363
|
+
this.rightMin,
|
|
364
|
+
),
|
|
333
365
|
}),
|
|
334
366
|
)
|
|
335
367
|
} else if (this.writeLast === this.commands.GET_CALIBRATION) {
|
|
@@ -359,7 +391,7 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
359
391
|
/**
|
|
360
392
|
* Sets the LED color based on a single color option. Defaults to turning the LEDs off if no configuration is provided.
|
|
361
393
|
* @param {"green" | "red" | "orange"} [config] - Optional color or array of climb placements for the LEDs. Ignored if placements are provided.
|
|
362
|
-
* @returns {Promise<number[] | undefined>} A promise that resolves with the payload array for
|
|
394
|
+
* @returns {Promise<number[] | undefined>} A promise that resolves with the payload array for Aurora-compatible LED settings, or `undefined` if no action was taken or for the Motherboard.
|
|
363
395
|
*/
|
|
364
396
|
led = async (config?: "green" | "red" | "orange"): Promise<number[] | undefined> => {
|
|
365
397
|
if (this.isConnected()) {
|
|
@@ -413,7 +445,17 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
413
445
|
*/
|
|
414
446
|
stream = async (duration = 0): Promise<void> => {
|
|
415
447
|
this.resetPacketTracking()
|
|
416
|
-
this.
|
|
448
|
+
this.resetSessionData()
|
|
449
|
+
// Reset per-zone session stats for the distribution measurements
|
|
450
|
+
this.leftPeak = Number.NEGATIVE_INFINITY
|
|
451
|
+
this.leftMin = Number.POSITIVE_INFINITY
|
|
452
|
+
this.leftSum = 0
|
|
453
|
+
this.centerPeak = Number.NEGATIVE_INFINITY
|
|
454
|
+
this.centerMin = Number.POSITIVE_INFINITY
|
|
455
|
+
this.centerSum = 0
|
|
456
|
+
this.rightPeak = Number.NEGATIVE_INFINITY
|
|
457
|
+
this.rightMin = Number.POSITIVE_INFINITY
|
|
458
|
+
this.rightSum = 0
|
|
417
459
|
// Read calibration data if not already available
|
|
418
460
|
if (!this.calibrationData[0].length) {
|
|
419
461
|
await this.calibration()
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Device } from "../device.model.js"
|
|
2
|
+
import type { IPB700BT } from "../../interfaces/device/pb-700bt.interface.js"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Represents a NSD PB-700BT device.
|
|
5
6
|
* {@link https://www.nsd.com.tw/}
|
|
6
7
|
*/
|
|
7
|
-
export class PB700BT extends Device {
|
|
8
|
+
export class PB700BT extends Device implements IPB700BT {
|
|
8
9
|
constructor() {
|
|
9
10
|
super({
|
|
10
11
|
filters: [{ name: "NSD Workout" }],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NordicDfuDevice, createNordicDfuService } from "../nordic.model.js"
|
|
2
2
|
import type { IProgressor } from "../../interfaces/device/progressor.interface.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -113,7 +113,7 @@ function parseCalibrationTableRecordPayload(payload: Uint8Array, index: number):
|
|
|
113
113
|
return `${String(index).padStart(2, "0")}: ${hex} | raw ${lowerRaw.toLocaleString()}..${upperRaw.toLocaleString()} | slope ${formatCalibrationFloat(slope)} | intercept ${formatCalibrationFloat(intercept)}`
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export class Progressor extends
|
|
116
|
+
export class Progressor extends NordicDfuDevice implements IProgressor {
|
|
117
117
|
/** Device timestamps (µs) of recent samples (samples in last 1s device time). */
|
|
118
118
|
private recentSampleTimestamps: number[] = []
|
|
119
119
|
/** 1-based index for multi-packet calibration-table export responses. */
|
|
@@ -140,18 +140,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
140
140
|
},
|
|
141
141
|
],
|
|
142
142
|
},
|
|
143
|
-
|
|
144
|
-
name: "Nordic Device Firmware Update (DFU) Service",
|
|
145
|
-
id: "dfu",
|
|
146
|
-
uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
|
|
147
|
-
characteristics: [
|
|
148
|
-
{
|
|
149
|
-
name: "Buttonless DFU",
|
|
150
|
-
id: "dfu",
|
|
151
|
-
uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
},
|
|
143
|
+
createNordicDfuService(),
|
|
155
144
|
],
|
|
156
145
|
// Tindeq API: opcode = single byte (ASCII char code = decimal 100–114 v2 firmware: 115-118)
|
|
157
146
|
commands: {
|
|
@@ -467,12 +456,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
467
456
|
*/
|
|
468
457
|
stream = async (duration = 0): Promise<void> => {
|
|
469
458
|
// Reset download packets and session stats for fresh measurement
|
|
470
|
-
this.
|
|
471
|
-
this.peak = Number.NEGATIVE_INFINITY
|
|
472
|
-
this.mean = 0
|
|
473
|
-
this.sum = 0
|
|
474
|
-
this.dataPointCount = 0
|
|
475
|
-
this.min = Number.POSITIVE_INFINITY
|
|
459
|
+
this.resetSessionData()
|
|
476
460
|
this.resetPacketTracking()
|
|
477
461
|
this.recentSampleTimestamps = []
|
|
478
462
|
// Start streaming data
|
|
@@ -41,6 +41,45 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
41
41
|
*/
|
|
42
42
|
private readonly advertisementTimeoutTime: number = 10
|
|
43
43
|
|
|
44
|
+
private readonly onAdvertisementReceived = (event: BluetoothAdvertisingEvent): void => {
|
|
45
|
+
const data = event.manufacturerData.get(WHC06.manufacturerId)
|
|
46
|
+
if (data) {
|
|
47
|
+
this.currentSamplesPerPacket = 1
|
|
48
|
+
this.recordPacketReceived()
|
|
49
|
+
const weight = (data.getUint8(WHC06.weightOffset) << 8) | data.getUint8(WHC06.weightOffset + 1)
|
|
50
|
+
// const stable = (data.getUint8(STABLE_OFFSET) & 0xf0) >> 4
|
|
51
|
+
// const unit = data.getUint8(STABLE_OFFSET) & 0x0f
|
|
52
|
+
const receivedTime: number = Date.now()
|
|
53
|
+
const receivedData = weight / 100
|
|
54
|
+
|
|
55
|
+
const numericData = receivedData - this.applyTare(receivedData)
|
|
56
|
+
const currentMassTotal = Math.max(-1000, numericData)
|
|
57
|
+
|
|
58
|
+
// Update session stats before building packet
|
|
59
|
+
this.peak = Math.max(this.peak, numericData)
|
|
60
|
+
this.min = Math.min(this.min, Math.max(-1000, numericData))
|
|
61
|
+
this.sum += currentMassTotal
|
|
62
|
+
this.dataPointCount++
|
|
63
|
+
this.mean = this.sum / this.dataPointCount
|
|
64
|
+
|
|
65
|
+
// Add data to downloadable Array
|
|
66
|
+
this.downloadPackets.push(
|
|
67
|
+
this.buildDownloadPacket(currentMassTotal, [numericData], {
|
|
68
|
+
timestamp: receivedTime,
|
|
69
|
+
sampleIndex: this.dataPointCount,
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// Check if device is being used
|
|
74
|
+
this.activityCheck(numericData)
|
|
75
|
+
|
|
76
|
+
// Notify with weight data
|
|
77
|
+
this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
|
|
78
|
+
}
|
|
79
|
+
// Reset "still advertising" counter
|
|
80
|
+
this.resetAdvertisementTimeout()
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
// /**
|
|
45
84
|
// * Offset for the byte location in the manufacturer data to determine weight stability.
|
|
46
85
|
// * @type {number}
|
|
@@ -93,47 +132,7 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
93
132
|
// Update timestamp
|
|
94
133
|
this.updateTimestamp()
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
onSuccess()
|
|
98
|
-
|
|
99
|
-
this.bluetooth.addEventListener("advertisementreceived", (event) => {
|
|
100
|
-
const data = event.manufacturerData.get(WHC06.manufacturerId)
|
|
101
|
-
if (data) {
|
|
102
|
-
this.currentSamplesPerPacket = 1
|
|
103
|
-
this.recordPacketReceived()
|
|
104
|
-
const weight = (data.getUint8(WHC06.weightOffset) << 8) | data.getUint8(WHC06.weightOffset + 1)
|
|
105
|
-
// const stable = (data.getUint8(STABLE_OFFSET) & 0xf0) >> 4
|
|
106
|
-
// const unit = data.getUint8(STABLE_OFFSET) & 0x0f
|
|
107
|
-
const receivedTime: number = Date.now()
|
|
108
|
-
const receivedData = weight / 100
|
|
109
|
-
|
|
110
|
-
const numericData = receivedData - this.applyTare(receivedData) * -1
|
|
111
|
-
const currentMassTotal = Math.max(-1000, numericData)
|
|
112
|
-
|
|
113
|
-
// Update session stats before building packet
|
|
114
|
-
this.peak = Math.max(this.peak, numericData)
|
|
115
|
-
this.min = Math.min(this.min, Math.max(-1000, numericData))
|
|
116
|
-
this.sum += currentMassTotal
|
|
117
|
-
this.dataPointCount++
|
|
118
|
-
this.mean = this.sum / this.dataPointCount
|
|
119
|
-
|
|
120
|
-
// Add data to downloadable Array
|
|
121
|
-
this.downloadPackets.push(
|
|
122
|
-
this.buildDownloadPacket(currentMassTotal, [numericData], {
|
|
123
|
-
timestamp: receivedTime,
|
|
124
|
-
sampleIndex: this.dataPointCount,
|
|
125
|
-
}),
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
// Check if device is being used
|
|
129
|
-
this.activityCheck(numericData)
|
|
130
|
-
|
|
131
|
-
// Notify with weight data
|
|
132
|
-
this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
|
|
133
|
-
}
|
|
134
|
-
// Reset "still advertising" counter
|
|
135
|
-
this.resetAdvertisementTimeout()
|
|
136
|
-
})
|
|
135
|
+
this.bluetooth.addEventListener("advertisementreceived", this.onAdvertisementReceived)
|
|
137
136
|
|
|
138
137
|
// When the companyIdentifier is provided we want to get manufacturerData using watchAdvertisements.
|
|
139
138
|
if (optionalManufacturerData.length) {
|
|
@@ -148,7 +147,11 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
148
147
|
)
|
|
149
148
|
}
|
|
150
149
|
}
|
|
150
|
+
|
|
151
|
+
// Device has no services / characteristics, so success means advertisement watching is ready.
|
|
152
|
+
onSuccess()
|
|
151
153
|
} catch (error) {
|
|
154
|
+
this.disconnect()
|
|
152
155
|
onError(error as Error)
|
|
153
156
|
}
|
|
154
157
|
}
|
|
@@ -167,7 +170,7 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
167
170
|
*/
|
|
168
171
|
private resetAdvertisementTimeout = (): void => {
|
|
169
172
|
// Clear the previous timeout
|
|
170
|
-
if (this.advertisementTimeout) {
|
|
173
|
+
if (this.advertisementTimeout !== null) {
|
|
171
174
|
clearTimeout(this.advertisementTimeout)
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -184,4 +187,13 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
184
187
|
this.onDisconnected(disconnectedEvent)
|
|
185
188
|
}, this.advertisementTimeoutTime * 1000) // 10 seconds
|
|
186
189
|
}
|
|
190
|
+
|
|
191
|
+
protected override onDisconnectCleanup(): void {
|
|
192
|
+
if (this.advertisementTimeout !== null) {
|
|
193
|
+
clearTimeout(this.advertisementTimeout)
|
|
194
|
+
this.advertisementTimeout = null
|
|
195
|
+
}
|
|
196
|
+
this.bluetooth?.removeEventListener("advertisementreceived", this.onAdvertisementReceived)
|
|
197
|
+
delete this.bluetooth
|
|
198
|
+
}
|
|
187
199
|
}
|
|
@@ -213,6 +213,14 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
213
213
|
*/
|
|
214
214
|
private tareActive = false
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The characteristic ID that accepts notifications.
|
|
218
|
+
* Switching to "buttonless" allows to enter DFU mode and update the firmware.
|
|
219
|
+
*
|
|
220
|
+
* @type {"rx" | "buttonless" | "control"}
|
|
221
|
+
*/
|
|
222
|
+
protected notifyCharacteristicId: "rx" | "buttonless" = "rx"
|
|
223
|
+
|
|
216
224
|
/**
|
|
217
225
|
* Timestamp when the tare calibration process started.
|
|
218
226
|
* @type {number | null}
|
|
@@ -276,6 +284,8 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
276
284
|
*/
|
|
277
285
|
private notificationListeners = new Map<string, EventListener>()
|
|
278
286
|
|
|
287
|
+
private activeCheckPending: { target: boolean; timeout: ReturnType<typeof setTimeout> } | undefined = undefined
|
|
288
|
+
|
|
279
289
|
constructor(device: Partial<IDevice>) {
|
|
280
290
|
super(device)
|
|
281
291
|
|
|
@@ -304,19 +314,20 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
304
314
|
/**
|
|
305
315
|
* Builds a ForceMeasurement for a single zone (e.g. left/center/right).
|
|
306
316
|
* With one argument, current/peak/mean are all set to that value.
|
|
307
|
-
* With three arguments, uses the given current, peak, and
|
|
317
|
+
* With three or four arguments, uses the given current, peak, mean, and min for the zone.
|
|
308
318
|
* @param valueOrCurrent - Force value, or current force for this zone
|
|
309
319
|
* @param peak - Optional peak for this zone (required if mean is provided)
|
|
310
320
|
* @param mean - Optional mean for this zone
|
|
321
|
+
* @param min - Optional session minimum for this zone
|
|
311
322
|
* @returns ForceMeasurement (no nested distribution)
|
|
312
323
|
* @protected
|
|
313
324
|
*/
|
|
314
|
-
protected buildZoneMeasurement(valueOrCurrent: number, peak?: number, mean?: number): ForceMeasurement {
|
|
325
|
+
protected buildZoneMeasurement(valueOrCurrent: number, peak?: number, mean?: number, min?: number): ForceMeasurement {
|
|
315
326
|
const useFullStats = peak !== undefined && mean !== undefined
|
|
316
327
|
const current = valueOrCurrent
|
|
317
328
|
const zonePeak = useFullStats ? (peak === 0 && current < 0 ? current : peak) : valueOrCurrent
|
|
318
329
|
const zoneMean = useFullStats ? mean : valueOrCurrent
|
|
319
|
-
const zoneMin = useFullStats ? Math.min(zonePeak, current) : current
|
|
330
|
+
const zoneMin = useFullStats ? (min ?? Math.min(zonePeak, current)) : current
|
|
320
331
|
return {
|
|
321
332
|
unit: this.unit,
|
|
322
333
|
timestamp: Date.now(),
|
|
@@ -493,6 +504,21 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
493
504
|
}
|
|
494
505
|
}
|
|
495
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Resets per-session measurement state: the download buffer and force statistics.
|
|
509
|
+
* Device models call this at the start of a new streaming session so stats from a
|
|
510
|
+
* previous session do not leak into the next one.
|
|
511
|
+
* @protected
|
|
512
|
+
*/
|
|
513
|
+
protected resetSessionData(): void {
|
|
514
|
+
this.downloadPackets.length = 0
|
|
515
|
+
this.peak = Number.NEGATIVE_INFINITY
|
|
516
|
+
this.min = Number.POSITIVE_INFINITY
|
|
517
|
+
this.mean = 0
|
|
518
|
+
this.sum = 0
|
|
519
|
+
this.dataPointCount = 0
|
|
520
|
+
}
|
|
521
|
+
|
|
496
522
|
/**
|
|
497
523
|
* Checks if a dynamic value is active based on a threshold and duration.
|
|
498
524
|
*
|
|
@@ -501,20 +527,37 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
501
527
|
* the previous state, the callback function is called with the updated activity status.
|
|
502
528
|
*
|
|
503
529
|
* @param {number} input - The dynamic value to check for activity status.
|
|
504
|
-
* @returns {Promise<void>} A promise that resolves once the activity check is complete.
|
|
505
530
|
*
|
|
506
531
|
* @example
|
|
507
|
-
*
|
|
532
|
+
* device.activityCheck(5.0);
|
|
508
533
|
*/
|
|
509
|
-
protected activityCheck =
|
|
510
|
-
const startValue = input
|
|
534
|
+
protected activityCheck = (input: number): void => {
|
|
511
535
|
const { threshold, duration } = this.activeConfig
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
536
|
+
const activeNow = input > threshold
|
|
537
|
+
|
|
538
|
+
if (activeNow === this.isActive) {
|
|
539
|
+
if (this.activeCheckPending) {
|
|
540
|
+
clearTimeout(this.activeCheckPending.timeout)
|
|
541
|
+
this.activeCheckPending = undefined
|
|
542
|
+
}
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this.activeCheckPending?.target === activeNow) {
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (this.activeCheckPending) {
|
|
551
|
+
clearTimeout(this.activeCheckPending.timeout)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
this.activeCheckPending = {
|
|
555
|
+
target: activeNow,
|
|
556
|
+
timeout: setTimeout(() => {
|
|
557
|
+
this.isActive = activeNow
|
|
558
|
+
this.activeCallback(activeNow)
|
|
559
|
+
this.activeCheckPending = undefined
|
|
560
|
+
}, duration),
|
|
518
561
|
}
|
|
519
562
|
}
|
|
520
563
|
|
|
@@ -562,10 +605,19 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
562
605
|
// }
|
|
563
606
|
// }
|
|
564
607
|
|
|
565
|
-
this.bluetooth
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
608
|
+
if (!this.bluetooth?.gatt) {
|
|
609
|
+
this.bluetooth = await bluetooth.requestDevice({
|
|
610
|
+
filters: this.filters,
|
|
611
|
+
optionalServices: deviceServices,
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!this.bluetooth) {
|
|
616
|
+
throw new Error("Bluetooth device is not available")
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Hook for when a device has been selected.
|
|
620
|
+
this.onBluetoothDeviceSelected(this.bluetooth)
|
|
569
621
|
|
|
570
622
|
if (!this.bluetooth.gatt) {
|
|
571
623
|
throw new Error("GATT is not available on this device")
|
|
@@ -573,12 +625,15 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
573
625
|
|
|
574
626
|
this.bluetooth.addEventListener("gattserverdisconnected", this.onDisconnectedListener)
|
|
575
627
|
|
|
576
|
-
this.server = await this.bluetooth.gatt.connect()
|
|
628
|
+
this.server = this.bluetooth.gatt.connected ? this.bluetooth.gatt : await this.bluetooth.gatt.connect()
|
|
577
629
|
|
|
578
|
-
if (this.server.connected) {
|
|
579
|
-
|
|
630
|
+
if (!this.server.connected) {
|
|
631
|
+
throw new Error("GATT server did not connect")
|
|
580
632
|
}
|
|
633
|
+
|
|
634
|
+
await this.onConnected(onSuccess)
|
|
581
635
|
} catch (error) {
|
|
636
|
+
this.disconnect()
|
|
582
637
|
onError(error as Error)
|
|
583
638
|
}
|
|
584
639
|
}
|
|
@@ -604,7 +659,7 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
604
659
|
// Remove all notification listeners and stop notifications if possible.
|
|
605
660
|
this.services.forEach((service) => {
|
|
606
661
|
service.characteristics.forEach((char) => {
|
|
607
|
-
if (!char.characteristic || char.id !==
|
|
662
|
+
if (!char.characteristic || char.id !== this.notifyCharacteristicId) return
|
|
608
663
|
|
|
609
664
|
if (isConnected) {
|
|
610
665
|
// Best effort only: avoid unhandled rejections when the device already disconnected.
|
|
@@ -629,6 +684,15 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
629
684
|
this.server = undefined
|
|
630
685
|
this.writeLast = null
|
|
631
686
|
this.isActive = false
|
|
687
|
+
if (this.activeCheckPending) {
|
|
688
|
+
clearTimeout(this.activeCheckPending.timeout)
|
|
689
|
+
this.activeCheckPending = undefined
|
|
690
|
+
}
|
|
691
|
+
this.onDisconnectCleanup()
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
protected onDisconnectCleanup(): void {
|
|
695
|
+
return
|
|
632
696
|
}
|
|
633
697
|
|
|
634
698
|
/**
|
|
@@ -820,6 +884,14 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
820
884
|
throw new Error("Bluetooth not available.")
|
|
821
885
|
}
|
|
822
886
|
|
|
887
|
+
/**
|
|
888
|
+
* Hook for device-specific setup after a Bluetooth device has been selected.
|
|
889
|
+
*/
|
|
890
|
+
protected onBluetoothDeviceSelected(device: BluetoothDevice): void {
|
|
891
|
+
void device
|
|
892
|
+
return
|
|
893
|
+
}
|
|
894
|
+
|
|
823
895
|
/**
|
|
824
896
|
* Handles notifications received from a characteristic.
|
|
825
897
|
* @param {DataView} value - The notification event.
|
|
@@ -921,10 +993,10 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
921
993
|
if (descriptor) {
|
|
922
994
|
// Assign the actual Bluetooth characteristic object to the descriptor so it can be used later
|
|
923
995
|
descriptor.characteristic = matchingCharacteristic
|
|
924
|
-
// Look for
|
|
925
|
-
if (descriptor.id ===
|
|
996
|
+
// Look for our default notify characteristic id that accepts notifications
|
|
997
|
+
if (descriptor.id === this.notifyCharacteristicId) {
|
|
926
998
|
// Start receiving notifications for changes on this characteristic
|
|
927
|
-
matchingCharacteristic.startNotifications()
|
|
999
|
+
await matchingCharacteristic.startNotifications()
|
|
928
1000
|
// Triggered when the characteristic's value changes
|
|
929
1001
|
const listener = (event: Event) => {
|
|
930
1002
|
// Cast the event's target to a BluetoothRemoteGATTCharacteristic to access its properties
|
|
@@ -934,12 +1006,20 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
934
1006
|
this.handleNotifications(target.value)
|
|
935
1007
|
}
|
|
936
1008
|
}
|
|
1009
|
+
const existingListener = this.notificationListeners.get(descriptor.uuid)
|
|
1010
|
+
if (existingListener) {
|
|
1011
|
+
matchingCharacteristic.removeEventListener("characteristicvaluechanged", existingListener)
|
|
1012
|
+
}
|
|
937
1013
|
// Attach the event listener to listen for changes in the characteristic's value
|
|
938
1014
|
matchingCharacteristic.addEventListener("characteristicvaluechanged", listener)
|
|
939
1015
|
// Store the listener so it can be referenced (for later removal)
|
|
940
1016
|
this.notificationListeners.set(descriptor.uuid, listener)
|
|
941
1017
|
}
|
|
942
1018
|
}
|
|
1019
|
+
} else if (matchingService.id === "dfu") {
|
|
1020
|
+
// App mode exposes buttonless only, bootloader exposes control+packet only.
|
|
1021
|
+
delete characteristic.characteristic
|
|
1022
|
+
continue
|
|
943
1023
|
} else {
|
|
944
1024
|
throw new Error(`Characteristic ${characteristic.uuid} not found in service ${service.uuid}`)
|
|
945
1025
|
}
|
package/src/models/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { Climbro } from "./device/climbro.model.js"
|
|
2
2
|
|
|
3
|
+
export { CTS500 } from "./device/cts500.model.js"
|
|
4
|
+
|
|
3
5
|
export { Entralpi } from "./device/entralpi.model.js"
|
|
4
6
|
|
|
5
7
|
export { ForceBoard } from "./device/forceboard.model.js"
|
|
6
8
|
|
|
7
|
-
export {
|
|
9
|
+
export { NordicDfuDevice, createNordicDfuService } from "./nordic.model.js"
|
|
8
10
|
|
|
9
11
|
export { Motherboard } from "./device/motherboard.model.js"
|
|
10
12
|
|
|
@@ -17,3 +19,5 @@ export { Progressor } from "./device/progressor.model.js"
|
|
|
17
19
|
export { SmartBoardPro } from "./device/smartboard-pro.model.js"
|
|
18
20
|
|
|
19
21
|
export { WHC06 } from "./device/wh-c06.model.js"
|
|
22
|
+
|
|
23
|
+
export { AuroraBoard } from "./device/aurora.model.js"
|