@hangtime/grip-connect 0.9.0 → 0.10.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 (116) hide show
  1. package/README.md +8 -4
  2. package/dist/cjs/index.d.ts +2 -1
  3. package/dist/cjs/index.d.ts.map +1 -1
  4. package/dist/cjs/index.js +3 -1
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/interfaces/callback.interface.d.ts +37 -31
  7. package/dist/cjs/interfaces/callback.interface.d.ts.map +1 -1
  8. package/dist/cjs/interfaces/device/kilterboard.interface.d.ts +3 -2
  9. package/dist/cjs/interfaces/device/kilterboard.interface.d.ts.map +1 -1
  10. package/dist/cjs/interfaces/device.interface.d.ts +2 -2
  11. package/dist/cjs/interfaces/device.interface.d.ts.map +1 -1
  12. package/dist/cjs/models/device/climbro.model.d.ts.map +1 -1
  13. package/dist/cjs/models/device/climbro.model.js +4 -8
  14. package/dist/cjs/models/device/climbro.model.js.map +1 -1
  15. package/dist/cjs/models/device/entralpi.model.d.ts.map +1 -1
  16. package/dist/cjs/models/device/entralpi.model.js +5 -9
  17. package/dist/cjs/models/device/entralpi.model.js.map +1 -1
  18. package/dist/cjs/models/device/forceboard.model.d.ts.map +1 -1
  19. package/dist/cjs/models/device/forceboard.model.js +5 -9
  20. package/dist/cjs/models/device/forceboard.model.js.map +1 -1
  21. package/dist/cjs/models/device/kilterboard.model.d.ts +5 -3
  22. package/dist/cjs/models/device/kilterboard.model.d.ts.map +1 -1
  23. package/dist/cjs/models/device/kilterboard.model.js +52 -16
  24. package/dist/cjs/models/device/kilterboard.model.js.map +1 -1
  25. package/dist/cjs/models/device/motherboard.model.d.ts +7 -0
  26. package/dist/cjs/models/device/motherboard.model.d.ts.map +1 -1
  27. package/dist/cjs/models/device/motherboard.model.js +28 -15
  28. package/dist/cjs/models/device/motherboard.model.js.map +1 -1
  29. package/dist/cjs/models/device/pb-700bt.model.d.ts +63 -0
  30. package/dist/cjs/models/device/pb-700bt.model.d.ts.map +1 -0
  31. package/dist/cjs/models/device/pb-700bt.model.js +247 -0
  32. package/dist/cjs/models/device/pb-700bt.model.js.map +1 -0
  33. package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
  34. package/dist/cjs/models/device/progressor.model.js +4 -8
  35. package/dist/cjs/models/device/progressor.model.js.map +1 -1
  36. package/dist/cjs/models/device/smartboard-pro.model.d.ts +8 -0
  37. package/dist/cjs/models/device/smartboard-pro.model.d.ts.map +1 -1
  38. package/dist/cjs/models/device/smartboard-pro.model.js +62 -6
  39. package/dist/cjs/models/device/smartboard-pro.model.js.map +1 -1
  40. package/dist/cjs/models/device/wh-c06.model.d.ts.map +1 -1
  41. package/dist/cjs/models/device/wh-c06.model.js +5 -9
  42. package/dist/cjs/models/device/wh-c06.model.js.map +1 -1
  43. package/dist/cjs/models/device.model.d.ts +72 -12
  44. package/dist/cjs/models/device.model.d.ts.map +1 -1
  45. package/dist/cjs/models/device.model.js +127 -5
  46. package/dist/cjs/models/device.model.js.map +1 -1
  47. package/dist/cjs/models/index.d.ts +2 -1
  48. package/dist/cjs/models/index.d.ts.map +1 -1
  49. package/dist/cjs/models/index.js +4 -1
  50. package/dist/cjs/models/index.js.map +1 -1
  51. package/dist/index.d.ts +2 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/interfaces/callback.interface.d.ts +37 -31
  56. package/dist/interfaces/callback.interface.d.ts.map +1 -1
  57. package/dist/interfaces/device/kilterboard.interface.d.ts +3 -2
  58. package/dist/interfaces/device/kilterboard.interface.d.ts.map +1 -1
  59. package/dist/interfaces/device.interface.d.ts +2 -2
  60. package/dist/interfaces/device.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 +4 -8
  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 +5 -9
  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 +5 -9
  69. package/dist/models/device/forceboard.model.js.map +1 -1
  70. package/dist/models/device/kilterboard.model.d.ts +5 -3
  71. package/dist/models/device/kilterboard.model.d.ts.map +1 -1
  72. package/dist/models/device/kilterboard.model.js +52 -16
  73. package/dist/models/device/kilterboard.model.js.map +1 -1
  74. package/dist/models/device/motherboard.model.d.ts +7 -0
  75. package/dist/models/device/motherboard.model.d.ts.map +1 -1
  76. package/dist/models/device/motherboard.model.js +28 -15
  77. package/dist/models/device/motherboard.model.js.map +1 -1
  78. package/dist/models/device/pb-700bt.model.d.ts +63 -0
  79. package/dist/models/device/pb-700bt.model.d.ts.map +1 -0
  80. package/dist/models/device/pb-700bt.model.js +243 -0
  81. package/dist/models/device/pb-700bt.model.js.map +1 -0
  82. package/dist/models/device/progressor.model.d.ts.map +1 -1
  83. package/dist/models/device/progressor.model.js +4 -8
  84. package/dist/models/device/progressor.model.js.map +1 -1
  85. package/dist/models/device/smartboard-pro.model.d.ts +8 -0
  86. package/dist/models/device/smartboard-pro.model.d.ts.map +1 -1
  87. package/dist/models/device/smartboard-pro.model.js +62 -6
  88. package/dist/models/device/smartboard-pro.model.js.map +1 -1
  89. package/dist/models/device/wh-c06.model.d.ts.map +1 -1
  90. package/dist/models/device/wh-c06.model.js +5 -9
  91. package/dist/models/device/wh-c06.model.js.map +1 -1
  92. package/dist/models/device.model.d.ts +72 -12
  93. package/dist/models/device.model.d.ts.map +1 -1
  94. package/dist/models/device.model.js +149 -14
  95. package/dist/models/device.model.js.map +1 -1
  96. package/dist/models/index.d.ts +2 -1
  97. package/dist/models/index.d.ts.map +1 -1
  98. package/dist/models/index.js +2 -1
  99. package/dist/models/index.js.map +1 -1
  100. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  101. package/package.json +4 -1
  102. package/src/index.ts +11 -0
  103. package/src/interfaces/callback.interface.ts +41 -31
  104. package/src/interfaces/device/kilterboard.interface.ts +2 -2
  105. package/src/interfaces/device.interface.ts +2 -2
  106. package/src/models/device/climbro.model.ts +4 -8
  107. package/src/models/device/entralpi.model.ts +5 -9
  108. package/src/models/device/forceboard.model.ts +5 -9
  109. package/src/models/device/kilterboard.model.ts +56 -19
  110. package/src/models/device/motherboard.model.ts +32 -15
  111. package/src/models/device/pb-700bt.model.ts +263 -0
  112. package/src/models/device/progressor.model.ts +4 -8
  113. package/src/models/device/smartboard-pro.model.ts +73 -6
  114. package/src/models/device/wh-c06.model.ts +5 -9
  115. package/src/models/device.model.ts +179 -16
  116. package/src/models/index.ts +3 -1
@@ -1,6 +1,12 @@
1
1
  import { BaseModel } from "./../models/base.model.js"
2
2
  import type { IDevice, Service } from "../interfaces/device.interface.js"
3
- import type { ActiveCallback, massObject, NotifyCallback, WriteCallback } from "../interfaces/callback.interface.js"
3
+ import type {
4
+ ActiveCallback,
5
+ ForceMeasurement,
6
+ ForceUnit,
7
+ NotifyCallback,
8
+ WriteCallback,
9
+ } from "../interfaces/callback.interface.js"
4
10
  import type { DownloadPacket } from "../interfaces/download.interface.js"
5
11
  import type { Commands } from "../interfaces/command.interface.js"
6
12
 
@@ -67,26 +73,55 @@ export abstract class Device extends BaseModel implements IDevice {
67
73
  }
68
74
 
69
75
  /**
70
- * Maximum mass recorded from the device, initialized to "0".
71
- * @type {string}
76
+ * Highest instantaneous force (peak) recorded in the session; may be negative.
77
+ * Initialized to Number.NEGATIVE_INFINITY so the first sample sets the peak.
78
+ * @type {number}
72
79
  * @protected
73
80
  */
74
- protected massMax: string
81
+ protected peak: number
75
82
 
76
83
  /**
77
- * Average mass calculated from the device data, initialized to "0".
78
- * @type {string}
84
+ * Mean (average) force over the session, initialized to 0.
85
+ * @type {number}
79
86
  * @protected
80
87
  */
81
- protected massAverage: string
88
+ protected mean: number
82
89
 
83
90
  /**
84
- * Total sum of all mass data points recorded from the device.
85
- * Used to calculate the average mass.
91
+ * Display unit for force measurements.
92
+ * @type {ForceUnit}
93
+ * @protected
94
+ */
95
+ protected unit: ForceUnit
96
+
97
+ /**
98
+ * Optional sampling rate in Hz when known or calculated from notification timestamps.
99
+ * @type {number | undefined}
100
+ * @protected
101
+ */
102
+ protected samplingRateHz?: number
103
+
104
+ /**
105
+ * Start time of the current rate measurement interval.
106
+ * @type {number}
107
+ * @private
108
+ */
109
+ private rateIntervalStart = 0
110
+
111
+ /**
112
+ * Number of samples in the current rate measurement interval.
113
+ * @type {number}
114
+ * @private
115
+ */
116
+ private rateIntervalSamples = 0
117
+
118
+ /**
119
+ * Running sum of force values for the session.
120
+ * Used to calculate mean (average) force.
86
121
  * @type {number}
87
122
  * @protected
88
123
  */
89
- protected massTotalSum: number
124
+ protected sum: number
90
125
 
91
126
  /**
92
127
  * Number of data points received from the device.
@@ -135,13 +170,13 @@ export abstract class Device extends BaseModel implements IDevice {
135
170
  private tareDuration = 5000
136
171
 
137
172
  /**
138
- * Optional callback for handling write operations.
173
+ * Optional callback for handling mass/force data notifications.
139
174
  * @callback NotifyCallback
140
- * @param {massObject} data - The data passed to the callback.
175
+ * @param {ForceMeasurement} data - The force measurement passed to the callback.
141
176
  * @type {NotifyCallback | undefined}
142
177
  * @protected
143
178
  */
144
- protected notifyCallback: NotifyCallback = (data: massObject) => console.log(data)
179
+ protected notifyCallback: NotifyCallback = (data: ForceMeasurement) => console.log(data)
145
180
 
146
181
  /**
147
182
  * Optional callback for handling write operations.
@@ -189,15 +224,121 @@ export abstract class Device extends BaseModel implements IDevice {
189
224
  this.bluetooth = device.bluetooth
190
225
  }
191
226
 
192
- this.massMax = "0"
193
- this.massAverage = "0"
194
- this.massTotalSum = 0
227
+ this.peak = Number.NEGATIVE_INFINITY
228
+ this.mean = 0
229
+ this.sum = 0
195
230
  this.dataPointCount = 0
231
+ this.unit = "kg"
232
+
233
+ // Reset sampling rate calculation state
234
+ this.rateIntervalStart = 0
235
+ this.rateIntervalSamples = 0
196
236
 
197
237
  this.createdAt = new Date()
198
238
  this.updatedAt = new Date()
199
239
  }
200
240
 
241
+ /**
242
+ * Builds a ForceMeasurement for a single zone (e.g. left/center/right).
243
+ * With one argument, current/peak/mean are all set to that value.
244
+ * With three arguments, uses the given current, peak, and mean for the zone.
245
+ * @param valueOrCurrent - Force value, or current force for this zone
246
+ * @param peak - Optional peak for this zone (required if mean is provided)
247
+ * @param mean - Optional mean for this zone
248
+ * @returns ForceMeasurement (no nested distribution)
249
+ * @protected
250
+ */
251
+ protected buildZoneMeasurement(valueOrCurrent: number, peak?: number, mean?: number): ForceMeasurement {
252
+ const useFullStats = peak !== undefined && mean !== undefined
253
+ const current = valueOrCurrent
254
+ const zonePeak = useFullStats ? (peak === 0 && current < 0 ? current : peak) : valueOrCurrent
255
+ const zoneMean = useFullStats ? mean : valueOrCurrent
256
+ const zone: ForceMeasurement = {
257
+ unit: this.unit,
258
+ timestamp: Date.now(),
259
+ current,
260
+ peak: zonePeak,
261
+ mean: zoneMean,
262
+ }
263
+ if (this.samplingRateHz !== undefined) {
264
+ zone.samplingRateHz = this.samplingRateHz
265
+ }
266
+ return zone
267
+ }
268
+
269
+ /**
270
+ * Interval duration (ms) for sampling rate calculation.
271
+ * @private
272
+ * @readonly
273
+ */
274
+ private static readonly RATE_INTERVAL_MS = 1000
275
+
276
+ /**
277
+ * Calculates sampling rate: samples per second.
278
+ * Uses fixed intervals to avoid sliding window edge effects.
279
+ * @private
280
+ */
281
+ private updateSamplingRate(): void {
282
+ const now = Date.now()
283
+
284
+ if (this.rateIntervalStart === 0) {
285
+ this.rateIntervalStart = now
286
+ }
287
+
288
+ this.rateIntervalSamples++
289
+
290
+ const elapsed = now - this.rateIntervalStart
291
+ if (elapsed >= Device.RATE_INTERVAL_MS) {
292
+ this.samplingRateHz = Math.round((this.rateIntervalSamples / elapsed) * 1000)
293
+ this.rateIntervalStart = now
294
+ this.rateIntervalSamples = 0
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Builds a ForceMeasurement payload with unit and timestamp for notify callbacks.
300
+ * @param current - Current force at this sample
301
+ * @param distribution - Optional zone distribution: numbers (converted via buildZoneMeasurement) or full ForceMeasurement per zone
302
+ * @returns ForceMeasurement
303
+ * @protected
304
+ */
305
+ protected buildForceMeasurement(
306
+ current: number,
307
+ distribution?: {
308
+ left?: ForceMeasurement
309
+ center?: ForceMeasurement
310
+ right?: ForceMeasurement
311
+ },
312
+ ): ForceMeasurement {
313
+ this.updateSamplingRate()
314
+ const payload: ForceMeasurement = {
315
+ unit: this.unit,
316
+ timestamp: Date.now(),
317
+ current,
318
+ peak: this.peak,
319
+ mean: this.mean,
320
+ }
321
+ if (this.samplingRateHz !== undefined) {
322
+ payload.samplingRateHz = this.samplingRateHz
323
+ }
324
+ if (
325
+ distribution !== undefined &&
326
+ (distribution.left !== undefined || distribution.center !== undefined || distribution.right !== undefined)
327
+ ) {
328
+ payload.distribution = {}
329
+ if (distribution.left !== undefined) {
330
+ payload.distribution.left = distribution.left
331
+ }
332
+ if (distribution.center !== undefined) {
333
+ payload.distribution.center = distribution.center
334
+ }
335
+ if (distribution.right !== undefined) {
336
+ payload.distribution.right = distribution.right
337
+ }
338
+ }
339
+ return payload
340
+ }
341
+
201
342
  /**
202
343
  * Sets the callback function to be called when the activity status changes,
203
344
  * and optionally sets the configuration for threshold and duration.
@@ -276,6 +417,28 @@ export abstract class Device extends BaseModel implements IDevice {
276
417
 
277
418
  const bluetooth = await this.getBluetooth()
278
419
 
420
+ // Experiment: Reconnect to known devices, enable these Chrome flags:
421
+ // - chrome://flags/#enable-experimental-web-platform-features → enables getDevices() API
422
+ // - chrome://flags/#enable-web-bluetooth-new-permissions-backend → ensures it returns all permitted devices, not just connected ones
423
+ // let reconnectDevice: BluetoothDevice | undefined
424
+ // if (typeof bluetooth.getDevices === "function") {
425
+ // const devices: BluetoothDevice[] = await bluetooth.getDevices()
426
+ // if (devices.length > 0 && this.filters.length > 0) {
427
+ // reconnectDevice = devices.find((device) => {
428
+ // if (!device.name) return false
429
+ // const d = device
430
+ // return this.filters.some(
431
+ // (f) => (f.name && d.name === f.name) || (f.namePrefix && d.name?.startsWith(f.namePrefix)),
432
+ // )
433
+ // })
434
+ // }
435
+ // if (reconnectDevice) {
436
+ // this.bluetooth = reconnectDevice
437
+ // // It's currently impossible to call this.bluetooth.gatt.connect() here.
438
+ // // After restarting the Browser, it will always give: "Bluetooth Device is no longer in range."
439
+ // }
440
+ // }
441
+
279
442
  this.bluetooth = await bluetooth.requestDevice({
280
443
  filters: this.filters,
281
444
  optionalServices: deviceServices,
@@ -4,12 +4,14 @@ export { Entralpi } from "./device/entralpi.model.js"
4
4
 
5
5
  export { ForceBoard } from "./device/forceboard.model.js"
6
6
 
7
- export { KilterBoard } from "./device/kilterboard.model.js"
7
+ export { KilterBoard, KilterBoardPlacementRoles } from "./device/kilterboard.model.js"
8
8
 
9
9
  export { Motherboard } from "./device/motherboard.model.js"
10
10
 
11
11
  export { mySmartBoard } from "./device/mysmartboard.model.js"
12
12
 
13
+ export { PB700BT } from "./device/pb-700bt.model.js"
14
+
13
15
  export { Progressor } from "./device/progressor.model.js"
14
16
 
15
17
  export { SmartBoardPro } from "./device/smartboard-pro.model.js"