@hangtime/grip-connect 0.8.7 → 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.
- package/README.md +15 -9
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interfaces/callback.interface.d.ts +37 -31
- package/dist/cjs/interfaces/callback.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/climbro.interface.d.ts +5 -0
- package/dist/cjs/interfaces/device/climbro.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/kilterboard.interface.d.ts +3 -2
- package/dist/cjs/interfaces/device/kilterboard.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device.interface.d.ts +2 -2
- package/dist/cjs/interfaces/device.interface.d.ts.map +1 -1
- package/dist/cjs/models/device/climbro.model.d.ts +32 -1
- package/dist/cjs/models/device/climbro.model.d.ts.map +1 -1
- package/dist/cjs/models/device/climbro.model.js +129 -3
- 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 +5 -9
- 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 +5 -9
- package/dist/cjs/models/device/forceboard.model.js.map +1 -1
- package/dist/cjs/models/device/kilterboard.model.d.ts +5 -3
- package/dist/cjs/models/device/kilterboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/kilterboard.model.js +52 -16
- package/dist/cjs/models/device/kilterboard.model.js.map +1 -1
- package/dist/cjs/models/device/motherboard.model.d.ts +7 -0
- package/dist/cjs/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/motherboard.model.js +28 -15
- package/dist/cjs/models/device/motherboard.model.js.map +1 -1
- package/dist/cjs/models/device/pb-700bt.model.d.ts +63 -0
- package/dist/cjs/models/device/pb-700bt.model.d.ts.map +1 -0
- package/dist/cjs/models/device/pb-700bt.model.js +247 -0
- package/dist/cjs/models/device/pb-700bt.model.js.map +1 -0
- package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
- package/dist/cjs/models/device/progressor.model.js +4 -8
- package/dist/cjs/models/device/progressor.model.js.map +1 -1
- package/dist/cjs/models/device/smartboard-pro.model.d.ts +8 -0
- package/dist/cjs/models/device/smartboard-pro.model.d.ts.map +1 -1
- package/dist/cjs/models/device/smartboard-pro.model.js +62 -6
- 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 +5 -9
- package/dist/cjs/models/device/wh-c06.model.js.map +1 -1
- package/dist/cjs/models/device.model.d.ts +72 -12
- package/dist/cjs/models/device.model.d.ts.map +1 -1
- package/dist/cjs/models/device.model.js +127 -5
- package/dist/cjs/models/device.model.js.map +1 -1
- package/dist/cjs/models/index.d.ts +2 -1
- package/dist/cjs/models/index.d.ts.map +1 -1
- package/dist/cjs/models/index.js +4 -1
- package/dist/cjs/models/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/callback.interface.d.ts +37 -31
- package/dist/interfaces/callback.interface.d.ts.map +1 -1
- package/dist/interfaces/device/climbro.interface.d.ts +5 -0
- package/dist/interfaces/device/climbro.interface.d.ts.map +1 -1
- package/dist/interfaces/device/kilterboard.interface.d.ts +3 -2
- package/dist/interfaces/device/kilterboard.interface.d.ts.map +1 -1
- package/dist/interfaces/device.interface.d.ts +2 -2
- package/dist/interfaces/device.interface.d.ts.map +1 -1
- package/dist/models/device/climbro.model.d.ts +32 -1
- package/dist/models/device/climbro.model.d.ts.map +1 -1
- package/dist/models/device/climbro.model.js +127 -3
- 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 +5 -9
- 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 +5 -9
- package/dist/models/device/forceboard.model.js.map +1 -1
- package/dist/models/device/kilterboard.model.d.ts +5 -3
- package/dist/models/device/kilterboard.model.d.ts.map +1 -1
- package/dist/models/device/kilterboard.model.js +52 -16
- package/dist/models/device/kilterboard.model.js.map +1 -1
- package/dist/models/device/motherboard.model.d.ts +7 -0
- package/dist/models/device/motherboard.model.d.ts.map +1 -1
- package/dist/models/device/motherboard.model.js +28 -15
- package/dist/models/device/motherboard.model.js.map +1 -1
- package/dist/models/device/pb-700bt.model.d.ts +63 -0
- package/dist/models/device/pb-700bt.model.d.ts.map +1 -0
- package/dist/models/device/pb-700bt.model.js +243 -0
- package/dist/models/device/pb-700bt.model.js.map +1 -0
- package/dist/models/device/progressor.model.d.ts.map +1 -1
- package/dist/models/device/progressor.model.js +4 -8
- package/dist/models/device/progressor.model.js.map +1 -1
- package/dist/models/device/smartboard-pro.model.d.ts +8 -0
- package/dist/models/device/smartboard-pro.model.d.ts.map +1 -1
- package/dist/models/device/smartboard-pro.model.js +62 -6
- 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 +5 -9
- package/dist/models/device/wh-c06.model.js.map +1 -1
- package/dist/models/device.model.d.ts +72 -12
- package/dist/models/device.model.d.ts.map +1 -1
- package/dist/models/device.model.js +149 -14
- package/dist/models/device.model.js.map +1 -1
- package/dist/models/index.d.ts +2 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/package.json +4 -1
- package/src/index.ts +11 -0
- package/src/interfaces/callback.interface.ts +41 -31
- package/src/interfaces/device/climbro.interface.ts +8 -2
- package/src/interfaces/device/kilterboard.interface.ts +2 -2
- package/src/interfaces/device.interface.ts +2 -2
- package/src/models/device/climbro.model.ts +146 -3
- package/src/models/device/entralpi.model.ts +5 -9
- package/src/models/device/forceboard.model.ts +5 -9
- package/src/models/device/kilterboard.model.ts +56 -19
- package/src/models/device/motherboard.model.ts +32 -15
- package/src/models/device/pb-700bt.model.ts +263 -0
- package/src/models/device/progressor.model.ts +4 -8
- package/src/models/device/smartboard-pro.model.ts +73 -6
- package/src/models/device/wh-c06.model.ts +5 -9
- package/src/models/device.model.ts +179 -16
- package/src/models/index.ts +3 -1
|
@@ -185,22 +185,23 @@ export class KilterBoard extends Device implements IKilterBoard {
|
|
|
185
185
|
/**
|
|
186
186
|
* Encodes a color string into a numeric representation.
|
|
187
187
|
* The rgb color, 3 bits for the R and G components, 2 bits for the B component, with the 3 R bits occupying the high end of the byte and the 2 B bits in the low end (hence 3 G bits in the middle).
|
|
188
|
+
* Format: 0bRRRGGGBB where RRR is 3 bits for red, GGG is 3 bits for green, BB is 2 bits for blue.
|
|
188
189
|
* @param color - The color string in hexadecimal format (e.g., 'FFFFFF').
|
|
189
190
|
* @returns The encoded /compressed color value.
|
|
190
191
|
*/
|
|
191
192
|
private encodeColor(color: string): number {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
193
|
+
const r = parseInt(color.substring(0, 2), 16)
|
|
194
|
+
const g = parseInt(color.substring(2, 4), 16)
|
|
195
|
+
const b = parseInt(color.substring(4, 6), 16)
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
197
|
+
// Integer division: R and G divided by 32, B divided by 64
|
|
198
|
+
// Then pack into 0bRRRGGGBB format
|
|
199
|
+
const rBits = Math.floor(r / 32) // 0-7 (3 bits)
|
|
200
|
+
const gBits = Math.floor(g / 32) // 0-7 (3 bits)
|
|
201
|
+
const bBits = Math.floor(b / 64) // 0-3 (2 bits)
|
|
198
202
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const finalParsedResult = parsedResult | parsedSubstring3
|
|
202
|
-
|
|
203
|
-
return finalParsedResult
|
|
203
|
+
// Pack: RRR in bits 7-5, GGG in bits 4-2, BB in bits 1-0
|
|
204
|
+
return (rBits << 5) | (gBits << 2) | bBits
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
/**
|
|
@@ -215,23 +216,59 @@ export class KilterBoard extends Device implements IKilterBoard {
|
|
|
215
216
|
|
|
216
217
|
/**
|
|
217
218
|
* Prepares byte arrays for transmission based on a list of climb placements.
|
|
218
|
-
* @param {{ position: number; role_id
|
|
219
|
+
* @param {{ position: number; role_id?: number; color?: string }[]} climbPlacementList - The list of climb placements containing position and either role ID or color.
|
|
219
220
|
* @returns {number[]} The final byte array ready for transmission.
|
|
220
221
|
*/
|
|
221
|
-
private prepBytesV3(climbPlacementList: { position: number; role_id
|
|
222
|
+
private prepBytesV3(climbPlacementList: { position: number; role_id?: number; color?: string }[]): number[] {
|
|
222
223
|
const resultArray: number[][] = []
|
|
223
224
|
let tempArray: number[] = [KilterBoardPacket.V3_MIDDLE]
|
|
224
225
|
|
|
225
|
-
|
|
226
|
+
// Filter out any invalid placements and clean up objects before processing
|
|
227
|
+
const validPlacements = climbPlacementList
|
|
228
|
+
.filter((p) => {
|
|
229
|
+
// Explicitly check for color
|
|
230
|
+
const hasColor = p.color != null && typeof p.color === "string" && p.color.trim() !== ""
|
|
231
|
+
// Explicitly check for role_id - must be a number, not undefined or null
|
|
232
|
+
const hasRoleId = typeof p.role_id === "number" && !isNaN(p.role_id)
|
|
233
|
+
|
|
234
|
+
return hasColor || hasRoleId
|
|
235
|
+
})
|
|
236
|
+
.map((p) => {
|
|
237
|
+
// Create clean objects without undefined properties
|
|
238
|
+
if (p.color != null && typeof p.color === "string" && p.color.trim() !== "") {
|
|
239
|
+
return { position: p.position, color: p.color.trim() } as { position: number; color: string }
|
|
240
|
+
} else if (typeof p.role_id === "number" && !isNaN(p.role_id)) {
|
|
241
|
+
return { position: p.position, role_id: p.role_id } as { position: number; role_id: number }
|
|
242
|
+
}
|
|
243
|
+
// This should never happen due to the filter above, but TypeScript needs this
|
|
244
|
+
throw new Error("Invalid placement after filter")
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
for (const climbPlacement of validPlacements) {
|
|
226
248
|
if (tempArray.length + 3 > KilterBoard.messageBodyMaxLength) {
|
|
227
249
|
resultArray.push(tempArray)
|
|
228
250
|
tempArray = [KilterBoardPacket.V3_MIDDLE]
|
|
229
251
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
|
|
253
|
+
let colorHex: string
|
|
254
|
+
|
|
255
|
+
// Type guard: check if this placement has a color property
|
|
256
|
+
if ("color" in climbPlacement && climbPlacement.color) {
|
|
257
|
+
// Use direct color if provided
|
|
258
|
+
colorHex = climbPlacement.color.replace("#", "").toUpperCase()
|
|
259
|
+
} else if ("role_id" in climbPlacement && typeof climbPlacement.role_id === "number") {
|
|
260
|
+
// Fall back to role_id if no color provided
|
|
261
|
+
const role = KilterBoardPlacementRoles.find((placement) => placement.id === climbPlacement.role_id)
|
|
262
|
+
if (!role) {
|
|
263
|
+
throw new Error(`Role with id ${climbPlacement.role_id} not found in placement_roles`)
|
|
264
|
+
}
|
|
265
|
+
colorHex = role.led_color
|
|
266
|
+
} else {
|
|
267
|
+
// This should never happen due to the filter above, but just in case
|
|
268
|
+
throw new Error(`Either role_id or color must be provided for position ${climbPlacement.position}`)
|
|
233
269
|
}
|
|
234
|
-
|
|
270
|
+
|
|
271
|
+
const encodedPlacement = this.encodePlacement(climbPlacement.position, colorHex)
|
|
235
272
|
tempArray.push(...encodedPlacement)
|
|
236
273
|
}
|
|
237
274
|
|
|
@@ -292,10 +329,10 @@ export class KilterBoard extends Device implements IKilterBoard {
|
|
|
292
329
|
|
|
293
330
|
/**
|
|
294
331
|
* Configures the LEDs based on an array of climb placements.
|
|
295
|
-
* @param {{ position: number; role_id
|
|
332
|
+
* @param {{ position: number; role_id?: number; color?: string }[]} config - Array of climb placements for the LEDs. Either role_id or color (hex string) must be provided.
|
|
296
333
|
* @returns {Promise<number[] | undefined>} A promise that resolves with the payload array for the Kilter Board if LED settings were applied, or `undefined` if no action was taken or for the Motherboard.
|
|
297
334
|
*/
|
|
298
|
-
led = async (config: { position: number; role_id
|
|
335
|
+
led = async (config: { position: number; role_id?: number; color?: string }[]): Promise<number[] | undefined> => {
|
|
299
336
|
// Handle Kilterboard logic: process placements and send payload if connected
|
|
300
337
|
if (Array.isArray(config)) {
|
|
301
338
|
// Prepares byte arrays for transmission based on a list of climb placements.
|
|
@@ -39,6 +39,14 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
39
39
|
*/
|
|
40
40
|
private calibrationData: number[][][] = [[], [], [], []]
|
|
41
41
|
|
|
42
|
+
/** Per-zone peak and running sum for left/center/right (used for distribution stats). */
|
|
43
|
+
private leftPeak = Number.NEGATIVE_INFINITY
|
|
44
|
+
private leftSum = 0
|
|
45
|
+
private centerPeak = Number.NEGATIVE_INFINITY
|
|
46
|
+
private centerSum = 0
|
|
47
|
+
private rightPeak = Number.NEGATIVE_INFINITY
|
|
48
|
+
private rightSum = 0
|
|
49
|
+
|
|
42
50
|
constructor() {
|
|
43
51
|
super({
|
|
44
52
|
filters: [{ name: "Motherboard" }],
|
|
@@ -281,28 +289,37 @@ export class Motherboard extends Device implements IMotherboard {
|
|
|
281
289
|
center -= this.applyTare(center)
|
|
282
290
|
right -= this.applyTare(right)
|
|
283
291
|
|
|
284
|
-
|
|
292
|
+
const totalCurrent = Math.max(-1000, left + center + right)
|
|
293
|
+
const leftClamped = Math.max(-1000, left)
|
|
294
|
+
const centerClamped = Math.max(-1000, center)
|
|
295
|
+
const rightClamped = Math.max(-1000, right)
|
|
296
|
+
|
|
297
|
+
this.peak = Math.max(this.peak, totalCurrent)
|
|
285
298
|
|
|
286
|
-
// Update running sum and count
|
|
287
|
-
|
|
288
|
-
this.massTotalSum += currentMassTotal
|
|
299
|
+
// Update running sum and count (total)
|
|
300
|
+
this.sum += totalCurrent
|
|
289
301
|
this.dataPointCount++
|
|
302
|
+
this.mean = this.sum / this.dataPointCount
|
|
290
303
|
|
|
291
|
-
//
|
|
292
|
-
this.
|
|
304
|
+
// Per-zone peak and sum for distribution
|
|
305
|
+
this.leftPeak = Math.max(this.leftPeak, leftClamped)
|
|
306
|
+
this.leftSum += leftClamped
|
|
307
|
+
this.centerPeak = Math.max(this.centerPeak, centerClamped)
|
|
308
|
+
this.centerSum += centerClamped
|
|
309
|
+
this.rightPeak = Math.max(this.rightPeak, rightClamped)
|
|
310
|
+
this.rightSum += rightClamped
|
|
293
311
|
|
|
294
312
|
// Check if device is being used
|
|
295
313
|
this.activityCheck(center)
|
|
296
314
|
|
|
297
|
-
// Notify with weight data
|
|
298
|
-
this.notifyCallback(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
})
|
|
315
|
+
// Notify with weight data (distribution zones have proper peak/mean per zone)
|
|
316
|
+
this.notifyCallback(
|
|
317
|
+
this.buildForceMeasurement(totalCurrent, {
|
|
318
|
+
left: this.buildZoneMeasurement(leftClamped, this.leftPeak, this.leftSum / this.dataPointCount),
|
|
319
|
+
center: this.buildZoneMeasurement(centerClamped, this.centerPeak, this.centerSum / this.dataPointCount),
|
|
320
|
+
right: this.buildZoneMeasurement(rightClamped, this.rightPeak, this.rightSum / this.dataPointCount),
|
|
321
|
+
}),
|
|
322
|
+
)
|
|
306
323
|
} else if (this.writeLast === this.commands.GET_CALIBRATION) {
|
|
307
324
|
// check data integrity
|
|
308
325
|
if ((receivedData.match(/,/g) || []).length === 3) {
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { Device } from "../device.model.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a NSD PB-700BT device.
|
|
5
|
+
* {@link https://www.nsd.com.tw/}
|
|
6
|
+
*/
|
|
7
|
+
export class PB700BT extends Device {
|
|
8
|
+
constructor() {
|
|
9
|
+
super({
|
|
10
|
+
filters: [{ name: "NSD Workout" }],
|
|
11
|
+
services: [
|
|
12
|
+
{
|
|
13
|
+
name: "Battery Service",
|
|
14
|
+
id: "battery",
|
|
15
|
+
uuid: "0000180f-0000-1000-8000-00805f9b34fb",
|
|
16
|
+
characteristics: [
|
|
17
|
+
{
|
|
18
|
+
name: "Battery Level",
|
|
19
|
+
id: "level",
|
|
20
|
+
uuid: "00002a19-0000-1000-8000-00805f9b34fb", // 100
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "Custom Service", // Unknown custom service
|
|
26
|
+
id: "custom",
|
|
27
|
+
uuid: "0000feba-0000-1000-8000-00805f9b34fb",
|
|
28
|
+
characteristics: [
|
|
29
|
+
{
|
|
30
|
+
name: "Custom Characteristic 1", // Unknown characteristic
|
|
31
|
+
id: "custom1",
|
|
32
|
+
uuid: "0000fa10-0000-1000-8000-00805f9b34fb",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "Custom Characteristic 2", // Unknown characteristic
|
|
36
|
+
id: "custom2",
|
|
37
|
+
uuid: "0000fa11-0000-1000-8000-00805f9b34fb",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "Custom Characteristic 3", // Unknown characteristic
|
|
41
|
+
id: "custom3",
|
|
42
|
+
uuid: "0000fa13-0000-1000-8000-00805f9b34fb",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "UART ISSC Transparent Service",
|
|
48
|
+
id: "uart",
|
|
49
|
+
uuid: "0000fff0-0000-1000-8000-00805f9b34fb",
|
|
50
|
+
characteristics: [
|
|
51
|
+
{
|
|
52
|
+
name: "TX",
|
|
53
|
+
id: "tx",
|
|
54
|
+
uuid: "0000fff1-0000-1000-8000-00805f9b34fb",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "Unknown UART Characteristic", // Unknown UART characteristic
|
|
58
|
+
id: "unknown1",
|
|
59
|
+
uuid: "0000fff3-0000-1000-8000-00805f9b34fb",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "RX",
|
|
63
|
+
id: "rx",
|
|
64
|
+
uuid: "0000fff4-0000-1000-8000-00805f9b34fb",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "Unknown UART Characteristic 2", // Unknown UART characteristic
|
|
68
|
+
id: "unknown2",
|
|
69
|
+
uuid: "0000fff6-0000-1000-8000-00805f9b34fb",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "Unknown UART Characteristic 3", // Unknown UART characteristic
|
|
73
|
+
id: "unknown3",
|
|
74
|
+
uuid: "0000fff7-0000-1000-8000-00805f9b34fb",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Unknown UART Characteristic 4", // Unknown UART characteristic
|
|
78
|
+
id: "unknown4",
|
|
79
|
+
uuid: "0000fff8-0000-1000-8000-00805f9b34fb",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "Device Information",
|
|
85
|
+
id: "device",
|
|
86
|
+
uuid: "0000180a-0000-1000-8000-00805f9b34fb",
|
|
87
|
+
characteristics: [
|
|
88
|
+
{
|
|
89
|
+
name: "System ID",
|
|
90
|
+
id: "system",
|
|
91
|
+
uuid: "00002a23-0000-1000-8000-00805f9b34fb",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "Model Number String",
|
|
95
|
+
id: "model",
|
|
96
|
+
uuid: "00002a24-0000-1000-8000-00805f9b34fb", // MD8107
|
|
97
|
+
},
|
|
98
|
+
// {
|
|
99
|
+
// name: "Serial Number String (Blocked)",
|
|
100
|
+
// id: "serial",
|
|
101
|
+
// uuid: "00002a25-0000-1000-8000-00805f9b34fb",
|
|
102
|
+
// },
|
|
103
|
+
{
|
|
104
|
+
name: "Firmware Revision String",
|
|
105
|
+
id: "firmware",
|
|
106
|
+
uuid: "00002a26-0000-1000-8000-00805f9b34fb", // 0.7
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "Hardware Revision String",
|
|
110
|
+
id: "hardware",
|
|
111
|
+
uuid: "00002a27-0000-1000-8000-00805f9b34fb",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "Software Revision String",
|
|
115
|
+
id: "software",
|
|
116
|
+
uuid: "00002a28-0000-1000-8000-00805f9b34fb",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "Manufacturer Name String",
|
|
120
|
+
id: "manufacturer",
|
|
121
|
+
uuid: "00002a29-0000-1000-8000-00805f9b34fb", // AMICCOM Elec.
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "PnP ID",
|
|
125
|
+
id: "pnp",
|
|
126
|
+
uuid: "00002a50-0000-1000-8000-00805f9b34fb",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves battery or voltage information from the device.
|
|
136
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the battery or voltage information.
|
|
137
|
+
*/
|
|
138
|
+
battery = async (): Promise<string | undefined> => {
|
|
139
|
+
return await this.read("battery", "level", 250)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Retrieves IEEE 11073-20601 Regulatory Certification from the device.
|
|
144
|
+
* @returns {Promise<string>} A Promise that resolves with the certification.
|
|
145
|
+
*/
|
|
146
|
+
certification = async (): Promise<string | undefined> => {
|
|
147
|
+
return await this.read("device", "certification", 250)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Retrieves firmware version from the device.
|
|
152
|
+
* @returns {Promise<string>} A Promise that resolves with the firmware version.
|
|
153
|
+
*/
|
|
154
|
+
firmware = async (): Promise<string | undefined> => {
|
|
155
|
+
return await this.read("device", "firmware", 250)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handles data received from the device, processes weight measurements,
|
|
160
|
+
* and updates mass data including maximum and average values.
|
|
161
|
+
* It also handles command responses for retrieving device information.
|
|
162
|
+
*
|
|
163
|
+
* @param {DataView} value - The notification event.
|
|
164
|
+
*/
|
|
165
|
+
override handleNotifications = (value: DataView): void => {
|
|
166
|
+
if (value) {
|
|
167
|
+
// Update timestamp
|
|
168
|
+
this.updateTimestamp()
|
|
169
|
+
if (value.buffer) {
|
|
170
|
+
const period = value.getUint32(0, false)
|
|
171
|
+
|
|
172
|
+
if (!Number.isFinite(period) || period === 0) return
|
|
173
|
+
|
|
174
|
+
const ts = value.getUint32(4, false)
|
|
175
|
+
|
|
176
|
+
const rpmFloat = 60 * (666666 / period)
|
|
177
|
+
|
|
178
|
+
// Accept only RPMs in plausible range (~800–15000)
|
|
179
|
+
if (!Number.isFinite(rpmFloat) || rpmFloat < 800 || rpmFloat > 15000) return
|
|
180
|
+
|
|
181
|
+
const receivedData = Math.round(rpmFloat)
|
|
182
|
+
const receivedTime = Date.now()
|
|
183
|
+
|
|
184
|
+
const numericData = receivedData - this.applyTare(receivedData)
|
|
185
|
+
|
|
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
|
+
})
|
|
194
|
+
|
|
195
|
+
// Update peak
|
|
196
|
+
this.peak = Math.max(this.peak, numericData)
|
|
197
|
+
|
|
198
|
+
// Update running sum and count
|
|
199
|
+
const currentMassTotal = Math.max(-1000, numericData)
|
|
200
|
+
this.sum += currentMassTotal
|
|
201
|
+
this.dataPointCount++
|
|
202
|
+
|
|
203
|
+
// Calculate the average dynamically
|
|
204
|
+
this.mean = this.sum / this.dataPointCount
|
|
205
|
+
|
|
206
|
+
// Check if device is being used
|
|
207
|
+
this.activityCheck(numericData)
|
|
208
|
+
|
|
209
|
+
// Notify with weight data
|
|
210
|
+
this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Retrieves hardware version from the device.
|
|
217
|
+
* @returns {Promise<string>} A Promise that resolves with the hardware version.
|
|
218
|
+
*/
|
|
219
|
+
hardware = async (): Promise<string | undefined> => {
|
|
220
|
+
return await this.read("device", "hardware", 250)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Retrieves manufacturer information from the device.
|
|
225
|
+
* @returns {Promise<string>} A Promise that resolves with the manufacturer information.
|
|
226
|
+
*/
|
|
227
|
+
manufacturer = async (): Promise<string | undefined> => {
|
|
228
|
+
return await this.read("device", "manufacturer", 250)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Retrieves model number from the device.
|
|
233
|
+
* @returns {Promise<string>} A Promise that resolves with the model number.
|
|
234
|
+
*/
|
|
235
|
+
model = async (): Promise<string | undefined> => {
|
|
236
|
+
return await this.read("device", "model", 250)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Retrieves PnP ID from the device, a set of values that used to create a device ID value that is unique for this device.
|
|
241
|
+
* Included in the characteristic is a Vendor ID Source field, a Vendor ID field, a Product ID field and a Product Version field
|
|
242
|
+
* @returns {Promise<string>} A Promise that resolves with the PnP ID.
|
|
243
|
+
*/
|
|
244
|
+
pnp = async (): Promise<string | undefined> => {
|
|
245
|
+
return await this.read("device", "pnp", 250)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Retrieves software version from the device.
|
|
250
|
+
* @returns {Promise<string>} A Promise that resolves with the software version.
|
|
251
|
+
*/
|
|
252
|
+
software = async (): Promise<string | undefined> => {
|
|
253
|
+
return await this.read("device", "software", 250)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Retrieves system id from the device.
|
|
258
|
+
* @returns {Promise<string>} A Promise that resolves with the system id.
|
|
259
|
+
*/
|
|
260
|
+
system = async (): Promise<string | undefined> => {
|
|
261
|
+
return await this.read("device", "system", 250)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -158,23 +158,19 @@ export class Progressor extends Device implements IProgressor {
|
|
|
158
158
|
masses: [numericData],
|
|
159
159
|
})
|
|
160
160
|
// Check for max weight
|
|
161
|
-
this.
|
|
161
|
+
this.peak = Math.max(this.peak, Number(numericData))
|
|
162
162
|
// Update running sum and count
|
|
163
163
|
const currentMassTotal = Math.max(-1000, Number(numericData))
|
|
164
|
-
this.
|
|
164
|
+
this.sum += currentMassTotal
|
|
165
165
|
this.dataPointCount++
|
|
166
166
|
|
|
167
167
|
// Calculate the average dynamically
|
|
168
|
-
this.
|
|
168
|
+
this.mean = this.sum / this.dataPointCount
|
|
169
169
|
|
|
170
170
|
// Check if device is being used
|
|
171
171
|
this.activityCheck(numericData)
|
|
172
172
|
|
|
173
|
-
this.notifyCallback(
|
|
174
|
-
massMax: this.massMax,
|
|
175
|
-
massAverage: this.massAverage,
|
|
176
|
-
massTotal: Math.max(-1000, numericData).toFixed(1),
|
|
177
|
-
})
|
|
173
|
+
this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
|
|
178
174
|
}
|
|
179
175
|
}
|
|
180
176
|
} else if (kind === ProgressorResponses.COMMAND_RESPONSE) {
|
|
@@ -12,8 +12,8 @@ export class SmartBoardPro extends Device implements ISmartBoardPro {
|
|
|
12
12
|
filters: [{ name: "SMARTBOARD" }],
|
|
13
13
|
services: [
|
|
14
14
|
{
|
|
15
|
-
name: "
|
|
16
|
-
id: "
|
|
15
|
+
name: "Weight Scale Service",
|
|
16
|
+
id: "weight",
|
|
17
17
|
uuid: "00001851-0000-1000-8000-00805f9b34fb",
|
|
18
18
|
characteristics: [
|
|
19
19
|
{
|
|
@@ -24,13 +24,13 @@ export class SmartBoardPro extends Device implements ISmartBoardPro {
|
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
name: "
|
|
28
|
-
id: "",
|
|
27
|
+
name: "Smartboard Service",
|
|
28
|
+
id: "smartboard",
|
|
29
29
|
uuid: "0000403d-0000-1000-8000-00805f9b34fb",
|
|
30
30
|
characteristics: [
|
|
31
31
|
{
|
|
32
|
-
name: "",
|
|
33
|
-
id: "",
|
|
32
|
+
name: "SmartBoard Measurement",
|
|
33
|
+
id: "rx",
|
|
34
34
|
uuid: "00001583-0000-1000-8000-00805f9b34fb",
|
|
35
35
|
},
|
|
36
36
|
],
|
|
@@ -50,4 +50,71 @@ export class SmartBoardPro extends Device implements ISmartBoardPro {
|
|
|
50
50
|
],
|
|
51
51
|
})
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handles data received from the device, processes weight measurements,
|
|
56
|
+
* and updates mass data including maximum and average values.
|
|
57
|
+
* It also handles command responses for retrieving device information.
|
|
58
|
+
*
|
|
59
|
+
* @param {DataView} value - The notification event.
|
|
60
|
+
*/
|
|
61
|
+
override handleNotifications = (value: DataView): void => {
|
|
62
|
+
if (value) {
|
|
63
|
+
// Update timestamp
|
|
64
|
+
this.updateTimestamp()
|
|
65
|
+
if (value.buffer) {
|
|
66
|
+
const length = value.byteLength / 2
|
|
67
|
+
const dataArray: number[] = []
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < length; i++) {
|
|
70
|
+
const offset = i * 2
|
|
71
|
+
if (offset + 1 < value.byteLength) {
|
|
72
|
+
const intValue = value.getInt16(offset, true)
|
|
73
|
+
// For debugging purposes
|
|
74
|
+
console.log(intValue)
|
|
75
|
+
|
|
76
|
+
dataArray.push(intValue)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (dataArray.length === 0) return
|
|
81
|
+
|
|
82
|
+
const receivedTime = Date.now()
|
|
83
|
+
|
|
84
|
+
// Process each data point
|
|
85
|
+
for (const receivedData of dataArray) {
|
|
86
|
+
// Skip invalid values
|
|
87
|
+
if (!Number.isFinite(receivedData)) continue
|
|
88
|
+
|
|
89
|
+
const numericData = receivedData - this.applyTare(receivedData)
|
|
90
|
+
|
|
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
|
+
})
|
|
99
|
+
|
|
100
|
+
// Update peak
|
|
101
|
+
this.peak = Math.max(this.peak, numericData)
|
|
102
|
+
|
|
103
|
+
// Update running sum and count
|
|
104
|
+
const currentMassTotal = Math.max(-1000, numericData)
|
|
105
|
+
this.sum += currentMassTotal
|
|
106
|
+
this.dataPointCount++
|
|
107
|
+
|
|
108
|
+
// Calculate the average dynamically
|
|
109
|
+
this.mean = this.sum / this.dataPointCount
|
|
110
|
+
|
|
111
|
+
// Check if device is being used
|
|
112
|
+
this.activityCheck(numericData)
|
|
113
|
+
|
|
114
|
+
// Notify with weight data
|
|
115
|
+
this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
53
120
|
}
|
|
@@ -117,26 +117,22 @@ export class WHC06 extends Device implements IWHC06 {
|
|
|
117
117
|
masses: [numericData],
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
-
// Update
|
|
121
|
-
this.
|
|
120
|
+
// Update peak
|
|
121
|
+
this.peak = Math.max(this.peak, numericData)
|
|
122
122
|
|
|
123
123
|
// Update running sum and count
|
|
124
124
|
const currentMassTotal = Math.max(-1000, numericData)
|
|
125
|
-
this.
|
|
125
|
+
this.sum += currentMassTotal
|
|
126
126
|
this.dataPointCount++
|
|
127
127
|
|
|
128
128
|
// Calculate the average dynamically
|
|
129
|
-
this.
|
|
129
|
+
this.mean = this.sum / this.dataPointCount
|
|
130
130
|
|
|
131
131
|
// Check if device is being used
|
|
132
132
|
this.activityCheck(numericData)
|
|
133
133
|
|
|
134
134
|
// Notify with weight data
|
|
135
|
-
this.notifyCallback(
|
|
136
|
-
massMax: this.massMax,
|
|
137
|
-
massAverage: this.massAverage,
|
|
138
|
-
massTotal: Math.max(-1000, numericData).toFixed(1),
|
|
139
|
-
})
|
|
135
|
+
this.notifyCallback(this.buildForceMeasurement(Math.max(-1000, numericData)))
|
|
140
136
|
}
|
|
141
137
|
// Reset "still advertising" counter
|
|
142
138
|
this.resetAdvertisementTimeout()
|