@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.
- package/README.md +1 -1
- package/dist/cjs/index.d.ts +1 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interfaces/callback.interface.d.ts +19 -12
- package/dist/cjs/interfaces/callback.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/command.interface.d.ts +14 -6
- package/dist/cjs/interfaces/command.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/progressor.interface.d.ts +47 -0
- package/dist/cjs/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device.interface.d.ts +1 -1
- package/dist/cjs/interfaces/download.interface.d.ts +8 -11
- package/dist/cjs/interfaces/download.interface.d.ts.map +1 -1
- package/dist/cjs/models/device/climbro.model.d.ts.map +1 -1
- package/dist/cjs/models/device/climbro.model.js +34 -16
- package/dist/cjs/models/device/climbro.model.js.map +1 -1
- package/dist/cjs/models/device/entralpi.model.d.ts.map +1 -1
- package/dist/cjs/models/device/entralpi.model.js +11 -14
- package/dist/cjs/models/device/entralpi.model.js.map +1 -1
- package/dist/cjs/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/forceboard.model.js +12 -17
- package/dist/cjs/models/device/forceboard.model.js.map +1 -1
- package/dist/cjs/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/motherboard.model.js +28 -20
- package/dist/cjs/models/device/motherboard.model.js.map +1 -1
- package/dist/cjs/models/device/pb-700bt.model.d.ts.map +1 -1
- package/dist/cjs/models/device/pb-700bt.model.js +11 -14
- package/dist/cjs/models/device/pb-700bt.model.js.map +1 -1
- package/dist/cjs/models/device/progressor.model.d.ts +52 -5
- package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
- package/dist/cjs/models/device/progressor.model.js +211 -57
- package/dist/cjs/models/device/progressor.model.js.map +1 -1
- package/dist/cjs/models/device/smartboard-pro.model.d.ts.map +1 -1
- package/dist/cjs/models/device/smartboard-pro.model.js +11 -16
- package/dist/cjs/models/device/smartboard-pro.model.js.map +1 -1
- package/dist/cjs/models/device/wh-c06.model.d.ts.map +1 -1
- package/dist/cjs/models/device/wh-c06.model.js +11 -14
- package/dist/cjs/models/device/wh-c06.model.js.map +1 -1
- package/dist/cjs/models/device.model.d.ts +64 -3
- package/dist/cjs/models/device.model.d.ts.map +1 -1
- package/dist/cjs/models/device.model.js +142 -37
- package/dist/cjs/models/device.model.js.map +1 -1
- package/dist/cjs/utils.d.ts +1 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +29 -6
- package/dist/cjs/utils.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/callback.interface.d.ts +19 -12
- package/dist/interfaces/callback.interface.d.ts.map +1 -1
- package/dist/interfaces/command.interface.d.ts +14 -6
- package/dist/interfaces/command.interface.d.ts.map +1 -1
- package/dist/interfaces/device/progressor.interface.d.ts +47 -0
- package/dist/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/interfaces/device.interface.d.ts +1 -1
- package/dist/interfaces/download.interface.d.ts +8 -11
- package/dist/interfaces/download.interface.d.ts.map +1 -1
- package/dist/models/device/climbro.model.d.ts.map +1 -1
- package/dist/models/device/climbro.model.js +34 -16
- package/dist/models/device/climbro.model.js.map +1 -1
- package/dist/models/device/entralpi.model.d.ts.map +1 -1
- package/dist/models/device/entralpi.model.js +11 -14
- package/dist/models/device/entralpi.model.js.map +1 -1
- package/dist/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/models/device/forceboard.model.js +12 -17
- package/dist/models/device/forceboard.model.js.map +1 -1
- package/dist/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/models/device/motherboard.model.js +28 -20
- package/dist/models/device/motherboard.model.js.map +1 -1
- package/dist/models/device/pb-700bt.model.d.ts.map +1 -1
- package/dist/models/device/pb-700bt.model.js +11 -14
- package/dist/models/device/pb-700bt.model.js.map +1 -1
- package/dist/models/device/progressor.model.d.ts +52 -5
- package/dist/models/device/progressor.model.d.ts.map +1 -1
- package/dist/models/device/progressor.model.js +210 -57
- package/dist/models/device/progressor.model.js.map +1 -1
- package/dist/models/device/smartboard-pro.model.d.ts.map +1 -1
- package/dist/models/device/smartboard-pro.model.js +11 -16
- package/dist/models/device/smartboard-pro.model.js.map +1 -1
- package/dist/models/device/wh-c06.model.d.ts.map +1 -1
- package/dist/models/device/wh-c06.model.js +11 -14
- package/dist/models/device/wh-c06.model.js.map +1 -1
- package/dist/models/device.model.d.ts +64 -3
- package/dist/models/device.model.d.ts.map +1 -1
- package/dist/models/device.model.js +146 -37
- package/dist/models/device.model.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +29 -6
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +0 -3
- package/src/interfaces/callback.interface.ts +21 -12
- package/src/interfaces/command.interface.ts +16 -6
- package/src/interfaces/device/progressor.interface.ts +56 -0
- package/src/interfaces/device.interface.ts +1 -1
- package/src/interfaces/download.interface.ts +9 -11
- package/src/models/device/climbro.model.ts +38 -20
- package/src/models/device/entralpi.model.ts +15 -17
- package/src/models/device/forceboard.model.ts +15 -19
- package/src/models/device/motherboard.model.ts +38 -26
- package/src/models/device/pb-700bt.model.ts +14 -16
- package/src/models/device/progressor.model.ts +227 -58
- package/src/models/device/smartboard-pro.model.ts +14 -19
- package/src/models/device/wh-c06.model.ts +14 -17
- package/src/models/device.model.ts +181 -41
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", //
|
|
83
|
-
START_PEAK_RFD_MEAS_SERIES: "h", //
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
SLEEP: "n", // 0x6e
|
|
90
|
-
|
|
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.
|
|
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.
|
|
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 < 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.
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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.
|
|
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.
|
|
184
|
-
output = new TextDecoder().decode(
|
|
185
|
-
} else if (this.writeLast === this.commands.
|
|
186
|
-
output = new TextDecoder().decode(
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
132
|
+
this.notifyCallback(this.buildForceMeasurement(currentMassTotal))
|
|
136
133
|
}
|
|
137
134
|
// Reset "still advertising" counter
|
|
138
135
|
this.resetAdvertisementTimeout()
|