@hangtime/grip-connect 0.10.1 → 0.10.3

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 +14 -6
  9. package/dist/cjs/interfaces/command.interface.d.ts.map +1 -1
  10. package/dist/cjs/interfaces/device/progressor.interface.d.ts +47 -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 +52 -5
  31. package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
  32. package/dist/cjs/models/device/progressor.model.js +211 -57
  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 +14 -6
  55. package/dist/interfaces/command.interface.d.ts.map +1 -1
  56. package/dist/interfaces/device/progressor.interface.d.ts +47 -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 +52 -5
  77. package/dist/models/device/progressor.model.d.ts.map +1 -1
  78. package/dist/models/device/progressor.model.js +210 -57
  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 +16 -6
  99. package/src/interfaces/device/progressor.interface.ts +56 -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 +227 -58
  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
@@ -9,38 +9,82 @@ enum ProgressorResponses {
9
9
  * Response received after sending a command to the device.
10
10
  * This could include acknowledgment or specific data related to the command sent.
11
11
  */
12
- COMMAND_RESPONSE,
12
+ RESPONSE_COMMAND,
13
13
 
14
14
  /**
15
15
  * Data representing a weight measurement from the device.
16
16
  * Typically used for tracking load or force applied.
17
17
  */
18
- WEIGHT_MEASURE,
18
+ RESPONSE_WEIGHT_MEASUREMENT,
19
19
 
20
20
  /**
21
21
  * Peak rate of force development (RFD) measurement.
22
22
  * This measures how quickly the force is applied over time.
23
23
  */
24
- PEAK_RFD_MEAS,
24
+ RESPONSE_RFD_PEAK,
25
25
 
26
26
  /**
27
27
  * Series of peak rate of force development (RFD) measurements.
28
28
  * This could be used for analyzing force trends over multiple data points.
29
29
  */
30
- PEAK_RFD_MEAS_SERIES,
30
+ RESPONSE_RFD_PEAK_SERIES,
31
31
 
32
32
  /**
33
33
  * Low battery warning from the device.
34
34
  * Indicates that the battery level is below a critical threshold.
35
35
  */
36
- LOW_BATTERY_WARNING,
36
+ RESPONSE_LOW_PWR_WARNING,
37
37
  }
38
38
 
39
39
  /**
40
40
  * Represents a Tindeq Progressor device.
41
41
  * {@link https://tindeq.com}
42
42
  */
43
+ /** One second in microseconds (device timestamp unit). Used for Hz = samples in last 1s. */
44
+ const ONE_SECOND_US = 1_000_000
45
+
46
+ /**
47
+ * Format bytes as hex string.
48
+ * @param payload - Bytes to format
49
+ * @param separator - String between bytes (default " ")
50
+ */
51
+ function toHex(payload: Uint8Array, separator = " "): string {
52
+ return Array.from(payload)
53
+ .map((b) => b.toString(16).padStart(2, "0"))
54
+ .join(separator)
55
+ }
56
+
57
+ /**
58
+ * Parse ProgressorId response: u64 little-endian, device may omit trailing zero bytes.
59
+ * Format as hex string MSB-first to match the official app.
60
+ */
61
+ function parseProgressorIdPayload(payload: Uint8Array): string {
62
+ if (payload.length === 0) return ""
63
+ const reversed = Uint8Array.from(payload)
64
+ reversed.reverse()
65
+ return toHex(reversed, "").toUpperCase()
66
+ }
67
+
68
+ /**
69
+ * Parse calibration curve: 12 opaque bytes (CalibrationCurve = [u8; 12]).
70
+ * Progressor two-point calibration stores two raw ADC readings (zero + reference)
71
+ * and a third value (version/reserved). Exact layout is device-specific.
72
+ */
73
+ function parseCalibrationCurvePayload(payload: Uint8Array): string {
74
+ const hex = toHex(payload)
75
+
76
+ if (payload.length !== 12) return hex
77
+
78
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)
79
+ const [v0, v1, v2] = [0, 4, 8].map((o) => view.getUint32(o, true))
80
+
81
+ return `${hex} — zero-point: ${v1.toLocaleString()} | ref-point: ${v0.toLocaleString()} | version: ${v2} (0x${v2.toString(16)})`
82
+ }
83
+
43
84
  export class Progressor extends Device implements IProgressor {
85
+ /** Device timestamps (µs) of recent samples (samples in last 1s device time). */
86
+ private recentSampleTimestamps: number[] = []
87
+
44
88
  constructor() {
45
89
  super({
46
90
  filters: [{ namePrefix: "Progressor" }],
@@ -75,19 +119,23 @@ export class Progressor extends Device implements IProgressor {
75
119
  ],
76
120
  },
77
121
  ],
122
+ // Tindeq API: opcode = single byte (ASCII char code = decimal 100–114;)
78
123
  commands: {
79
- TARE_SCALE: "d", // 0x64
80
- START_WEIGHT_MEAS: "e", // 0x65
81
- STOP_WEIGHT_MEAS: "f", // 0x66
82
- START_PEAK_RFD_MEAS: "g", // 0x67
83
- START_PEAK_RFD_MEAS_SERIES: "h", // 0x68
84
- ADD_CALIB_POINT: "i", // 0x69
85
- SAVE_CALIB: "j", // 0x6a
86
- GET_FW_VERSION: "k", // 0x6b
87
- GET_ERR_INFO: "l", // 0x6c
88
- CLR_ERR_INFO: "m", // 0x6d
89
- SLEEP: "n", // 0x6e
90
- GET_BATT_VLTG: "o", // 0x6f
124
+ TARE_SCALE: "d", // 100 (0x64)
125
+ START_WEIGHT_MEAS: "e", // 101 (0x65)
126
+ STOP_WEIGHT_MEAS: "f", // 102 (0x66)
127
+ START_PEAK_RFD_MEAS: "g", // 103 (0x67)
128
+ START_PEAK_RFD_MEAS_SERIES: "h", // 104 (0x68)
129
+ ADD_CALIBRATION_POINT: "i", // 105 (0x69)
130
+ SAVE_CALIBRATION: "j", // 106 (0x6a)
131
+ GET_FIRMWARE_VERSION: "k", // 107 (0x6b)
132
+ GET_ERROR_INFORMATION: "l", // 108 (0x6c)
133
+ CLR_ERROR_INFORMATION: "m", // 109 (0x6d)
134
+ SLEEP: "n", // 110 (0x6e)
135
+ GET_BATTERY_VOLTAGE: "o", // 111 (0x6f)
136
+ GET_PROGRESSOR_ID: "p", // 112 (0x70)
137
+ RESET_CALIBRATION: "q", // 113 (0x71)
138
+ GET_CALIBRATION: "r", // 114 (0x72)
91
139
  },
92
140
  })
93
141
  }
@@ -98,24 +146,116 @@ export class Progressor extends Device implements IProgressor {
98
146
  */
99
147
  battery = async (): Promise<string | undefined> => {
100
148
  let response: string | undefined = undefined
101
- await this.write("progressor", "tx", this.commands.GET_BATT_VLTG, 250, (data) => {
149
+ await this.write("progressor", "tx", this.commands.GET_BATTERY_VOLTAGE, 250, (data) => {
102
150
  response = data
103
151
  })
104
152
  return response
105
153
  }
106
154
 
107
155
  /**
108
- * Retrieves firmware version from the device.
156
+ * Retrieves firmware version from the device (GetAppVersion, opcode 0x6B).
109
157
  * @returns {Promise<string>} A Promise that resolves with the firmware version,
110
158
  */
111
159
  firmware = async (): Promise<string | undefined> => {
112
160
  let response: string | undefined = undefined
113
- await this.write("progressor", "tx", this.commands.GET_FW_VERSION, 250, (data) => {
161
+ await this.write("progressor", "tx", this.commands.GET_FIRMWARE_VERSION, 250, (data) => {
162
+ response = data
163
+ })
164
+ return response
165
+ }
166
+
167
+ /**
168
+ * Retrieves the Progressor ID from the device (opcode 0x70).
169
+ * @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
170
+ */
171
+ progressorId = async (): Promise<string | undefined> => {
172
+ let response: string | undefined = undefined
173
+ await this.write("progressor", "tx", this.commands.GET_PROGRESSOR_ID, 250, (data) => {
114
174
  response = data
115
175
  })
116
176
  return response
117
177
  }
118
178
 
179
+ /**
180
+ * Retrieves calibration values from the device.
181
+ * @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
182
+ */
183
+ calibration = async (): Promise<string | undefined> => {
184
+ let response: string | undefined = undefined
185
+ await this.write("progressor", "tx", this.commands.GET_CALIBRATION, 250, (data) => {
186
+ response = data
187
+ })
188
+ return response
189
+ }
190
+
191
+ /**
192
+ * Saves the current calibration to the device. Call after adding both calibration points.
193
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
194
+ */
195
+ saveCalibration = async (): Promise<void> => {
196
+ await this.write("progressor", "tx", this.commands.SAVE_CALIBRATION, 0)
197
+ }
198
+
199
+ /**
200
+ * Resets the calibration to default values of the device.
201
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
202
+ */
203
+ resetCalibration = async (): Promise<void> => {
204
+ await this.write("progressor", "tx", this.commands.RESET_CALIBRATION, 0)
205
+ }
206
+
207
+ /**
208
+ * Adds a calibration point: 0x69 + 32-bit float (weight in kg), 5 bytes to the control characteristic.
209
+ * Use for two-point calibration: (1) zero weight — addCalibrationPoint(0); (2) known weight — addCalibrationPoint(knownKg).
210
+ * Order can be either way; writing zero first is recommended (hysteresis). Then call saveCalibration().
211
+ * @param {number} weightKg - Weight in kg (use 0 for zero point, or known reference weight &lt; 150 kg).
212
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
213
+ */
214
+ addCalibrationPoint = async (weightKg: number): Promise<void> => {
215
+ const payload = new Uint8Array(5)
216
+ payload[0] = (this.commands.ADD_CALIBRATION_POINT as string).charCodeAt(0) // 0x69
217
+ new DataView(payload.buffer).setFloat32(1, weightKg, true)
218
+ await this.write("progressor", "tx", payload, 0)
219
+ }
220
+
221
+ /**
222
+ * Sends the device tare command to zero the scale (hardware tare).
223
+ * For software tare over a duration, use the base tare(duration) method.
224
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
225
+ */
226
+ tareScale = async (): Promise<void> => {
227
+ await this.write("progressor", "tx", this.commands.TARE_SCALE, 0)
228
+ }
229
+
230
+ /**
231
+ * Puts the device to sleep / shutdown.
232
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
233
+ */
234
+ sleep = async (): Promise<void> => {
235
+ const cmd = this.commands.SLEEP
236
+ await this.write("progressor", "tx", typeof cmd === "string" ? cmd : String(cmd), 0)
237
+ }
238
+
239
+ /**
240
+ * Retrieves error information from the device.
241
+ * @returns {Promise<string | undefined>} A Promise that resolves with the error info text.
242
+ */
243
+ errorInfo = async (): Promise<string | undefined> => {
244
+ let response: string | undefined = undefined
245
+ await this.write("progressor", "tx", this.commands.GET_ERROR_INFORMATION, 250, (data) => {
246
+ response = data
247
+ })
248
+ return response
249
+ }
250
+
251
+ /**
252
+ * Clears error information on the device.
253
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
254
+ */
255
+ clearErrorInfo = async (): Promise<void> => {
256
+ await this.write("progressor", "tx", this.commands.CLR_ERROR_INFORMATION, 0)
257
+ }
258
+
119
259
  /**
120
260
  * Handles data received from the device, processes weight measurements,
121
261
  * and updates mass data including maximum and average values.
@@ -132,61 +272,83 @@ export class Progressor extends Device implements IProgressor {
132
272
  // Read the first byte of the buffer to determine the kind of message
133
273
  const kind = value.getInt8(0)
134
274
  // Check if the message is a weight measurement
135
- if (kind === ProgressorResponses.WEIGHT_MEASURE) {
136
- // Start parsing data from the 3rd byte (index 2)
137
- let offset = 2
138
- // Continue parsing while there's data left in the buffer
139
- while (offset < value.byteLength) {
140
- // Read a 32-bit float (4 bytes) for the weight, using little-endian
275
+ if (kind === ProgressorResponses.RESPONSE_WEIGHT_MEASUREMENT) {
276
+ // Payload = bytes from index 2 (skip byte 0 = message type, byte 1 = reserved/length)
277
+ const payloadLength = value.byteLength - 2
278
+ if (payloadLength % 8 !== 0) return
279
+ const samplesPerPacket = payloadLength / 8
280
+ this.currentSamplesPerPacket = samplesPerPacket
281
+ this.recordPacketReceived()
282
+
283
+ // for (i = 0; i < samplesPerPacket; i++) { offset = i*8; weight = getFloat32(offset); timestampUs = getUint32(offset+4); }
284
+ for (let i = 0; i < samplesPerPacket; i++) {
285
+ const offset = 2 + i * 8
141
286
  const weight = value.getFloat32(offset, true)
142
- // Move the offset by 4 bytes
143
- offset += 4
144
- // Read a 32-bit integer (4 bytes) for the seconds, using little-endian
145
- const seconds = value.getInt32(offset, true)
146
- // Move the offset by 4 bytes
147
- offset += 4
148
- // Check if both weight and seconds are valid numbers
149
- if (!isNaN(weight) && !isNaN(seconds)) {
150
- // Tare correction
287
+ const timestampUs = value.getUint32(offset + 4, true)
288
+ if (!isNaN(weight)) {
151
289
  const numericData = weight - this.applyTare(weight)
152
- // Add data to downloadable Array
153
- this.downloadPackets.push({
154
- received: receivedTime,
155
- sampleNum: seconds,
156
- battRaw: 0,
157
- samples: [weight],
158
- masses: [numericData],
159
- })
160
- // Check for max weight
161
- this.peak = Math.max(this.peak, Number(numericData))
162
- // Update running sum and count
163
290
  const currentMassTotal = Math.max(-1000, Number(numericData))
291
+
292
+ // Update session stats before building packet
293
+ this.peak = Math.max(this.peak, Number(numericData))
294
+ this.min = Math.min(this.min, Math.max(-1000, Number(numericData)))
164
295
  this.sum += currentMassTotal
165
296
  this.dataPointCount++
166
-
167
- // Calculate the average dynamically
168
297
  this.mean = this.sum / this.dataPointCount
169
298
 
170
- // Check if device is being used
299
+ this.downloadPackets.push(
300
+ this.buildDownloadPacket(currentMassTotal, [weight], {
301
+ timestamp: receivedTime,
302
+ sampleIndex: timestampUs,
303
+ }),
304
+ )
171
305
  this.activityCheck(numericData)
172
306
 
173
- this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
307
+ // Hz from device timestamps: keep only samples in last 1s
308
+ this.recentSampleTimestamps.push(timestampUs)
309
+ const latestUs = this.recentSampleTimestamps[this.recentSampleTimestamps.length - 1] ?? 0
310
+ this.recentSampleTimestamps = this.recentSampleTimestamps.filter((ts) => latestUs - ts <= ONE_SECOND_US)
311
+ const samplingRateHz = this.recentSampleTimestamps.length
312
+
313
+ const payload = this.buildForceMeasurement(currentMassTotal)
314
+ if (payload.performance) payload.performance.samplingRateHz = samplingRateHz
315
+ this.notifyCallback(payload)
174
316
  }
175
317
  }
176
- } else if (kind === ProgressorResponses.COMMAND_RESPONSE) {
318
+ }
319
+ // Command response
320
+ else if (kind === ProgressorResponses.RESPONSE_COMMAND) {
177
321
  if (!this.writeLast) return
178
322
 
179
323
  let output = ""
324
+ const payload = new Uint8Array(value.buffer).slice(2)
180
325
 
181
- if (this.writeLast === this.commands.GET_BATT_VLTG) {
326
+ if (this.writeLast === this.commands.GET_BATTERY_VOLTAGE) {
182
327
  output = new DataView(value.buffer, 2).getUint32(0, true).toString()
183
- } else if (this.writeLast === this.commands.GET_FW_VERSION) {
184
- output = new TextDecoder().decode(new Uint8Array(value.buffer).slice(2))
185
- } else if (this.writeLast === this.commands.GET_ERR_INFO) {
186
- output = new TextDecoder().decode(new Uint8Array(value.buffer.slice(2)))
328
+ } else if (this.writeLast === this.commands.GET_FIRMWARE_VERSION) {
329
+ output = new TextDecoder().decode(payload)
330
+ } else if (this.writeLast === this.commands.GET_ERROR_INFORMATION) {
331
+ output = new TextDecoder().decode(payload)
332
+ } else if (this.writeLast === this.commands.GET_PROGRESSOR_ID) {
333
+ output = parseProgressorIdPayload(payload)
334
+ } else if (this.writeLast === this.commands.GET_CALIBRATION) {
335
+ output = parseCalibrationCurvePayload(payload)
336
+ } else {
337
+ // Unknown command response: return raw hex
338
+ output = toHex(payload)
187
339
  }
188
340
  this.writeCallback(output)
189
- } else if (kind === ProgressorResponses.LOW_BATTERY_WARNING) {
341
+ }
342
+ // RFD peak response
343
+ else if (kind === ProgressorResponses.RESPONSE_RFD_PEAK) {
344
+ console.warn("⚠️ RFD peak is currently unsupported.")
345
+ }
346
+ // RFD peak series response
347
+ else if (kind === ProgressorResponses.RESPONSE_RFD_PEAK_SERIES) {
348
+ console.warn("⚠️ RFD peak series is currently unsupported.")
349
+ }
350
+ // Low power warning response
351
+ else if (kind === ProgressorResponses.RESPONSE_LOW_PWR_WARNING) {
190
352
  console.warn("⚠️ Low power detected. Please consider connecting to a power source.")
191
353
  } else {
192
354
  throw new Error(`Unknown message kind detected: ${kind}`)
@@ -209,8 +371,15 @@ export class Progressor extends Device implements IProgressor {
209
371
  * @returns {Promise<void>} A promise that resolves when the streaming operation is completed.
210
372
  */
211
373
  stream = async (duration = 0): Promise<void> => {
212
- // Reset download packets
374
+ // Reset download packets and session stats for fresh measurement
213
375
  this.downloadPackets.length = 0
376
+ this.peak = Number.NEGATIVE_INFINITY
377
+ this.mean = 0
378
+ this.sum = 0
379
+ this.dataPointCount = 0
380
+ this.min = Number.POSITIVE_INFINITY
381
+ this.resetPacketTracking()
382
+ this.recentSampleTimestamps = []
214
383
  // Start streaming data
215
384
  await this.write("progressor", "tx", this.commands.START_WEIGHT_MEAS, duration)
216
385
  // Stop streaming if duration is set
@@ -60,7 +60,6 @@ export class SmartBoardPro extends Device implements ISmartBoardPro {
60
60
  */
61
61
  override handleNotifications = (value: DataView): void => {
62
62
  if (value) {
63
- // Update timestamp
64
63
  this.updateTimestamp()
65
64
  if (value.buffer) {
66
65
  const length = value.byteLength / 2
@@ -70,49 +69,45 @@ export class SmartBoardPro extends Device implements ISmartBoardPro {
70
69
  const offset = i * 2
71
70
  if (offset + 1 < value.byteLength) {
72
71
  const intValue = value.getInt16(offset, true)
73
- // For debugging purposes
74
72
  console.log(intValue)
75
-
76
73
  dataArray.push(intValue)
77
74
  }
78
75
  }
79
76
 
80
77
  if (dataArray.length === 0) return
81
78
 
79
+ this.currentSamplesPerPacket = dataArray.length
80
+ this.recordPacketReceived()
82
81
  const receivedTime = Date.now()
83
82
 
84
- // Process each data point
85
83
  for (const receivedData of dataArray) {
86
84
  // Skip invalid values
87
85
  if (!Number.isFinite(receivedData)) continue
88
86
 
89
87
  const numericData = receivedData - this.applyTare(receivedData)
90
88
 
91
- // Add data to downloadable Array
92
- this.downloadPackets.push({
93
- received: receivedTime,
94
- sampleNum: this.dataPointCount,
95
- battRaw: 0,
96
- samples: [numericData],
97
- masses: [numericData],
98
- })
89
+ const currentMassTotal = Math.max(-1000, numericData)
99
90
 
100
- // Update peak
91
+ // Update session stats before building packet
101
92
  this.peak = Math.max(this.peak, numericData)
102
-
103
- // Update running sum and count
104
- const currentMassTotal = Math.max(-1000, numericData)
93
+ this.min = Math.min(this.min, Math.max(-1000, numericData))
105
94
  this.sum += currentMassTotal
106
95
  this.dataPointCount++
107
-
108
- // Calculate the average dynamically
109
96
  this.mean = this.sum / this.dataPointCount
110
97
 
98
+ // Add data to downloadable Array
99
+ this.downloadPackets.push(
100
+ this.buildDownloadPacket(currentMassTotal, [numericData], {
101
+ timestamp: receivedTime,
102
+ sampleIndex: this.dataPointCount,
103
+ }),
104
+ )
105
+
111
106
  // Check if device is being used
112
107
  this.activityCheck(numericData)
113
108
 
114
109
  // Notify with weight data
115
- this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
110
+ this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
116
111
  }
117
112
  }
118
113
  }
@@ -99,7 +99,8 @@ export class WHC06 extends Device implements IWHC06 {
99
99
  this.bluetooth.addEventListener("advertisementreceived", (event) => {
100
100
  const data = event.manufacturerData.get(WHC06.manufacturerId)
101
101
  if (data) {
102
- // Handle recieved data
102
+ this.currentSamplesPerPacket = 1
103
+ this.recordPacketReceived()
103
104
  const weight = (data.getUint8(WHC06.weightOffset) << 8) | data.getUint8(WHC06.weightOffset + 1)
104
105
  // const stable = (data.getUint8(STABLE_OFFSET) & 0xf0) >> 4
105
106
  // const unit = data.getUint8(STABLE_OFFSET) & 0x0f
@@ -107,32 +108,28 @@ export class WHC06 extends Device implements IWHC06 {
107
108
  const receivedData = weight / 100
108
109
 
109
110
  const numericData = receivedData - this.applyTare(receivedData) * -1
111
+ const currentMassTotal = Math.max(-1000, numericData)
110
112
 
111
- // Add data to downloadable Array
112
- this.downloadPackets.push({
113
- received: receivedTime,
114
- sampleNum: this.dataPointCount,
115
- battRaw: 0,
116
- samples: [numericData],
117
- masses: [numericData],
118
- })
119
-
120
- // Update peak
113
+ // Update session stats before building packet
121
114
  this.peak = Math.max(this.peak, numericData)
122
-
123
- // Update running sum and count
124
- const currentMassTotal = Math.max(-1000, numericData)
115
+ this.min = Math.min(this.min, Math.max(-1000, numericData))
125
116
  this.sum += currentMassTotal
126
117
  this.dataPointCount++
127
-
128
- // Calculate the average dynamically
129
118
  this.mean = this.sum / this.dataPointCount
130
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
+
131
128
  // Check if device is being used
132
129
  this.activityCheck(numericData)
133
130
 
134
131
  // Notify with weight data
135
- this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
132
+ this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
136
133
  }
137
134
  // Reset "still advertising" counter
138
135
  this.resetAdvertisementTimeout()