@hangtime/grip-connect 0.0.8 → 0.0.10

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
@@ -13,7 +13,7 @@ Force-Sensing Hangboards / Plates used by climbers for strength measurement. Exa
13
13
 
14
14
  ## Roadmap
15
15
 
16
- - ✅ Griptonte Motherboard
16
+ - ✅ Griptonite Motherboard
17
17
  - ✅️ Connect with devices
18
18
  - ✅️ Read / Write / Notify using Bluetooth
19
19
  - ➡️ Calibrate Devices
@@ -80,15 +80,11 @@ motherboardButton.addEventListener("click", () => {
80
80
  await read(Motherboard, "device", "hardware", 1000)
81
81
  await read(Motherboard, "device", "firmware", 1000)
82
82
 
83
- // recalibrate
84
- await write(Motherboard, "uart", "tx", "", 0)
85
- await write(Motherboard, "uart", "tx", "", 0)
86
- await write(Motherboard, "uart", "tx", "", 1000)
87
-
88
- await write(Motherboard, "uart", "tx", "C3,0,0,0", 5000)
83
+ // read calibration (required before reading data)
84
+ await write(Motherboard, "uart", "tx", "C", 5000)
89
85
 
90
86
  // start stream
91
- await write(Motherboard, "uart", "tx", "S20", 15000)
87
+ await write(Motherboard, "uart", "tx", "S30", 15000)
92
88
 
93
89
  // end stream
94
90
  await write(Motherboard, "uart", "tx", "", 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hangtime/grip-connect",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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,16 @@ 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
+ notifyCallback({
61
+ uuid,
62
+ value: {
63
+ massTotal: receivedData,
64
+ },
65
+ });
66
+ }
@@ -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,17 @@ 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
+ notifyCallback({
64
+ uuid,
65
+ value: {
66
+ massTotal: receivedData,
67
+ },
68
+ })
69
+ }
@@ -2,6 +2,7 @@ import { Device } from "./types";
2
2
  export declare const Motherboard: Device;
3
3
  /**
4
4
  * handleMotherboardData
5
- * @param line
5
+ * @param uuid - Unique identifier
6
+ * @param receivedData - Received data string
6
7
  */
7
- export declare function handleMotherboardData(uuid: string, receivedString: string): void;
8
+ export declare function handleMotherboardData(uuid: string, receivedData: string): void;
@@ -87,37 +87,50 @@ export const Motherboard = {
87
87
  * @param calibration
88
88
  */
89
89
  const applyCalibration = (sample, calibration) => {
90
- const zeroCalib = calibration[0][2];
91
- let sgn = 1;
90
+ // Extract the calibrated value for the zero point
91
+ const zeroCalibration = calibration[0][2];
92
+ // Initialize sign as positive
93
+ let sign = 1;
94
+ // Initialize the final calibrated value
92
95
  let final = 0;
93
- if (sample < zeroCalib) {
94
- sgn = -1;
95
- sample = 2 * zeroCalib - sample;
96
+ // If the sample value is less than the zero calibration point
97
+ if (sample < zeroCalibration) {
98
+ // Change the sign to negative
99
+ sign = -1;
100
+ // Reflect the sample around the zero calibration point
101
+ sample = 2 * zeroCalibration - sample;
96
102
  }
103
+ // Iterate through the calibration data
97
104
  for (let i = 1; i < calibration.length; i++) {
98
- const calibStart = calibration[i - 1][2];
99
- const calibEnd = calibration[i][2];
100
- if (sample < calibEnd) {
105
+ // Extract the lower and upper bounds of the current calibration range
106
+ const calibrationStart = calibration[i - 1][2];
107
+ const calibrationEnd = calibration[i][2];
108
+ // If the sample value is within the current calibration range
109
+ if (sample < calibrationEnd) {
110
+ // Interpolate to get the calibrated value within the range
101
111
  final =
102
112
  calibration[i - 1][1] +
103
- ((sample - calibStart) / (calibEnd - calibStart)) * (calibration[i][1] - calibration[i - 1][1]);
113
+ ((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
114
+ (calibration[i][1] - calibration[i - 1][1]);
104
115
  break;
105
116
  }
106
117
  }
107
- return sgn * final;
118
+ // Return the calibrated value with the appropriate sign (positive/negative)
119
+ return sign * final;
108
120
  };
109
121
  /**
110
122
  * handleMotherboardData
111
- * @param line
123
+ * @param uuid - Unique identifier
124
+ * @param receivedData - Received data string
112
125
  */
113
- export function handleMotherboardData(uuid, receivedString) {
126
+ export function handleMotherboardData(uuid, receivedData) {
114
127
  const receivedTime = Date.now();
115
128
  // Check if the line is entirely hex characters
116
- const allHex = /^[0-9A-Fa-f]+$/g.test(receivedString);
117
- // Decide if this is a streaming packet
118
- if (allHex && receivedString.length === PACKET_LENGTH) {
129
+ const isAllHex = /^[0-9A-Fa-f]+$/g.test(receivedData);
130
+ // Handle streaming packet
131
+ if (isAllHex && receivedData.length === PACKET_LENGTH) {
119
132
  // Base-16 decode the string: convert hex pairs to byte values
120
- 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)}`));
121
134
  // Translate header into packet, number of samples from the packet length
122
135
  const packet = {
123
136
  received: receivedTime,
@@ -126,9 +139,15 @@ export function handleMotherboardData(uuid, receivedString) {
126
139
  samples: [],
127
140
  masses: [],
128
141
  };
142
+ const dataView = new DataView(new Uint8Array(bytes).buffer);
129
143
  for (let i = 0; i < NUM_SAMPLES; i++) {
130
144
  const sampleStart = 4 + 3 * i;
131
- packet.samples[i] = bytes[sampleStart] | (bytes[sampleStart + 1] << 8) | (bytes[sampleStart + 2] << 16);
145
+ // Use DataView to read the 24-bit unsigned integer
146
+ const rawValue = dataView.getUint8(sampleStart) |
147
+ (dataView.getUint8(sampleStart + 1) << 8) |
148
+ (dataView.getUint8(sampleStart + 2) << 16);
149
+ // Ensure unsigned 32-bit integer
150
+ packet.samples[i] = rawValue >>> 0;
132
151
  if (packet.samples[i] >= 0x7fffff) {
133
152
  packet.samples[i] -= 0x1000000;
134
153
  }
@@ -146,18 +165,19 @@ export function handleMotherboardData(uuid, receivedString) {
146
165
  massTotal: Math.max(-1000, left + right + center).toFixed(3),
147
166
  massLeft: Math.max(-1000, left).toFixed(3),
148
167
  massRight: Math.max(-1000, right).toFixed(3),
149
- massCentre: Math.max(-1000, center).toFixed(3),
168
+ massCenter: Math.max(-1000, center).toFixed(3),
150
169
  },
151
170
  });
152
171
  }
153
- else if ((receivedString.match(/,/g) || []).length === 3) {
172
+ else if ((receivedData.match(/,/g) || []).length === 3) {
173
+ console.log(receivedData);
154
174
  // if the returned notification is a calibration string add them to the array
155
- const parts = receivedString.split(",");
175
+ const parts = receivedData.split(",");
156
176
  const numericParts = parts.map((x) => parseFloat(x));
157
177
  CALIBRATION[numericParts[0]].push(numericParts.slice(1));
158
178
  }
159
179
  else {
160
180
  // unhanded data
161
- console.log(receivedString);
181
+ console.log(receivedData);
162
182
  }
163
183
  }
@@ -90,28 +90,42 @@ export const Motherboard: Device = {
90
90
  * @param calibration
91
91
  */
92
92
  const applyCalibration = (sample: number, calibration: number[][]): number => {
93
- const zeroCalib: number = calibration[0][2]
94
- let sgn: number = 1
93
+ // Extract the calibrated value for the zero point
94
+ const zeroCalibration: number = calibration[0][2]
95
+
96
+ // Initialize sign as positive
97
+ let sign: number = 1
98
+
99
+ // Initialize the final calibrated value
95
100
  let final: number = 0
96
101
 
97
- if (sample < zeroCalib) {
98
- sgn = -1
99
- sample = 2 * zeroCalib - sample
102
+ // If the sample value is less than the zero calibration point
103
+ if (sample < zeroCalibration) {
104
+ // Change the sign to negative
105
+ sign = -1
106
+
107
+ // Reflect the sample around the zero calibration point
108
+ sample = 2 * zeroCalibration - sample
100
109
  }
101
110
 
111
+ // Iterate through the calibration data
102
112
  for (let i = 1; i < calibration.length; i++) {
103
- const calibStart: number = calibration[i - 1][2]
104
- const calibEnd: number = calibration[i][2]
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]
105
116
 
106
- if (sample < calibEnd) {
117
+ // If the sample value is within the current calibration range
118
+ if (sample < calibrationEnd) {
119
+ // Interpolate to get the calibrated value within the range
107
120
  final =
108
121
  calibration[i - 1][1] +
109
- ((sample - calibStart) / (calibEnd - calibStart)) * (calibration[i][1] - calibration[i - 1][1])
122
+ ((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
123
+ (calibration[i][1] - calibration[i - 1][1])
110
124
  break
111
125
  }
112
126
  }
113
-
114
- return sgn * final
127
+ // Return the calibrated value with the appropriate sign (positive/negative)
128
+ return sign * final
115
129
  }
116
130
 
117
131
  interface Packet {
@@ -124,19 +138,20 @@ interface Packet {
124
138
 
125
139
  /**
126
140
  * handleMotherboardData
127
- * @param line
141
+ * @param uuid - Unique identifier
142
+ * @param receivedData - Received data string
128
143
  */
129
- export function handleMotherboardData(uuid: string, receivedString: string): void {
144
+ export function handleMotherboardData(uuid: string, receivedData: string): void {
130
145
  const receivedTime: number = Date.now()
131
146
 
132
147
  // Check if the line is entirely hex characters
133
- const allHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedString)
148
+ const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedData)
134
149
 
135
- // Decide if this is a streaming packet
136
- if (allHex && receivedString.length === PACKET_LENGTH) {
150
+ // Handle streaming packet
151
+ if (isAllHex && receivedData.length === PACKET_LENGTH) {
137
152
  // Base-16 decode the string: convert hex pairs to byte values
138
- const bytes: number[] = Array.from({ length: receivedString.length / 2 }, (_, i) =>
139
- 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)}`),
140
155
  )
141
156
 
142
157
  // Translate header into packet, number of samples from the packet length
@@ -148,9 +163,18 @@ export function handleMotherboardData(uuid: string, receivedString: string): voi
148
163
  masses: [],
149
164
  }
150
165
 
166
+ const dataView = new DataView(new Uint8Array(bytes).buffer)
167
+
151
168
  for (let i = 0; i < NUM_SAMPLES; i++) {
152
169
  const sampleStart: number = 4 + 3 * i
153
- packet.samples[i] = bytes[sampleStart] | (bytes[sampleStart + 1] << 8) | (bytes[sampleStart + 2] << 16)
170
+ // Use DataView to read the 24-bit unsigned integer
171
+ const rawValue =
172
+ dataView.getUint8(sampleStart) |
173
+ (dataView.getUint8(sampleStart + 1) << 8) |
174
+ (dataView.getUint8(sampleStart + 2) << 16)
175
+
176
+ // Ensure unsigned 32-bit integer
177
+ packet.samples[i] = rawValue >>> 0
154
178
 
155
179
  if (packet.samples[i] >= 0x7fffff) {
156
180
  packet.samples[i] -= 0x1000000
@@ -158,7 +182,6 @@ export function handleMotherboardData(uuid: string, receivedString: string): voi
158
182
 
159
183
  // TODO: make sure device is calibrated
160
184
  if (!CALIBRATION[0].length) return
161
-
162
185
  packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i])
163
186
  }
164
187
 
@@ -172,16 +195,17 @@ export function handleMotherboardData(uuid: string, receivedString: string): voi
172
195
  massTotal: Math.max(-1000, left + right + center).toFixed(3),
173
196
  massLeft: Math.max(-1000, left).toFixed(3),
174
197
  massRight: Math.max(-1000, right).toFixed(3),
175
- massCentre: Math.max(-1000, center).toFixed(3),
198
+ massCenter: Math.max(-1000, center).toFixed(3),
176
199
  },
177
200
  })
178
- } else if ((receivedString.match(/,/g) || []).length === 3) {
201
+ } else if ((receivedData.match(/,/g) || []).length === 3) {
202
+ console.log(receivedData)
179
203
  // if the returned notification is a calibration string add them to the array
180
- const parts: string[] = receivedString.split(",")
204
+ const parts: string[] = receivedData.split(",")
181
205
  const numericParts: number[] = parts.map((x) => parseFloat(x))
182
206
  ;(CALIBRATION[numericParts[0]] as number[][]).push(numericParts.slice(1))
183
207
  } else {
184
208
  // unhanded data
185
- console.log(receivedString)
209
+ console.log(receivedData)
186
210
  }
187
211
  }
package/src/write.js CHANGED
@@ -10,9 +10,8 @@ export const write = (board, serviceId, characteristicId, message, duration = 0)
10
10
  const encoder = new TextEncoder();
11
11
  const characteristic = getCharacteristic(board, serviceId, characteristicId);
12
12
  if (characteristic) {
13
- const value = message + "\n";
14
13
  characteristic
15
- .writeValue(encoder.encode(value))
14
+ .writeValue(encoder.encode(message))
16
15
  .then(() => {
17
16
  setTimeout(() => {
18
17
  resolve();
package/src/write.ts CHANGED
@@ -19,9 +19,8 @@ export const write = (
19
19
  const characteristic = getCharacteristic(board, serviceId, characteristicId)
20
20
 
21
21
  if (characteristic) {
22
- const value = message + "\n"
23
22
  characteristic
24
- .writeValue(encoder.encode(value))
23
+ .writeValue(encoder.encode(message))
25
24
  .then(() => {
26
25
  setTimeout(() => {
27
26
  resolve()
package/tsconfig.json CHANGED
@@ -3,7 +3,5 @@
3
3
  "include": ["src"],
4
4
  "compilerOptions": {
5
5
  "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo",
6
- "types": ["web-bluetooth"],
7
- "outDir": "./build",
8
6
  },
9
7
  }
@@ -1,9 +0,0 @@
1
- /// <reference types="web-bluetooth" />
2
- import { Device } from "./devices/types";
3
- /**
4
- * getCharacteristic
5
- * @param board
6
- * @param serviceId
7
- * @param characteristicId
8
- */
9
- export declare const getCharacteristic: (board: Device, serviceId: string, characteristicId: string) => BluetoothRemoteGATTCharacteristic | undefined;
@@ -1,15 +0,0 @@
1
- /**
2
- * getCharacteristic
3
- * @param board
4
- * @param serviceId
5
- * @param characteristicId
6
- */
7
- export const getCharacteristic = (board, serviceId, characteristicId) => {
8
- const boardService = board.services.find((service) => service.id === serviceId);
9
- if (boardService) {
10
- const boardCharacteristic = boardService.characteristics.find((characteristic) => characteristic.id === characteristicId);
11
- if (boardCharacteristic) {
12
- return boardCharacteristic.characteristic;
13
- }
14
- }
15
- };
@@ -1,7 +0,0 @@
1
- import { Device } from "./devices/types";
2
- /**
3
- * Connect to the BluetoothDevice
4
- * @param device
5
- * @param onSuccess
6
- */
7
- export declare const connect: (board: Device, onSuccess: () => void) => Promise<void>;
package/build/connect.js DELETED
@@ -1,150 +0,0 @@
1
- import { notifyCallback } from "./notify";
2
- import { handleMotherboardData } from "./devices/moterboard";
3
- let server;
4
- const receiveBuffer = [];
5
- /**
6
- * onDisconnected
7
- * @param board
8
- * @param event
9
- */
10
- const onDisconnected = (event, board) => {
11
- board.device = undefined;
12
- const device = event.target;
13
- console.log(`Device ${device.name} is disconnected.`);
14
- };
15
- /**
16
- * handleNotifications
17
- * @param event
18
- * @param onNotify
19
- */
20
- const handleNotifications = (event, board) => {
21
- const characteristic = event.target;
22
- const value = characteristic.value;
23
- if (value) {
24
- 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
- }
38
- }
39
- }
40
- 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
- }
46
- }
47
- else if (board.name === "Tindeq") {
48
- // TODO: handle Tindeq notify
49
- }
50
- else {
51
- if (notifyCallback) {
52
- notifyCallback({ uuid: characteristic.uuid, value: value });
53
- }
54
- }
55
- }
56
- };
57
- /**
58
- * onConnected
59
- * @param event
60
- * @param board
61
- */
62
- const onConnected = async (board, onSuccess) => {
63
- try {
64
- const services = await server?.getPrimaryServices();
65
- if (!services || services.length === 0) {
66
- console.error("No services found");
67
- return;
68
- }
69
- for (const service of services) {
70
- const matchingService = board.services.find((boardService) => boardService.uuid === service.uuid);
71
- if (matchingService) {
72
- // Android bug: Introduce a delay before getting characteristics
73
- await new Promise((resolve) => setTimeout(resolve, 100));
74
- const characteristics = await service.getCharacteristics();
75
- for (const characteristic of matchingService.characteristics) {
76
- const matchingCharacteristic = characteristics.find((char) => char.uuid === characteristic.uuid);
77
- if (matchingCharacteristic) {
78
- const element = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid);
79
- if (element) {
80
- element.characteristic = matchingCharacteristic;
81
- // notify
82
- if (element.id === "rx") {
83
- matchingCharacteristic.startNotifications();
84
- matchingCharacteristic.addEventListener("characteristicvaluechanged", (event) => handleNotifications(event, board));
85
- }
86
- }
87
- }
88
- else {
89
- console.warn(`Characteristic ${characteristic.uuid} not found in service ${service.uuid}`);
90
- }
91
- }
92
- }
93
- }
94
- // Call the onSuccess callback after successful connection and setup
95
- onSuccess();
96
- }
97
- catch (error) {
98
- console.error(error);
99
- }
100
- };
101
- /**
102
- * Return all service UUIDs
103
- * @param device
104
- */
105
- function getAllServiceUUIDs(device) {
106
- return device.services.map((service) => service.uuid);
107
- }
108
- /**
109
- * Connect to the BluetoothDevice
110
- * @param device
111
- * @param onSuccess
112
- */
113
- export const connect = async (board, onSuccess) => {
114
- try {
115
- const deviceServices = getAllServiceUUIDs(board);
116
- // setup filter list
117
- const filters = [];
118
- if (board.name) {
119
- filters.push({
120
- name: board.name,
121
- });
122
- }
123
- if (board.companyId) {
124
- filters.push({
125
- manufacturerData: [
126
- {
127
- companyIdentifier: board.companyId,
128
- },
129
- ],
130
- });
131
- }
132
- const device = await navigator.bluetooth.requestDevice({
133
- filters: filters,
134
- optionalServices: deviceServices,
135
- });
136
- board.device = device;
137
- if (!board.device.gatt) {
138
- console.error("GATT is not available on this device");
139
- return;
140
- }
141
- server = await board.device?.gatt?.connect();
142
- board.device.addEventListener("gattserverdisconnected", (event) => onDisconnected(event, board));
143
- if (server.connected) {
144
- await onConnected(board, onSuccess);
145
- }
146
- }
147
- catch (error) {
148
- console.error(error);
149
- }
150
- };
@@ -1,2 +0,0 @@
1
- import { Device } from "./types";
2
- export declare const Entralpi: Device;
@@ -1,52 +0,0 @@
1
- export const Entralpi = {
2
- name: "ENTRALPI",
3
- services: [
4
- {
5
- name: "Device Information",
6
- id: "device",
7
- uuid: "0000180a-0000-1000-8000-00805f9b34fb",
8
- characteristics: [],
9
- },
10
- {
11
- name: "Battery Service",
12
- id: "battery",
13
- uuid: "0000180f-0000-1000-8000-00805f9b34fb",
14
- characteristics: [],
15
- },
16
- {
17
- name: "Generic Attribute",
18
- id: "attribute",
19
- uuid: "00001801-0000-1000-8000-00805f9b34fb",
20
- characteristics: [],
21
- },
22
- {
23
- name: "UART ISSC Transparent Service",
24
- id: "uart",
25
- uuid: "0000fff0-0000-1000-8000-00805f9b34fb",
26
- characteristics: [
27
- {
28
- name: "TX",
29
- id: "tx",
30
- uuid: "0000fff5-0000-1000-8000-00805f9b34fb",
31
- },
32
- {
33
- name: "RX",
34
- id: "rx",
35
- uuid: "0000fff4-0000-1000-8000-00805f9b34fb",
36
- },
37
- ],
38
- },
39
- {
40
- name: "Weight Scale",
41
- id: "weight",
42
- uuid: "0000181d-0000-1000-8000-00805f9b34fb",
43
- characteristics: [],
44
- },
45
- {
46
- name: "Generic Access",
47
- id: "access",
48
- uuid: "00001800-0000-1000-8000-00805f9b34fb",
49
- characteristics: [],
50
- },
51
- ],
52
- };
@@ -1,3 +0,0 @@
1
- export { Motherboard } from "./moterboard";
2
- export { Entralpi } from "./entralpi";
3
- export { Tindeq } from "./tindeq";
@@ -1,3 +0,0 @@
1
- export { Motherboard } from "./moterboard";
2
- export { Entralpi } from "./entralpi";
3
- export { Tindeq } from "./tindeq";
@@ -1,7 +0,0 @@
1
- import { Device } from "./types";
2
- export declare const Motherboard: Device;
3
- /**
4
- * handleMotherboardData
5
- * @param line
6
- */
7
- export declare function handleMotherboardData(uuid: string, receivedString: string): void;
@@ -1,163 +0,0 @@
1
- import { notifyCallback } from "../notify";
2
- const PACKET_LENGTH = 32;
3
- const NUM_SAMPLES = 3;
4
- const CALIBRATION = [[], [], [], []];
5
- export const Motherboard = {
6
- name: "Motherboard",
7
- companyId: 0x2a29,
8
- services: [
9
- {
10
- name: "Device Information",
11
- id: "device",
12
- uuid: "0000180a-0000-1000-8000-00805f9b34fb",
13
- characteristics: [
14
- // {
15
- // name: 'Serial Number (Blocked)',
16
- // id: 'serial'
17
- // uuid: '00002a25-0000-1000-8000-00805f9b34fb'
18
- // },
19
- {
20
- name: "Firmware Revision",
21
- id: "firmware",
22
- uuid: "00002a26-0000-1000-8000-00805f9b34fb",
23
- },
24
- {
25
- name: "Hardware Revision",
26
- id: "hardware",
27
- uuid: "00002a27-0000-1000-8000-00805f9b34fb",
28
- },
29
- {
30
- name: "Manufacturer Name",
31
- id: "manufacturer",
32
- uuid: "00002a29-0000-1000-8000-00805f9b34fb",
33
- },
34
- ],
35
- },
36
- {
37
- name: "Battery Service",
38
- id: "battery",
39
- uuid: "0000180f-0000-1000-8000-00805f9b34fb",
40
- characteristics: [
41
- {
42
- name: "Battery Level",
43
- id: "level",
44
- uuid: "00002a19-0000-1000-8000-00805f9b34fb",
45
- },
46
- ],
47
- },
48
- {
49
- name: "Unknown Service",
50
- id: "unknown",
51
- uuid: "10ababcd-15e1-28ff-de13-725bea03b127",
52
- characteristics: [
53
- {
54
- name: "Unknown 01",
55
- id: "01",
56
- uuid: "10ab1524-15e1-28ff-de13-725bea03b127",
57
- },
58
- {
59
- name: "Unknown 02",
60
- id: "02",
61
- uuid: "10ab1525-15e1-28ff-de13-725bea03b127",
62
- },
63
- ],
64
- },
65
- {
66
- name: "UART Nordic Service",
67
- id: "uart",
68
- uuid: "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
69
- characteristics: [
70
- {
71
- name: "TX",
72
- id: "tx",
73
- uuid: "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
74
- },
75
- {
76
- name: "RX",
77
- id: "rx",
78
- uuid: "6e400003-b5a3-f393-e0a9-e50e24dcca9e",
79
- },
80
- ],
81
- },
82
- ],
83
- };
84
- /**
85
- * applyCalibration
86
- * @param sample
87
- * @param calibration
88
- */
89
- const applyCalibration = (sample, calibration) => {
90
- const zeroCalib = calibration[0][2];
91
- let sgn = 1;
92
- let final = 0;
93
- if (sample < zeroCalib) {
94
- sgn = -1;
95
- sample = 2 * zeroCalib - sample;
96
- }
97
- for (let i = 1; i < calibration.length; i++) {
98
- const calibStart = calibration[i - 1][2];
99
- const calibEnd = calibration[i][2];
100
- if (sample < calibEnd) {
101
- final =
102
- calibration[i - 1][1] +
103
- ((sample - calibStart) / (calibEnd - calibStart)) * (calibration[i][1] - calibration[i - 1][1]);
104
- break;
105
- }
106
- }
107
- return sgn * final;
108
- };
109
- /**
110
- * handleMotherboardData
111
- * @param line
112
- */
113
- export function handleMotherboardData(uuid, receivedString) {
114
- const receivedTime = Date.now();
115
- // Check if the line is entirely hex characters
116
- const allHex = /^[0-9A-Fa-f]+$/g.test(receivedString);
117
- // Decide if this is a streaming packet
118
- if (allHex && receivedString.length === PACKET_LENGTH) {
119
- // Base-16 decode the string: convert hex pairs to byte values
120
- const bytes = Array.from({ length: receivedString.length / 2 }, (_, i) => Number(`0x${receivedString.substring(i * 2, i * 2 + 2)}`));
121
- // Translate header into packet, number of samples from the packet length
122
- const packet = {
123
- received: receivedTime,
124
- sampleNum: new DataView(new Uint8Array(bytes).buffer).getUint16(0, true),
125
- battRaw: new DataView(new Uint8Array(bytes).buffer).getUint16(2, true),
126
- samples: [],
127
- masses: [],
128
- };
129
- for (let i = 0; i < NUM_SAMPLES; i++) {
130
- const sampleStart = 4 + 3 * i;
131
- packet.samples[i] = bytes[sampleStart] | (bytes[sampleStart + 1] << 8) | (bytes[sampleStart + 2] << 16);
132
- if (packet.samples[i] >= 0x7fffff) {
133
- packet.samples[i] -= 0x1000000;
134
- }
135
- // TODO: make sure device is calibrated
136
- if (!CALIBRATION[0].length)
137
- return;
138
- packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i]);
139
- }
140
- const left = packet.masses[0];
141
- const center = packet.masses[1];
142
- const right = packet.masses[2];
143
- notifyCallback({
144
- uuid,
145
- value: {
146
- massTotal: Math.max(-1000, left + right + center).toFixed(3),
147
- massLeft: Math.max(-1000, left).toFixed(3),
148
- massRight: Math.max(-1000, right).toFixed(3),
149
- massCentre: Math.max(-1000, center).toFixed(3),
150
- },
151
- });
152
- }
153
- else if ((receivedString.match(/,/g) || []).length === 3) {
154
- // if the returned notification is a calibration string add them to the array
155
- const parts = receivedString.split(",");
156
- const numericParts = parts.map((x) => parseFloat(x));
157
- CALIBRATION[numericParts[0]].push(numericParts.slice(1));
158
- }
159
- else {
160
- // unhanded data
161
- console.log(receivedString);
162
- }
163
- }
@@ -1,17 +0,0 @@
1
- import { Device } from "./types";
2
- export declare const Tindeq: Device;
3
- export declare const Commands: {
4
- TARE_SCALE: number;
5
- START_MEASURING: number;
6
- STOP_MEASURING: number;
7
- GET_APP_VERSION: number;
8
- GET_ERROR_INFO: number;
9
- CLEAR_ERR_INFO: number;
10
- GET_BATTERY_LEVEL: number;
11
- SLEEP: number;
12
- };
13
- export declare const NotificationTypes: {
14
- COMMAND_RESPONSE: number;
15
- WEIGHT_MEASURE: number;
16
- LOW_BATTERY_WARNING: number;
17
- };
@@ -1,37 +0,0 @@
1
- export const Tindeq = {
2
- name: "Tindeq",
3
- services: [
4
- {
5
- name: "Progressor Service",
6
- id: "progressor",
7
- uuid: "7e4e1701-1ea6-40c9-9dcc-13d34ffead57",
8
- characteristics: [
9
- {
10
- name: "Write",
11
- id: "tx",
12
- uuid: "7e4e1703-1ea6-40c9-9dcc-13d34ffead57",
13
- },
14
- {
15
- name: "Notify",
16
- id: "rx",
17
- uuid: "7e4e1702-1ea6-40c9-9dcc-13d34ffead57",
18
- },
19
- ],
20
- },
21
- ],
22
- };
23
- export const Commands = {
24
- TARE_SCALE: 0x64,
25
- START_MEASURING: 0x65,
26
- STOP_MEASURING: 0x66,
27
- GET_APP_VERSION: 0x6b,
28
- GET_ERROR_INFO: 0x6c,
29
- CLEAR_ERR_INFO: 0x6d,
30
- GET_BATTERY_LEVEL: 0x6f,
31
- SLEEP: 0x6e,
32
- };
33
- export const NotificationTypes = {
34
- COMMAND_RESPONSE: 0,
35
- WEIGHT_MEASURE: 1,
36
- LOW_BATTERY_WARNING: 2,
37
- };
@@ -1,20 +0,0 @@
1
- /// <reference types="web-bluetooth" />
2
- interface Characteristic {
3
- name: string;
4
- id: string;
5
- uuid: string;
6
- characteristic?: BluetoothRemoteGATTCharacteristic;
7
- }
8
- interface Service {
9
- name: string;
10
- id: string;
11
- uuid: string;
12
- characteristics: Characteristic[];
13
- }
14
- export interface Device {
15
- name: string;
16
- companyId?: number;
17
- services: Service[];
18
- device?: BluetoothDevice;
19
- }
20
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- import { Device } from "./devices/types";
2
- /**
3
- * disconnect
4
- * @param board
5
- */
6
- export declare const disconnect: (board: Device) => void;
@@ -1,11 +0,0 @@
1
- /**
2
- * disconnect
3
- * @param board
4
- */
5
- export const disconnect = (board) => {
6
- if (!board.device)
7
- return;
8
- if (board.device.gatt?.connected) {
9
- board.device.gatt?.disconnect();
10
- }
11
- };
package/build/index.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export { Motherboard, Entralpi, Tindeq } from "./devices/index";
2
- export { connect } from "./connect";
3
- export { disconnect } from "./disconnect";
4
- export { notify } from "./notify";
5
- export { read } from "./read";
6
- export { write } from "./write";
package/build/index.js DELETED
@@ -1,6 +0,0 @@
1
- export { Motherboard, Entralpi, Tindeq } from "./devices/index";
2
- export { connect } from "./connect";
3
- export { disconnect } from "./disconnect";
4
- export { notify } from "./notify";
5
- export { read } from "./read";
6
- export { write } from "./write";
package/build/notify.d.ts DELETED
@@ -1,4 +0,0 @@
1
- type NotifyCallback = (data: object) => void;
2
- export declare let notifyCallback: NotifyCallback;
3
- export declare const notify: (callback: NotifyCallback) => void;
4
- export {};
package/build/notify.js DELETED
@@ -1,6 +0,0 @@
1
- // Initialize the callback variable
2
- export let notifyCallback;
3
- // Export a function to set the callback
4
- export const notify = (callback) => {
5
- notifyCallback = callback;
6
- };
package/build/read.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import { Device } from "./devices/types";
2
- /**
3
- * read
4
- * @param characteristic
5
- */
6
- export declare const read: (board: Device, serviceId: string, characteristicId: string, duration?: number) => Promise<void>;
package/build/read.js DELETED
@@ -1,44 +0,0 @@
1
- import { notifyCallback } from "./notify";
2
- import { getCharacteristic } from "./characteristic";
3
- /**
4
- * read
5
- * @param characteristic
6
- */
7
- export const read = (board, serviceId, characteristicId, duration = 0) => {
8
- return new Promise((resolve, reject) => {
9
- if (board.device?.gatt?.connected) {
10
- const characteristic = getCharacteristic(board, serviceId, characteristicId);
11
- if (characteristic) {
12
- characteristic
13
- .readValue()
14
- .then((value) => {
15
- let decodedValue;
16
- const decoder = new TextDecoder("utf-8");
17
- switch (characteristicId) {
18
- case "level":
19
- decodedValue = value.getUint8(0);
20
- break;
21
- default:
22
- decodedValue = decoder.decode(value);
23
- break;
24
- }
25
- if (notifyCallback) {
26
- notifyCallback({ uuid: characteristic.uuid, value: decodedValue });
27
- }
28
- setTimeout(() => {
29
- resolve();
30
- }, duration);
31
- })
32
- .catch((error) => {
33
- reject(error);
34
- });
35
- }
36
- else {
37
- reject(new Error("Characteristic is undefined"));
38
- }
39
- }
40
- else {
41
- reject(new Error("Device is not connected"));
42
- }
43
- });
44
- };
package/build/write.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { Device } from "./devices/types";
2
- /**
3
- * write
4
- * @param characteristic
5
- * @param message
6
- */
7
- export declare const write: (board: Device, serviceId: string, characteristicId: string, message: string, duration?: number) => Promise<void>;
package/build/write.js DELETED
@@ -1,33 +0,0 @@
1
- import { getCharacteristic } from "./characteristic";
2
- /**
3
- * write
4
- * @param characteristic
5
- * @param message
6
- */
7
- export const write = (board, serviceId, characteristicId, message, duration = 0) => {
8
- return new Promise((resolve, reject) => {
9
- if (board.device?.gatt?.connected) {
10
- const encoder = new TextEncoder();
11
- const characteristic = getCharacteristic(board, serviceId, characteristicId);
12
- if (characteristic) {
13
- const value = message + "\n";
14
- characteristic
15
- .writeValue(encoder.encode(value))
16
- .then(() => {
17
- setTimeout(() => {
18
- resolve();
19
- }, duration);
20
- })
21
- .catch((error) => {
22
- reject(error);
23
- });
24
- }
25
- else {
26
- reject(new Error("Characteristics is undefined"));
27
- }
28
- }
29
- else {
30
- reject(new Error("Device is not connected"));
31
- }
32
- });
33
- };