@hangtime/grip-connect 0.0.9 → 0.0.11
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 +8 -7
- package/package.json +1 -1
- package/src/connect.js +14 -18
- package/src/connect.ts +13 -17
- package/src/devices/entralpi.d.ts +6 -0
- package/src/devices/entralpi.js +16 -0
- package/src/devices/entralpi.ts +17 -0
- package/src/devices/moterboard.d.ts +2 -2
- package/src/devices/moterboard.js +27 -20
- package/src/devices/moterboard.ts +42 -38
package/README.md
CHANGED
|
@@ -75,16 +75,16 @@ motherboardButton.addEventListener("click", () => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
// read battery + device info
|
|
78
|
-
await read(Motherboard, "battery", "level",
|
|
79
|
-
await read(Motherboard, "device", "manufacturer",
|
|
80
|
-
await read(Motherboard, "device", "hardware",
|
|
81
|
-
await read(Motherboard, "device", "firmware",
|
|
78
|
+
await read(Motherboard, "battery", "level", 250)
|
|
79
|
+
await read(Motherboard, "device", "manufacturer", 250)
|
|
80
|
+
await read(Motherboard, "device", "hardware", 250)
|
|
81
|
+
await read(Motherboard, "device", "firmware", 250)
|
|
82
82
|
|
|
83
83
|
// read calibration (required before reading data)
|
|
84
|
-
await write(Motherboard, "uart", "tx", "C",
|
|
84
|
+
await write(Motherboard, "uart", "tx", "C", 2500)
|
|
85
85
|
|
|
86
|
-
// start
|
|
87
|
-
await write(Motherboard, "uart", "tx", "S30",
|
|
86
|
+
// start streaming for a minute
|
|
87
|
+
await write(Motherboard, "uart", "tx", "S30", 60000)
|
|
88
88
|
|
|
89
89
|
// end stream
|
|
90
90
|
await write(Motherboard, "uart", "tx", "", 0)
|
|
@@ -100,6 +100,7 @@ A special thank you to:
|
|
|
100
100
|
|
|
101
101
|
- [@CassimLadha](https://github.com/CassimLadha) for sharing insights on reading the Motherboards data.
|
|
102
102
|
- [@donaldharvey](https://github.com/donaldharvey) for a valuable example on connecting to the motherboard.
|
|
103
|
+
- [@ecstrema](https://github.com/ecstrema) for providing an example on how to play games with the entralpi.
|
|
103
104
|
|
|
104
105
|
## Disclamer
|
|
105
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hangtime/grip-connect",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "A client that can establish connections with various Force-Sensing Hangboards/Plates used by climbers for strength measurement. Examples of such hangboards include the Motherboard, Climbro, SmartBoard, Entralpi or Tindeq Progressor",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
package/src/connect.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { notifyCallback } from "./notify";
|
|
2
2
|
import { handleMotherboardData } from "./devices/moterboard";
|
|
3
|
+
import { handleEntralpiData } from "./devices/entralpi";
|
|
3
4
|
let server;
|
|
4
5
|
const receiveBuffer = [];
|
|
5
6
|
/**
|
|
@@ -22,27 +23,22 @@ const handleNotifications = (event, board) => {
|
|
|
22
23
|
const value = characteristic.value;
|
|
23
24
|
if (value) {
|
|
24
25
|
if (board.name === "Motherboard") {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
handleMotherboardData(characteristic.uuid, receivedString);
|
|
37
|
-
}
|
|
26
|
+
for (let i = 0; i < value.byteLength; i++) {
|
|
27
|
+
receiveBuffer.push(value.getUint8(i));
|
|
28
|
+
}
|
|
29
|
+
let idx;
|
|
30
|
+
while ((idx = receiveBuffer.indexOf(10)) >= 0) {
|
|
31
|
+
const line = receiveBuffer.splice(0, idx + 1).slice(0, -1); // Combine and remove LF
|
|
32
|
+
if (line.length > 0 && line[line.length - 1] === 13)
|
|
33
|
+
line.pop(); // Remove CR
|
|
34
|
+
const decoder = new TextDecoder("utf-8");
|
|
35
|
+
const receivedData = decoder.decode(new Uint8Array(line));
|
|
36
|
+
handleMotherboardData(characteristic.uuid, receivedData);
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
else if (board.name === "ENTRALPI") {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (notifyCallback) {
|
|
44
|
-
notifyCallback({ uuid: characteristic.uuid, value: value });
|
|
45
|
-
}
|
|
40
|
+
const receivedData = value.getInt16(0) / 100;
|
|
41
|
+
handleEntralpiData(characteristic.uuid, receivedData);
|
|
46
42
|
}
|
|
47
43
|
else if (board.name === "Tindeq") {
|
|
48
44
|
// TODO: handle Tindeq notify
|
package/src/connect.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Device } from "./devices/types"
|
|
2
2
|
import { notifyCallback } from "./notify"
|
|
3
3
|
import { handleMotherboardData } from "./devices/moterboard"
|
|
4
|
+
import { handleEntralpiData } from "./devices/entralpi"
|
|
4
5
|
|
|
5
6
|
let server: BluetoothRemoteGATTServer
|
|
6
7
|
const receiveBuffer: number[] = []
|
|
@@ -25,26 +26,21 @@ const handleNotifications = (event: Event, board: Device): void => {
|
|
|
25
26
|
const value = characteristic.value
|
|
26
27
|
if (value) {
|
|
27
28
|
if (board.name === "Motherboard") {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
29
|
+
for (let i = 0; i < value.byteLength; i++) {
|
|
30
|
+
receiveBuffer.push(value.getUint8(i))
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
33
|
+
let idx: number
|
|
34
|
+
while ((idx = receiveBuffer.indexOf(10)) >= 0) {
|
|
35
|
+
const line = receiveBuffer.splice(0, idx + 1).slice(0, -1) // Combine and remove LF
|
|
36
|
+
if (line.length > 0 && line[line.length - 1] === 13) line.pop() // Remove CR
|
|
37
|
+
const decoder = new TextDecoder("utf-8")
|
|
38
|
+
const receivedData = decoder.decode(new Uint8Array(line))
|
|
39
|
+
handleMotherboardData(characteristic.uuid, receivedData)
|
|
41
40
|
}
|
|
42
41
|
} else if (board.name === "ENTRALPI") {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (notifyCallback) {
|
|
46
|
-
notifyCallback({ uuid: characteristic.uuid, value: value })
|
|
47
|
-
}
|
|
42
|
+
const receivedData: number = value.getInt16(0) / 100
|
|
43
|
+
handleEntralpiData(characteristic.uuid, receivedData)
|
|
48
44
|
} else if (board.name === "Tindeq") {
|
|
49
45
|
// TODO: handle Tindeq notify
|
|
50
46
|
} else {
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { Device } from "./types";
|
|
2
2
|
export declare const Entralpi: Device;
|
|
3
|
+
/**
|
|
4
|
+
* handleEntralpiData
|
|
5
|
+
* @param uuid - Unique identifier
|
|
6
|
+
* @param receivedData - Received data string
|
|
7
|
+
*/
|
|
8
|
+
export declare function handleEntralpiData(uuid: string, receivedData: number): void;
|
package/src/devices/entralpi.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notifyCallback } from "../notify";
|
|
1
2
|
export const Entralpi = {
|
|
2
3
|
name: "ENTRALPI",
|
|
3
4
|
services: [
|
|
@@ -50,3 +51,18 @@ export const Entralpi = {
|
|
|
50
51
|
},
|
|
51
52
|
],
|
|
52
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* handleEntralpiData
|
|
56
|
+
* @param uuid - Unique identifier
|
|
57
|
+
* @param receivedData - Received data string
|
|
58
|
+
*/
|
|
59
|
+
export function handleEntralpiData(uuid, receivedData) {
|
|
60
|
+
if (notifyCallback) {
|
|
61
|
+
notifyCallback({
|
|
62
|
+
uuid,
|
|
63
|
+
value: {
|
|
64
|
+
massTotal: receivedData,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/devices/entralpi.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Device } from "./types"
|
|
2
|
+
import { notifyCallback } from "../notify"
|
|
2
3
|
|
|
3
4
|
export const Entralpi: Device = {
|
|
4
5
|
name: "ENTRALPI",
|
|
@@ -52,3 +53,19 @@ export const Entralpi: Device = {
|
|
|
52
53
|
},
|
|
53
54
|
],
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* handleEntralpiData
|
|
59
|
+
* @param uuid - Unique identifier
|
|
60
|
+
* @param receivedData - Received data string
|
|
61
|
+
*/
|
|
62
|
+
export function handleEntralpiData(uuid: string, receivedData: number): void {
|
|
63
|
+
if (notifyCallback) {
|
|
64
|
+
notifyCallback({
|
|
65
|
+
uuid,
|
|
66
|
+
value: {
|
|
67
|
+
massTotal: receivedData,
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -3,6 +3,6 @@ export declare const Motherboard: Device;
|
|
|
3
3
|
/**
|
|
4
4
|
* handleMotherboardData
|
|
5
5
|
* @param uuid - Unique identifier
|
|
6
|
-
* @param
|
|
6
|
+
* @param receivedData - Received data string
|
|
7
7
|
*/
|
|
8
|
-
export declare function handleMotherboardData(uuid: string,
|
|
8
|
+
export declare function handleMotherboardData(uuid: string, receivedData: string): void;
|
|
@@ -98,7 +98,7 @@ const applyCalibration = (sample, calibration) => {
|
|
|
98
98
|
// Change the sign to negative
|
|
99
99
|
sign = -1;
|
|
100
100
|
// Reflect the sample around the zero calibration point
|
|
101
|
-
sample = 2 * zeroCalibration -
|
|
101
|
+
sample = /* 2 * zeroCalibration */ -sample;
|
|
102
102
|
}
|
|
103
103
|
// Iterate through the calibration data
|
|
104
104
|
for (let i = 1; i < calibration.length; i++) {
|
|
@@ -110,7 +110,8 @@ const applyCalibration = (sample, calibration) => {
|
|
|
110
110
|
// Interpolate to get the calibrated value within the range
|
|
111
111
|
final =
|
|
112
112
|
calibration[i - 1][1] +
|
|
113
|
-
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
|
|
113
|
+
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
|
|
114
|
+
(calibration[i][1] - calibration[i - 1][1]);
|
|
114
115
|
break;
|
|
115
116
|
}
|
|
116
117
|
}
|
|
@@ -120,16 +121,16 @@ const applyCalibration = (sample, calibration) => {
|
|
|
120
121
|
/**
|
|
121
122
|
* handleMotherboardData
|
|
122
123
|
* @param uuid - Unique identifier
|
|
123
|
-
* @param
|
|
124
|
+
* @param receivedData - Received data string
|
|
124
125
|
*/
|
|
125
|
-
export function handleMotherboardData(uuid,
|
|
126
|
+
export function handleMotherboardData(uuid, receivedData) {
|
|
126
127
|
const receivedTime = Date.now();
|
|
127
128
|
// Check if the line is entirely hex characters
|
|
128
|
-
const isAllHex = /^[0-9A-Fa-f]+$/g.test(
|
|
129
|
+
const isAllHex = /^[0-9A-Fa-f]+$/g.test(receivedData);
|
|
129
130
|
// Handle streaming packet
|
|
130
|
-
if (isAllHex &&
|
|
131
|
+
if (isAllHex && receivedData.length === PACKET_LENGTH) {
|
|
131
132
|
// Base-16 decode the string: convert hex pairs to byte values
|
|
132
|
-
const bytes = Array.from({ length:
|
|
133
|
+
const bytes = Array.from({ length: receivedData.length / 2 }, (_, i) => Number(`0x${receivedData.substring(i * 2, i * 2 + 2)}`));
|
|
133
134
|
// Translate header into packet, number of samples from the packet length
|
|
134
135
|
const packet = {
|
|
135
136
|
received: receivedTime,
|
|
@@ -155,28 +156,34 @@ export function handleMotherboardData(uuid, receivedString) {
|
|
|
155
156
|
return;
|
|
156
157
|
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i]);
|
|
157
158
|
}
|
|
159
|
+
// invert center and right values
|
|
160
|
+
packet.masses[1] *= -1;
|
|
161
|
+
packet.masses[2] *= -1;
|
|
162
|
+
// map to variables
|
|
158
163
|
const left = packet.masses[0];
|
|
159
164
|
const center = packet.masses[1];
|
|
160
165
|
const right = packet.masses[2];
|
|
161
|
-
notifyCallback
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
if (notifyCallback) {
|
|
167
|
+
notifyCallback({
|
|
168
|
+
uuid,
|
|
169
|
+
value: {
|
|
170
|
+
massTotal: Math.max(-1000, left + right + center).toFixed(1),
|
|
171
|
+
massLeft: Math.max(-1000, left).toFixed(1),
|
|
172
|
+
massRight: Math.max(-1000, right).toFixed(1),
|
|
173
|
+
massCenter: Math.max(-1000, center).toFixed(1),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
170
177
|
}
|
|
171
|
-
else if ((
|
|
172
|
-
console.log(
|
|
178
|
+
else if ((receivedData.match(/,/g) || []).length === 3) {
|
|
179
|
+
console.log(receivedData);
|
|
173
180
|
// if the returned notification is a calibration string add them to the array
|
|
174
|
-
const parts =
|
|
181
|
+
const parts = receivedData.split(",");
|
|
175
182
|
const numericParts = parts.map((x) => parseFloat(x));
|
|
176
183
|
CALIBRATION[numericParts[0]].push(numericParts.slice(1));
|
|
177
184
|
}
|
|
178
185
|
else {
|
|
179
186
|
// unhanded data
|
|
180
|
-
console.log(
|
|
187
|
+
console.log(receivedData);
|
|
181
188
|
}
|
|
182
189
|
}
|
|
@@ -91,40 +91,41 @@ export const Motherboard: Device = {
|
|
|
91
91
|
*/
|
|
92
92
|
const applyCalibration = (sample: number, calibration: number[][]): number => {
|
|
93
93
|
// Extract the calibrated value for the zero point
|
|
94
|
-
const zeroCalibration: number = calibration[0][2]
|
|
94
|
+
const zeroCalibration: number = calibration[0][2]
|
|
95
95
|
|
|
96
96
|
// Initialize sign as positive
|
|
97
|
-
let sign: number = 1
|
|
97
|
+
let sign: number = 1
|
|
98
98
|
|
|
99
99
|
// Initialize the final calibrated value
|
|
100
|
-
let final: number = 0
|
|
100
|
+
let final: number = 0
|
|
101
101
|
|
|
102
102
|
// If the sample value is less than the zero calibration point
|
|
103
103
|
if (sample < zeroCalibration) {
|
|
104
104
|
// Change the sign to negative
|
|
105
|
-
sign = -1
|
|
105
|
+
sign = -1
|
|
106
106
|
|
|
107
107
|
// Reflect the sample around the zero calibration point
|
|
108
|
-
sample = 2 * zeroCalibration -
|
|
108
|
+
sample = /* 2 * zeroCalibration */ -sample
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Iterate through the calibration data
|
|
112
112
|
for (let i = 1; i < calibration.length; i++) {
|
|
113
113
|
// Extract the lower and upper bounds of the current calibration range
|
|
114
|
-
const calibrationStart: number = calibration[i - 1][2]
|
|
115
|
-
const calibrationEnd: number = calibration[i][2]
|
|
114
|
+
const calibrationStart: number = calibration[i - 1][2]
|
|
115
|
+
const calibrationEnd: number = calibration[i][2]
|
|
116
116
|
|
|
117
117
|
// If the sample value is within the current calibration range
|
|
118
118
|
if (sample < calibrationEnd) {
|
|
119
119
|
// Interpolate to get the calibrated value within the range
|
|
120
120
|
final =
|
|
121
121
|
calibration[i - 1][1] +
|
|
122
|
-
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
|
|
123
|
-
|
|
122
|
+
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
|
|
123
|
+
(calibration[i][1] - calibration[i - 1][1])
|
|
124
|
+
break
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
// Return the calibrated value with the appropriate sign (positive/negative)
|
|
127
|
-
return sign * final
|
|
128
|
+
return sign * final
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
interface Packet {
|
|
@@ -138,19 +139,19 @@ interface Packet {
|
|
|
138
139
|
/**
|
|
139
140
|
* handleMotherboardData
|
|
140
141
|
* @param uuid - Unique identifier
|
|
141
|
-
* @param
|
|
142
|
+
* @param receivedData - Received data string
|
|
142
143
|
*/
|
|
143
|
-
export function handleMotherboardData(uuid: string,
|
|
144
|
+
export function handleMotherboardData(uuid: string, receivedData: string): void {
|
|
144
145
|
const receivedTime: number = Date.now()
|
|
145
146
|
|
|
146
147
|
// Check if the line is entirely hex characters
|
|
147
|
-
const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(
|
|
148
|
+
const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedData)
|
|
148
149
|
|
|
149
150
|
// Handle streaming packet
|
|
150
|
-
if (isAllHex &&
|
|
151
|
+
if (isAllHex && receivedData.length === PACKET_LENGTH) {
|
|
151
152
|
// Base-16 decode the string: convert hex pairs to byte values
|
|
152
|
-
const bytes: number[] = Array.from({ length:
|
|
153
|
-
Number(`0x${
|
|
153
|
+
const bytes: number[] = Array.from({ length: receivedData.length / 2 }, (_, i) =>
|
|
154
|
+
Number(`0x${receivedData.substring(i * 2, i * 2 + 2)}`),
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
// Translate header into packet, number of samples from the packet length
|
|
@@ -162,50 +163,53 @@ export function handleMotherboardData(uuid: string, receivedString: string): voi
|
|
|
162
163
|
masses: [],
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
const dataView = new DataView(new Uint8Array(bytes).buffer)
|
|
166
|
+
const dataView = new DataView(new Uint8Array(bytes).buffer)
|
|
166
167
|
|
|
167
168
|
for (let i = 0; i < NUM_SAMPLES; i++) {
|
|
168
169
|
const sampleStart: number = 4 + 3 * i
|
|
169
170
|
// Use DataView to read the 24-bit unsigned integer
|
|
170
|
-
const rawValue =
|
|
171
|
+
const rawValue =
|
|
172
|
+
dataView.getUint8(sampleStart) |
|
|
171
173
|
(dataView.getUint8(sampleStart + 1) << 8) |
|
|
172
|
-
(dataView.getUint8(sampleStart + 2) << 16)
|
|
174
|
+
(dataView.getUint8(sampleStart + 2) << 16)
|
|
173
175
|
|
|
174
176
|
// Ensure unsigned 32-bit integer
|
|
175
|
-
packet.samples[i] = rawValue >>> 0
|
|
177
|
+
packet.samples[i] = rawValue >>> 0
|
|
176
178
|
|
|
177
179
|
if (packet.samples[i] >= 0x7fffff) {
|
|
178
|
-
packet.samples[i] -= 0x1000000
|
|
180
|
+
packet.samples[i] -= 0x1000000
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
// TODO: make sure device is calibrated
|
|
182
184
|
if (!CALIBRATION[0].length) return
|
|
183
185
|
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i])
|
|
184
186
|
}
|
|
185
|
-
|
|
187
|
+
// invert center and right values
|
|
188
|
+
packet.masses[1] *= -1;
|
|
189
|
+
packet.masses[2] *= -1;
|
|
190
|
+
// map to variables
|
|
186
191
|
const left: number = packet.masses[0]
|
|
187
192
|
const center: number = packet.masses[1]
|
|
188
193
|
const right: number = packet.masses[2]
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
if (notifyCallback) {
|
|
195
|
+
notifyCallback({
|
|
196
|
+
uuid,
|
|
197
|
+
value: {
|
|
198
|
+
massTotal: Math.max(-1000, left + right + center).toFixed(1),
|
|
199
|
+
massLeft: Math.max(-1000, left).toFixed(1),
|
|
200
|
+
massRight: Math.max(-1000, right).toFixed(1),
|
|
201
|
+
massCenter: Math.max(-1000, center).toFixed(1),
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
} else if ((receivedData.match(/,/g) || []).length === 3) {
|
|
206
|
+
console.log(receivedData)
|
|
201
207
|
// if the returned notification is a calibration string add them to the array
|
|
202
|
-
const parts: string[] =
|
|
208
|
+
const parts: string[] = receivedData.split(",")
|
|
203
209
|
const numericParts: number[] = parts.map((x) => parseFloat(x))
|
|
204
210
|
;(CALIBRATION[numericParts[0]] as number[][]).push(numericParts.slice(1))
|
|
205
211
|
} else {
|
|
206
212
|
// unhanded data
|
|
207
|
-
console.log(
|
|
213
|
+
console.log(receivedData)
|
|
208
214
|
}
|
|
209
215
|
}
|
|
210
|
-
|
|
211
|
-
|