@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
@@ -164,13 +164,14 @@ export class PB700BT extends Device {
164
164
  */
165
165
  override handleNotifications = (value: DataView): void => {
166
166
  if (value) {
167
- // Update timestamp
168
167
  this.updateTimestamp()
169
168
  if (value.buffer) {
170
169
  const period = value.getUint32(0, false)
171
170
 
172
171
  if (!Number.isFinite(period) || period === 0) return
173
172
 
173
+ this.currentSamplesPerPacket = 1
174
+ this.recordPacketReceived()
174
175
  const ts = value.getUint32(4, false)
175
176
 
176
177
  const rpmFloat = 60 * (666666 / period)
@@ -183,31 +184,28 @@ export class PB700BT extends Device {
183
184
 
184
185
  const numericData = receivedData - this.applyTare(receivedData)
185
186
 
186
- // Add data to downloadable Array
187
- this.downloadPackets.push({
188
- received: receivedTime,
189
- sampleNum: ts,
190
- battRaw: 0,
191
- samples: [numericData],
192
- masses: [numericData],
193
- })
187
+ const currentMassTotal = Math.max(-1000, numericData)
194
188
 
195
- // Update peak
189
+ // Update session stats before building packet
196
190
  this.peak = Math.max(this.peak, numericData)
197
-
198
- // Update running sum and count
199
- const currentMassTotal = Math.max(-1000, numericData)
191
+ this.min = Math.min(this.min, Math.max(-1000, numericData))
200
192
  this.sum += currentMassTotal
201
193
  this.dataPointCount++
202
-
203
- // Calculate the average dynamically
204
194
  this.mean = this.sum / this.dataPointCount
205
195
 
196
+ // Add data to downloadable Array
197
+ this.downloadPackets.push(
198
+ this.buildDownloadPacket(currentMassTotal, [numericData], {
199
+ timestamp: receivedTime,
200
+ sampleIndex: ts,
201
+ }),
202
+ )
203
+
206
204
  // Check if device is being used
207
205
  this.activityCheck(numericData)
208
206
 
209
207
  // Notify with weight data
210
- this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
208
+ this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
211
209
  }
212
210
  }
213
211
  }
@@ -40,7 +40,51 @@ enum ProgressorResponses {
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,22 @@ 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
+ GET_CALIBRATION: "r", // 114 (0x72)
91
138
  },
92
139
  })
93
140
  }
@@ -98,24 +145,108 @@ export class Progressor extends Device implements IProgressor {
98
145
  */
99
146
  battery = async (): Promise<string | undefined> => {
100
147
  let response: string | undefined = undefined
101
- await this.write("progressor", "tx", this.commands.GET_BATT_VLTG, 250, (data) => {
148
+ await this.write("progressor", "tx", this.commands.GET_BATTERY_VOLTAGE, 250, (data) => {
102
149
  response = data
103
150
  })
104
151
  return response
105
152
  }
106
153
 
107
154
  /**
108
- * Retrieves firmware version from the device.
155
+ * Retrieves firmware version from the device (GetAppVersion, opcode 0x6B).
109
156
  * @returns {Promise<string>} A Promise that resolves with the firmware version,
110
157
  */
111
158
  firmware = async (): Promise<string | undefined> => {
112
159
  let response: string | undefined = undefined
113
- await this.write("progressor", "tx", this.commands.GET_FW_VERSION, 250, (data) => {
160
+ await this.write("progressor", "tx", this.commands.GET_FIRMWARE_VERSION, 250, (data) => {
114
161
  response = data
115
162
  })
116
163
  return response
117
164
  }
118
165
 
166
+ /**
167
+ * Retrieves the Progressor ID from the device (opcode 0x70).
168
+ * @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
169
+ */
170
+ progressorId = async (): Promise<string | undefined> => {
171
+ let response: string | undefined = undefined
172
+ await this.write("progressor", "tx", this.commands.GET_PROGRESSOR_ID, 250, (data) => {
173
+ response = data
174
+ })
175
+ return response
176
+ }
177
+
178
+ /**
179
+ * Retrieves calibration values from the device.
180
+ * @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
181
+ */
182
+ calibration = async (): Promise<string | undefined> => {
183
+ let response: string | undefined = undefined
184
+ await this.write("progressor", "tx", this.commands.GET_CALIBRATION, 250, (data) => {
185
+ response = data
186
+ })
187
+ return response
188
+ }
189
+
190
+ /**
191
+ * Saves the current calibration to the device. Call after adding both calibration points.
192
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
193
+ */
194
+ saveCalibration = async (): Promise<void> => {
195
+ await this.write("progressor", "tx", this.commands.SAVE_CALIBRATION, 0)
196
+ }
197
+
198
+ /**
199
+ * Adds a calibration point: 0x69 + 32-bit float (weight in kg), 5 bytes to the control characteristic.
200
+ * Use for two-point calibration: (1) zero weight — addCalibrationPoint(0); (2) known weight — addCalibrationPoint(knownKg).
201
+ * Order can be either way; writing zero first is recommended (hysteresis). Then call saveCalibration().
202
+ * @param {number} weightKg - Weight in kg (use 0 for zero point, or known reference weight &lt; 150 kg).
203
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
204
+ */
205
+ addCalibrationPoint = async (weightKg: number): Promise<void> => {
206
+ const payload = new Uint8Array(5)
207
+ payload[0] = (this.commands.ADD_CALIBRATION_POINT as string).charCodeAt(0) // 0x69
208
+ new DataView(payload.buffer).setFloat32(1, weightKg, true)
209
+ await this.write("progressor", "tx", payload, 0)
210
+ }
211
+
212
+ /**
213
+ * Sends the device tare command to zero the scale (hardware tare).
214
+ * For software tare over a duration, use the base tare(duration) method.
215
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
216
+ */
217
+ tareScale = async (): Promise<void> => {
218
+ await this.write("progressor", "tx", this.commands.TARE_SCALE, 0)
219
+ }
220
+
221
+ /**
222
+ * Puts the device to sleep / shutdown.
223
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
224
+ */
225
+ sleep = async (): Promise<void> => {
226
+ const cmd = this.commands.SLEEP
227
+ await this.write("progressor", "tx", typeof cmd === "string" ? cmd : String(cmd), 0)
228
+ }
229
+
230
+ /**
231
+ * Retrieves error information from the device.
232
+ * @returns {Promise<string | undefined>} A Promise that resolves with the error info text.
233
+ */
234
+ errorInfo = async (): Promise<string | undefined> => {
235
+ let response: string | undefined = undefined
236
+ await this.write("progressor", "tx", this.commands.GET_ERROR_INFORMATION, 250, (data) => {
237
+ response = data
238
+ })
239
+ return response
240
+ }
241
+
242
+ /**
243
+ * Clears error information on the device.
244
+ * @returns {Promise<void>} A Promise that resolves when the command is sent.
245
+ */
246
+ clearErrorInfo = async (): Promise<void> => {
247
+ await this.write("progressor", "tx", this.commands.CLR_ERROR_INFORMATION, 0)
248
+ }
249
+
119
250
  /**
120
251
  * Handles data received from the device, processes weight measurements,
121
252
  * and updates mass data including maximum and average values.
@@ -133,57 +264,67 @@ export class Progressor extends Device implements IProgressor {
133
264
  const kind = value.getInt8(0)
134
265
  // Check if the message is a weight measurement
135
266
  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
267
+ // Payload = bytes from index 2 (skip byte 0 = message type, byte 1 = reserved/length)
268
+ const payloadLength = value.byteLength - 2
269
+ if (payloadLength % 8 !== 0) return
270
+ const samplesPerPacket = payloadLength / 8
271
+ this.currentSamplesPerPacket = samplesPerPacket
272
+ this.recordPacketReceived()
273
+
274
+ // for (i = 0; i < samplesPerPacket; i++) { offset = i*8; weight = getFloat32(offset); timestampUs = getUint32(offset+4); }
275
+ for (let i = 0; i < samplesPerPacket; i++) {
276
+ const offset = 2 + i * 8
141
277
  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
278
+ const timestampUs = value.getUint32(offset + 4, true)
279
+ if (!isNaN(weight)) {
151
280
  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
281
  const currentMassTotal = Math.max(-1000, Number(numericData))
282
+
283
+ // Update session stats before building packet
284
+ this.peak = Math.max(this.peak, Number(numericData))
285
+ this.min = Math.min(this.min, Math.max(-1000, Number(numericData)))
164
286
  this.sum += currentMassTotal
165
287
  this.dataPointCount++
166
-
167
- // Calculate the average dynamically
168
288
  this.mean = this.sum / this.dataPointCount
169
289
 
170
- // Check if device is being used
290
+ this.downloadPackets.push(
291
+ this.buildDownloadPacket(currentMassTotal, [weight], {
292
+ timestamp: receivedTime,
293
+ sampleIndex: timestampUs,
294
+ }),
295
+ )
171
296
  this.activityCheck(numericData)
172
297
 
173
- this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
298
+ // Hz from device timestamps: keep only samples in last 1s
299
+ this.recentSampleTimestamps.push(timestampUs)
300
+ const latestUs = this.recentSampleTimestamps[this.recentSampleTimestamps.length - 1] ?? 0
301
+ this.recentSampleTimestamps = this.recentSampleTimestamps.filter((ts) => latestUs - ts <= ONE_SECOND_US)
302
+ const samplingRateHz = this.recentSampleTimestamps.length
303
+
304
+ const payload = this.buildForceMeasurement(currentMassTotal)
305
+ if (payload.performance) payload.performance.samplingRateHz = samplingRateHz
306
+ this.notifyCallback(payload)
174
307
  }
175
308
  }
176
309
  } else if (kind === ProgressorResponses.COMMAND_RESPONSE) {
177
310
  if (!this.writeLast) return
178
311
 
179
312
  let output = ""
313
+ const payload = new Uint8Array(value.buffer).slice(2)
180
314
 
181
- if (this.writeLast === this.commands.GET_BATT_VLTG) {
315
+ if (this.writeLast === this.commands.GET_BATTERY_VOLTAGE) {
182
316
  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)))
317
+ } else if (this.writeLast === this.commands.GET_FIRMWARE_VERSION) {
318
+ output = new TextDecoder().decode(payload)
319
+ } else if (this.writeLast === this.commands.GET_ERROR_INFORMATION) {
320
+ output = new TextDecoder().decode(payload)
321
+ } else if (this.writeLast === this.commands.GET_PROGRESSOR_ID) {
322
+ output = parseProgressorIdPayload(payload)
323
+ } else if (this.writeLast === this.commands.GET_CALIBRATION) {
324
+ output = parseCalibrationCurvePayload(payload)
325
+ } else {
326
+ // Unknown command response: return raw hex
327
+ output = toHex(payload)
187
328
  }
188
329
  this.writeCallback(output)
189
330
  } else if (kind === ProgressorResponses.LOW_BATTERY_WARNING) {
@@ -209,8 +350,15 @@ export class Progressor extends Device implements IProgressor {
209
350
  * @returns {Promise<void>} A promise that resolves when the streaming operation is completed.
210
351
  */
211
352
  stream = async (duration = 0): Promise<void> => {
212
- // Reset download packets
353
+ // Reset download packets and session stats for fresh measurement
213
354
  this.downloadPackets.length = 0
355
+ this.peak = Number.NEGATIVE_INFINITY
356
+ this.mean = 0
357
+ this.sum = 0
358
+ this.dataPointCount = 0
359
+ this.min = Number.POSITIVE_INFINITY
360
+ this.resetPacketTracking()
361
+ this.recentSampleTimestamps = []
214
362
  // Start streaming data
215
363
  await this.write("progressor", "tx", this.commands.START_WEIGHT_MEAS, duration)
216
364
  // 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()