@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 CHANGED
@@ -75,16 +75,16 @@ motherboardButton.addEventListener("click", () => {
75
75
  })
76
76
 
77
77
  // read battery + device info
78
- await read(Motherboard, "battery", "level", 1000)
79
- await read(Motherboard, "device", "manufacturer", 1000)
80
- await read(Motherboard, "device", "hardware", 1000)
81
- await read(Motherboard, "device", "firmware", 1000)
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", 5000)
84
+ await write(Motherboard, "uart", "tx", "C", 2500)
85
85
 
86
- // start stream
87
- await write(Motherboard, "uart", "tx", "S30", 15000)
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.9",
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
- if (value) {
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 receivedString = decoder.decode(new Uint8Array(line));
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
- // TODO: handle Entralpi notify
42
- // characteristic.value!.getInt16(0) / 100;
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
- if (value) {
29
- for (let i = 0; i < value.byteLength; i++) {
30
- receiveBuffer.push(value.getUint8(i))
31
- }
29
+ for (let i = 0; i < value.byteLength; i++) {
30
+ receiveBuffer.push(value.getUint8(i))
31
+ }
32
32
 
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 receivedString = decoder.decode(new Uint8Array(line))
39
- handleMotherboardData(characteristic.uuid, receivedString)
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
- // TODO: handle Entralpi notify
44
- // characteristic.value!.getInt16(0) / 100;
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;
@@ -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
+ }
@@ -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 receivedString - Received data string
6
+ * @param receivedData - Received data string
7
7
  */
8
- export declare function handleMotherboardData(uuid: string, receivedString: string): void;
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 - sample;
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)) * (calibration[i][1] - calibration[i - 1][1]);
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 receivedString - Received data string
124
+ * @param receivedData - Received data string
124
125
  */
125
- export function handleMotherboardData(uuid, receivedString) {
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(receivedString);
129
+ const isAllHex = /^[0-9A-Fa-f]+$/g.test(receivedData);
129
130
  // Handle streaming packet
130
- if (isAllHex && receivedString.length === PACKET_LENGTH) {
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: receivedString.length / 2 }, (_, i) => Number(`0x${receivedString.substring(i * 2, i * 2 + 2)}`));
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
- uuid,
163
- value: {
164
- massTotal: Math.max(-1000, left + right + center).toFixed(3),
165
- massLeft: Math.max(-1000, left).toFixed(3),
166
- massRight: Math.max(-1000, right).toFixed(3),
167
- massCenter: Math.max(-1000, center).toFixed(3),
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 ((receivedString.match(/,/g) || []).length === 3) {
172
- console.log(receivedString);
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 = receivedString.split(",");
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(receivedString);
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 - sample;
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)) * (calibration[i][1] - calibration[i - 1][1]);
123
- break;
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 receivedString - Received data string
142
+ * @param receivedData - Received data string
142
143
  */
143
- export function handleMotherboardData(uuid: string, receivedString: string): void {
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(receivedString);
148
+ const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedData)
148
149
 
149
150
  // Handle streaming packet
150
- if (isAllHex && receivedString.length === PACKET_LENGTH) {
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: receivedString.length / 2 }, (_, i) =>
153
- Number(`0x${receivedString.substring(i * 2, i * 2 + 2)}`),
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 = dataView.getUint8(sampleStart) |
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
- notifyCallback({
191
- uuid,
192
- value: {
193
- massTotal: Math.max(-1000, left + right + center).toFixed(3),
194
- massLeft: Math.max(-1000, left).toFixed(3),
195
- massRight: Math.max(-1000, right).toFixed(3),
196
- massCenter: Math.max(-1000, center).toFixed(3),
197
- },
198
- })
199
- } else if ((receivedString.match(/,/g) || []).length === 3) {
200
- console.log(receivedString)
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[] = receivedString.split(",")
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(receivedString)
213
+ console.log(receivedData)
208
214
  }
209
215
  }
210
-
211
-