@hangtime/grip-connect 0.10.1 → 0.10.2

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 (111) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/index.d.ts +1 -2
  3. package/dist/cjs/index.d.ts.map +1 -1
  4. package/dist/cjs/index.js +1 -3
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/interfaces/callback.interface.d.ts +19 -12
  7. package/dist/cjs/interfaces/callback.interface.d.ts.map +1 -1
  8. package/dist/cjs/interfaces/command.interface.d.ts +10 -6
  9. package/dist/cjs/interfaces/command.interface.d.ts.map +1 -1
  10. package/dist/cjs/interfaces/device/progressor.interface.d.ts +42 -0
  11. package/dist/cjs/interfaces/device/progressor.interface.d.ts.map +1 -1
  12. package/dist/cjs/interfaces/device.interface.d.ts +1 -1
  13. package/dist/cjs/interfaces/download.interface.d.ts +8 -11
  14. package/dist/cjs/interfaces/download.interface.d.ts.map +1 -1
  15. package/dist/cjs/models/device/climbro.model.d.ts.map +1 -1
  16. package/dist/cjs/models/device/climbro.model.js +34 -16
  17. package/dist/cjs/models/device/climbro.model.js.map +1 -1
  18. package/dist/cjs/models/device/entralpi.model.d.ts.map +1 -1
  19. package/dist/cjs/models/device/entralpi.model.js +11 -14
  20. package/dist/cjs/models/device/entralpi.model.js.map +1 -1
  21. package/dist/cjs/models/device/forceboard.model.d.ts.map +1 -1
  22. package/dist/cjs/models/device/forceboard.model.js +12 -17
  23. package/dist/cjs/models/device/forceboard.model.js.map +1 -1
  24. package/dist/cjs/models/device/motherboard.model.d.ts.map +1 -1
  25. package/dist/cjs/models/device/motherboard.model.js +28 -20
  26. package/dist/cjs/models/device/motherboard.model.js.map +1 -1
  27. package/dist/cjs/models/device/pb-700bt.model.d.ts.map +1 -1
  28. package/dist/cjs/models/device/pb-700bt.model.js +11 -14
  29. package/dist/cjs/models/device/pb-700bt.model.js.map +1 -1
  30. package/dist/cjs/models/device/progressor.model.d.ts +47 -5
  31. package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
  32. package/dist/cjs/models/device/progressor.model.js +185 -49
  33. package/dist/cjs/models/device/progressor.model.js.map +1 -1
  34. package/dist/cjs/models/device/smartboard-pro.model.d.ts.map +1 -1
  35. package/dist/cjs/models/device/smartboard-pro.model.js +11 -16
  36. package/dist/cjs/models/device/smartboard-pro.model.js.map +1 -1
  37. package/dist/cjs/models/device/wh-c06.model.d.ts.map +1 -1
  38. package/dist/cjs/models/device/wh-c06.model.js +11 -14
  39. package/dist/cjs/models/device/wh-c06.model.js.map +1 -1
  40. package/dist/cjs/models/device.model.d.ts +64 -3
  41. package/dist/cjs/models/device.model.d.ts.map +1 -1
  42. package/dist/cjs/models/device.model.js +142 -37
  43. package/dist/cjs/models/device.model.js.map +1 -1
  44. package/dist/cjs/utils.d.ts +1 -1
  45. package/dist/cjs/utils.d.ts.map +1 -1
  46. package/dist/cjs/utils.js +29 -6
  47. package/dist/cjs/utils.js.map +1 -1
  48. package/dist/index.d.ts +1 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +0 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/interfaces/callback.interface.d.ts +19 -12
  53. package/dist/interfaces/callback.interface.d.ts.map +1 -1
  54. package/dist/interfaces/command.interface.d.ts +10 -6
  55. package/dist/interfaces/command.interface.d.ts.map +1 -1
  56. package/dist/interfaces/device/progressor.interface.d.ts +42 -0
  57. package/dist/interfaces/device/progressor.interface.d.ts.map +1 -1
  58. package/dist/interfaces/device.interface.d.ts +1 -1
  59. package/dist/interfaces/download.interface.d.ts +8 -11
  60. package/dist/interfaces/download.interface.d.ts.map +1 -1
  61. package/dist/models/device/climbro.model.d.ts.map +1 -1
  62. package/dist/models/device/climbro.model.js +34 -16
  63. package/dist/models/device/climbro.model.js.map +1 -1
  64. package/dist/models/device/entralpi.model.d.ts.map +1 -1
  65. package/dist/models/device/entralpi.model.js +11 -14
  66. package/dist/models/device/entralpi.model.js.map +1 -1
  67. package/dist/models/device/forceboard.model.d.ts.map +1 -1
  68. package/dist/models/device/forceboard.model.js +12 -17
  69. package/dist/models/device/forceboard.model.js.map +1 -1
  70. package/dist/models/device/motherboard.model.d.ts.map +1 -1
  71. package/dist/models/device/motherboard.model.js +28 -20
  72. package/dist/models/device/motherboard.model.js.map +1 -1
  73. package/dist/models/device/pb-700bt.model.d.ts.map +1 -1
  74. package/dist/models/device/pb-700bt.model.js +11 -14
  75. package/dist/models/device/pb-700bt.model.js.map +1 -1
  76. package/dist/models/device/progressor.model.d.ts +47 -5
  77. package/dist/models/device/progressor.model.d.ts.map +1 -1
  78. package/dist/models/device/progressor.model.js +184 -49
  79. package/dist/models/device/progressor.model.js.map +1 -1
  80. package/dist/models/device/smartboard-pro.model.d.ts.map +1 -1
  81. package/dist/models/device/smartboard-pro.model.js +11 -16
  82. package/dist/models/device/smartboard-pro.model.js.map +1 -1
  83. package/dist/models/device/wh-c06.model.d.ts.map +1 -1
  84. package/dist/models/device/wh-c06.model.js +11 -14
  85. package/dist/models/device/wh-c06.model.js.map +1 -1
  86. package/dist/models/device.model.d.ts +64 -3
  87. package/dist/models/device.model.d.ts.map +1 -1
  88. package/dist/models/device.model.js +146 -37
  89. package/dist/models/device.model.js.map +1 -1
  90. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  91. package/dist/utils.d.ts +1 -1
  92. package/dist/utils.d.ts.map +1 -1
  93. package/dist/utils.js +29 -6
  94. package/dist/utils.js.map +1 -1
  95. package/package.json +1 -1
  96. package/src/index.ts +0 -3
  97. package/src/interfaces/callback.interface.ts +21 -12
  98. package/src/interfaces/command.interface.ts +11 -6
  99. package/src/interfaces/device/progressor.interface.ts +50 -0
  100. package/src/interfaces/device.interface.ts +1 -1
  101. package/src/interfaces/download.interface.ts +9 -11
  102. package/src/models/device/climbro.model.ts +38 -20
  103. package/src/models/device/entralpi.model.ts +15 -17
  104. package/src/models/device/forceboard.model.ts +15 -19
  105. package/src/models/device/motherboard.model.ts +38 -26
  106. package/src/models/device/pb-700bt.model.ts +14 -16
  107. package/src/models/device/progressor.model.ts +198 -50
  108. package/src/models/device/smartboard-pro.model.ts +14 -19
  109. package/src/models/device/wh-c06.model.ts +14 -17
  110. package/src/models/device.model.ts +181 -41
  111. package/src/utils.ts +31 -6
@@ -88,6 +88,14 @@ export abstract class Device extends BaseModel implements IDevice {
88
88
  */
89
89
  protected mean: number
90
90
 
91
+ /**
92
+ * Lowest instantaneous force recorded in the session; may be negative.
93
+ * Initialized to Number.POSITIVE_INFINITY so the first sample sets the min.
94
+ * @type {number}
95
+ * @protected
96
+ */
97
+ protected min: number
98
+
91
99
  /**
92
100
  * Display unit for force measurements (output unit for notify callbacks).
93
101
  * @type {ForceUnit}
@@ -109,6 +117,52 @@ export abstract class Device extends BaseModel implements IDevice {
109
117
  */
110
118
  protected samplingRateHz?: number
111
119
 
120
+ /**
121
+ * Timestamp (ms) of the previous BLE notification for notify-interval calculation.
122
+ * @protected
123
+ */
124
+ protected lastPacketTimestamp = 0
125
+
126
+ /**
127
+ * Count of data packets received this session (one BLE notification = one packet).
128
+ * @protected
129
+ */
130
+ protected packetCount = 0
131
+
132
+ /**
133
+ * Notify interval in ms for the current packet (set by recordPacketReceived).
134
+ * @protected
135
+ */
136
+ protected currentNotifyIntervalMs: number | undefined = undefined
137
+
138
+ /**
139
+ * Samples in the current packet (set by device before buildForceMeasurement).
140
+ * @protected
141
+ */
142
+ protected currentSamplesPerPacket: number | undefined = undefined
143
+
144
+ /**
145
+ * Call at the start of each BLE notification (packet). Updates notify interval and packet count.
146
+ * @protected
147
+ */
148
+ protected recordPacketReceived(): void {
149
+ const now = Date.now()
150
+ this.currentNotifyIntervalMs = this.lastPacketTimestamp > 0 ? now - this.lastPacketTimestamp : undefined
151
+ this.lastPacketTimestamp = now
152
+ this.packetCount += 1
153
+ }
154
+
155
+ /**
156
+ * Reset packet tracking (call when starting a new stream).
157
+ * @protected
158
+ */
159
+ protected resetPacketTracking(): void {
160
+ this.lastPacketTimestamp = 0
161
+ this.packetCount = 0
162
+ this.currentNotifyIntervalMs = undefined
163
+ this.currentSamplesPerPacket = undefined
164
+ }
165
+
112
166
  /**
113
167
  * Start time of the current rate measurement interval.
114
168
  * @type {number}
@@ -234,6 +288,7 @@ export abstract class Device extends BaseModel implements IDevice {
234
288
 
235
289
  this.peak = Number.NEGATIVE_INFINITY
236
290
  this.mean = 0
291
+ this.min = Number.POSITIVE_INFINITY
237
292
  this.sum = 0
238
293
  this.dataPointCount = 0
239
294
  this.unit = "kg"
@@ -261,17 +316,15 @@ export abstract class Device extends BaseModel implements IDevice {
261
316
  const current = valueOrCurrent
262
317
  const zonePeak = useFullStats ? (peak === 0 && current < 0 ? current : peak) : valueOrCurrent
263
318
  const zoneMean = useFullStats ? mean : valueOrCurrent
264
- const zone: ForceMeasurement = {
319
+ const zoneMin = useFullStats ? Math.min(zonePeak, current) : current
320
+ return {
265
321
  unit: this.unit,
266
322
  timestamp: Date.now(),
267
323
  current,
268
324
  peak: zonePeak,
269
325
  mean: zoneMean,
326
+ min: zoneMin,
270
327
  }
271
- if (this.samplingRateHz !== undefined) {
272
- zone.samplingRateHz = this.samplingRateHz
273
- }
274
- return zone
275
328
  }
276
329
 
277
330
  /**
@@ -304,49 +357,111 @@ export abstract class Device extends BaseModel implements IDevice {
304
357
  }
305
358
 
306
359
  /**
307
- * Builds a ForceMeasurement payload with unit and timestamp for notify callbacks.
308
- * @param current - Current force at this sample
309
- * @param distribution - Optional zone distribution: numbers (converted via buildZoneMeasurement) or full ForceMeasurement per zone
310
- * @returns ForceMeasurement
311
- * @protected
360
+ * Shared base for ForceMeasurement/DownloadPacket payload construction.
361
+ * @private
312
362
  */
313
- protected buildForceMeasurement(
363
+ private buildForcePayload(
314
364
  current: number,
315
- distribution?: {
316
- left?: ForceMeasurement
317
- center?: ForceMeasurement
318
- right?: ForceMeasurement
365
+ overrides?: {
366
+ timestamp?: number
367
+ sampleIndex?: number
368
+ distribution?: { left?: ForceMeasurement; center?: ForceMeasurement; right?: ForceMeasurement }
319
369
  },
320
370
  ): ForceMeasurement {
321
- this.updateSamplingRate()
371
+ const timestamp = overrides?.timestamp ?? Date.now()
322
372
  const payload: ForceMeasurement = {
323
373
  unit: this.unit,
324
- timestamp: Date.now(),
374
+ timestamp,
325
375
  current: convertForce(current, this.streamUnit, this.unit),
326
376
  peak: convertForce(this.peak, this.streamUnit, this.unit),
327
377
  mean: convertForce(this.mean, this.streamUnit, this.unit),
378
+ min: Number.isFinite(this.min)
379
+ ? convertForce(this.min, this.streamUnit, this.unit)
380
+ : convertForce(0, this.streamUnit, this.unit),
381
+ performance: {
382
+ packetIndex: this.packetCount,
383
+ ...(overrides?.sampleIndex != null && { sampleIndex: overrides.sampleIndex }),
384
+ ...(this.currentNotifyIntervalMs != null && { notifyIntervalMs: this.currentNotifyIntervalMs }),
385
+ ...(this.currentSamplesPerPacket != null && { samplesPerPacket: this.currentSamplesPerPacket }),
386
+ ...(this.samplingRateHz != null && { samplingRateHz: this.samplingRateHz }),
387
+ },
328
388
  }
329
- if (this.samplingRateHz !== undefined) {
330
- payload.samplingRateHz = this.samplingRateHz
331
- }
332
- if (
333
- distribution !== undefined &&
334
- (distribution.left !== undefined || distribution.center !== undefined || distribution.right !== undefined)
335
- ) {
389
+
390
+ const distribution = overrides?.distribution
391
+ if (distribution && (distribution.left != null || distribution.center != null || distribution.right != null)) {
336
392
  payload.distribution = {}
337
- if (distribution.left !== undefined) {
393
+ if (distribution.left != null) {
338
394
  payload.distribution.left = convertForceMeasurement(distribution.left, this.streamUnit, this.unit)
339
395
  }
340
- if (distribution.center !== undefined) {
396
+ if (distribution.center != null) {
341
397
  payload.distribution.center = convertForceMeasurement(distribution.center, this.streamUnit, this.unit)
342
398
  }
343
- if (distribution.right !== undefined) {
399
+ if (distribution.right != null) {
344
400
  payload.distribution.right = convertForceMeasurement(distribution.right, this.streamUnit, this.unit)
345
401
  }
346
402
  }
403
+
347
404
  return payload
348
405
  }
349
406
 
407
+ /**
408
+ * Builds a ForceMeasurement payload for notify callbacks.
409
+ * @param current - Current force at this sample
410
+ * @param distribution - Optional per-zone measurements (e.g. from buildZoneMeasurement)
411
+ * @returns ForceMeasurement
412
+ * @protected
413
+ */
414
+ protected buildForceMeasurement(
415
+ current: number,
416
+ distribution?: {
417
+ left?: ForceMeasurement
418
+ center?: ForceMeasurement
419
+ right?: ForceMeasurement
420
+ },
421
+ ): ForceMeasurement {
422
+ this.updateSamplingRate()
423
+ return this.buildForcePayload(
424
+ current,
425
+ distribution != null ? { sampleIndex: this.dataPointCount, distribution } : { sampleIndex: this.dataPointCount },
426
+ )
427
+ }
428
+
429
+ /**
430
+ * Builds a DownloadPacket for export (CSV, JSON, XML).
431
+ * Converts force values from streamUnit to display unit.
432
+ * @param current - Current force at this sample (stream unit)
433
+ * @param samples - Raw sensor/ADC values from device
434
+ * @param options - Optional timestamp, battRaw, sampleIndex, distribution (for multi-zone)
435
+ * @returns DownloadPacket
436
+ * @protected
437
+ */
438
+ protected buildDownloadPacket(
439
+ current: number,
440
+ samples: number[],
441
+ options?: {
442
+ timestamp?: number
443
+ battRaw?: number
444
+ sampleIndex?: number
445
+ distribution?: { left?: ForceMeasurement; center?: ForceMeasurement; right?: ForceMeasurement }
446
+ },
447
+ ): DownloadPacket {
448
+ const overrides: {
449
+ timestamp?: number
450
+ sampleIndex?: number
451
+ distribution?: { left?: ForceMeasurement; center?: ForceMeasurement; right?: ForceMeasurement }
452
+ } = {}
453
+ if (options?.timestamp != null) overrides.timestamp = options.timestamp
454
+ if (options?.sampleIndex != null) overrides.sampleIndex = options.sampleIndex
455
+ if (options?.distribution != null) overrides.distribution = options.distribution
456
+ const packet = this.buildForcePayload(
457
+ current,
458
+ Object.keys(overrides).length > 0 ? overrides : undefined,
459
+ ) as DownloadPacket
460
+ packet.samples = samples
461
+ if (options?.battRaw != null) packet.battRaw = options.battRaw
462
+ return packet
463
+ }
464
+
350
465
  /**
351
466
  * Sets the callback function to be called when the activity status changes,
352
467
  * and optionally sets the configuration for threshold and duration.
@@ -523,18 +638,30 @@ export abstract class Device extends BaseModel implements IDevice {
523
638
  return ""
524
639
  }
525
640
  return packets
526
- .map((packet) =>
527
- [
528
- packet.received.toString(),
529
- packet.sampleNum.toString(),
530
- packet.battRaw.toString(),
641
+ .map((packet) => {
642
+ const forceValues =
643
+ packet.distribution != null
644
+ ? [
645
+ packet.distribution.left?.current ?? "",
646
+ packet.distribution.center?.current ?? "",
647
+ packet.distribution.right?.current ?? "",
648
+ ].map((v) => (v !== "" ? String(v) : ""))
649
+ : [packet.current.toString()]
650
+ return [
651
+ packet.timestamp.toString(),
652
+ packet.current.toString(),
653
+ packet.peak.toString(),
654
+ packet.mean.toString(),
655
+ packet.min.toString(),
656
+ (packet.performance?.sampleIndex ?? "").toString(),
657
+ (packet.battRaw ?? "").toString(),
531
658
  ...packet.samples.map(String),
532
- ...packet.masses.map(String),
659
+ ...forceValues,
533
660
  ]
534
661
  .map((v) => v.replace(/"/g, '""'))
535
662
  .map((v) => `"${v}"`)
536
- .join(","),
537
- )
663
+ .join(",")
664
+ })
538
665
  .join("\r\n")
539
666
  }
540
667
 
@@ -565,14 +692,27 @@ export abstract class Device extends BaseModel implements IDevice {
565
692
  const xmlPackets = this.downloadPackets
566
693
  .map((packet) => {
567
694
  const samples = packet.samples.map((sample) => `<sample>${sample}</sample>`).join("")
568
- const masses = packet.masses.map((mass) => `<mass>${mass}</mass>`).join("")
695
+ const distributionElements =
696
+ packet.distribution != null
697
+ ? [
698
+ packet.distribution.left?.current != null ? `<left>${packet.distribution.left.current}</left>` : "",
699
+ packet.distribution.center?.current != null
700
+ ? `<center>${packet.distribution.center.current}</center>`
701
+ : "",
702
+ packet.distribution.right?.current != null ? `<right>${packet.distribution.right.current}</right>` : "",
703
+ ].join("")
704
+ : ""
569
705
  return `
570
706
  <packet>
571
- <received>${packet.received}</received>
572
- <sampleNum>${packet.sampleNum}</sampleNum>
573
- <battRaw>${packet.battRaw}</battRaw>
707
+ <timestamp>${packet.timestamp}</timestamp>
708
+ <current>${packet.current}</current>
709
+ <peak>${packet.peak}</peak>
710
+ <mean>${packet.mean}</mean>
711
+ <min>${packet.min}</min>
712
+ ${packet.performance?.sampleIndex != null ? `<sampleIndex>${packet.performance.sampleIndex}</sampleIndex>` : ""}
713
+ ${packet.battRaw != null ? `<battRaw>${packet.battRaw}</battRaw>` : ""}
574
714
  <samples>${samples}</samples>
575
- <masses>${masses}</masses>
715
+ ${distributionElements}
576
716
  </packet>
577
717
  `
578
718
  })
@@ -939,7 +1079,7 @@ export abstract class Device extends BaseModel implements IDevice {
939
1079
  *
940
1080
  * @example
941
1081
  * // Example usage of the write function with a custom callback
942
- * await Progressor.write("progressor", "tx", ProgressorCommands.GET_BATT_VLTG, 250, (data) => {
1082
+ * await Progressor.write("progressor", "tx", ProgressorCommands.GET_BATTERY_VOLTAGE, 250, (data) => {
943
1083
  * console.log(`Battery voltage: ${data}`);
944
1084
  * });
945
1085
  */
package/src/utils.ts CHANGED
@@ -1,10 +1,34 @@
1
1
  import type { ForceMeasurement, ForceUnit } from "./interfaces/callback.interface.js"
2
2
 
3
- /** 1 kg = this many lbs (force-equivalent) */
4
- const KG_TO_LBS = 2.20462262185
3
+ /** 1 kgf = this many N (standard gravity) */
4
+ const KG_TO_N = 9.80665
5
+ /** 1 lbf = this many N */
6
+ const LBS_TO_N = 4.4482216152605
7
+
8
+ function toNewtons(value: number, from: ForceUnit): number {
9
+ switch (from) {
10
+ case "kg":
11
+ return value * KG_TO_N
12
+ case "lbs":
13
+ return value * LBS_TO_N
14
+ case "n":
15
+ return value
16
+ }
17
+ }
18
+
19
+ function fromNewtons(value: number, to: ForceUnit): number {
20
+ switch (to) {
21
+ case "kg":
22
+ return value / KG_TO_N
23
+ case "lbs":
24
+ return value / LBS_TO_N
25
+ case "n":
26
+ return value
27
+ }
28
+ }
5
29
 
6
30
  /**
7
- * Converts a force value between kg and lbs.
31
+ * Converts a force value between kg, lbs, and newtons.
8
32
  * @param value - The numeric force value in the source unit.
9
33
  * @param from - The unit of the input value.
10
34
  * @param to - The unit for the output value.
@@ -12,7 +36,7 @@ const KG_TO_LBS = 2.20462262185
12
36
  */
13
37
  export function convertForce(value: number, from: ForceUnit, to: ForceUnit): number {
14
38
  if (from === to) return value
15
- return from === "kg" ? value * KG_TO_LBS : value / KG_TO_LBS
39
+ return fromNewtons(toNewtons(value, from), to)
16
40
  }
17
41
 
18
42
  /**
@@ -32,9 +56,10 @@ export function convertForceMeasurement(
32
56
  current: convertForce(measurement.current, from, to),
33
57
  peak: convertForce(measurement.peak, from, to),
34
58
  mean: convertForce(measurement.mean, from, to),
59
+ min: convertForce(measurement.min, from, to),
35
60
  }
36
- if (measurement.samplingRateHz !== undefined) {
37
- out.samplingRateHz = measurement.samplingRateHz
61
+ if (measurement.performance !== undefined) {
62
+ out.performance = { ...measurement.performance }
38
63
  }
39
64
  if (
40
65
  measurement.distribution &&