@hangtime/grip-connect 0.0.7 → 0.0.9
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 +49 -23
- package/package.json +2 -2
- package/src/connect.js +30 -52
- package/src/connect.ts +30 -54
- package/src/devices/moterboard.d.ts +6 -0
- package/src/devices/moterboard.js +103 -0
- package/src/devices/moterboard.ts +130 -0
- package/src/read.ts +6 -1
- package/tsconfig.json +1 -3
- package/build/characteristic.d.ts +0 -9
- package/build/characteristic.js +0 -15
- package/build/connect.d.ts +0 -7
- package/build/connect.js +0 -172
- package/build/devices/entralpi.d.ts +0 -2
- package/build/devices/entralpi.js +0 -52
- package/build/devices/index.d.ts +0 -3
- package/build/devices/index.js +0 -3
- package/build/devices/moterboard.d.ts +0 -2
- package/build/devices/moterboard.js +0 -79
- package/build/devices/tindeq.d.ts +0 -17
- package/build/devices/tindeq.js +0 -37
- package/build/devices/types.d.ts +0 -20
- package/build/devices/types.js +0 -1
- package/build/disconnect.d.ts +0 -6
- package/build/disconnect.js +0 -11
- package/build/index.d.ts +0 -6
- package/build/index.js +0 -6
- package/build/notify.d.ts +0 -4
- package/build/notify.js +0 -6
- package/build/read.d.ts +0 -6
- package/build/read.js +0 -44
- package/build/write.d.ts +0 -7
- package/build/write.js +0 -32
package/README.md
CHANGED
|
@@ -2,23 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
**Force-Sensing Climbing Training**
|
|
4
4
|
|
|
5
|
-
The objective of this project is to create a client that can establish connections with various
|
|
6
|
-
/ Plates used by climbers for strength measurement. Examples of such hangboards include the
|
|
5
|
+
The objective of this project is to create a Web Bluetooth API client that can establish connections with various
|
|
6
|
+
Force-Sensing Hangboards / Plates used by climbers for strength measurement. Examples of such hangboards include the
|
|
7
7
|
[Motherboard](https://griptonite.io/shop/motherboard/), [Climbro](https://climbro.com/),
|
|
8
8
|
[SmartBoard](https://www.smartboard-climbing.com/), [Entralpi](https://entralpi.com/) or
|
|
9
9
|
[Tindeq Progressor](https://tindeq.com/)
|
|
10
10
|
|
|
11
|
+
[Try it out](https://grip-connect.vercel.app/) - [Docs](https://stevie-ray.github.io/hangtime-grip-connect/) -
|
|
12
|
+
[Browser Support](https://caniuse.com/web-bluetooth)
|
|
13
|
+
|
|
11
14
|
## Roadmap
|
|
12
15
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- ➡️
|
|
16
|
+
- ✅ Griptonite Motherboard
|
|
17
|
+
- ✅️ Connect with devices
|
|
18
|
+
- ✅️ Read / Write / Notify using Bluetooth
|
|
19
|
+
- ➡️ Calibrate Devices
|
|
20
|
+
- ✅️ Output weight/force stream
|
|
21
|
+
- ✅ Tindeq Progressor
|
|
22
|
+
- ✅️ Connect with devices
|
|
23
|
+
- ✅️ Read / Write / Notify using Bluetooth
|
|
24
|
+
- ➡️ Calibrate Devices
|
|
25
|
+
- ➡️ Output weight/force stream
|
|
26
|
+
- ✅ Entralpi
|
|
27
|
+
- ✅️ Connect with devices
|
|
28
|
+
- ✅️ Read / Write / Notify using Bluetooth
|
|
29
|
+
- ➡️ Calibrate Devices
|
|
30
|
+
- ➡️ Output weight/force stream
|
|
31
|
+
- ➡️ Climbro
|
|
32
|
+
- ➡️ Connect with devices
|
|
33
|
+
- ➡️ Read / Write / Notify using Bluetooth
|
|
34
|
+
- ➡️ Calibrate Devices
|
|
35
|
+
- ➡️ Output weight/force stream
|
|
36
|
+
- ➡️ SmartBoard
|
|
37
|
+
- ➡️ Connect with devices
|
|
38
|
+
- ➡️ Read / Write / Notify using Bluetooth
|
|
39
|
+
- ➡️ Calibrate Devices
|
|
40
|
+
- ➡️ Output weight/force stream
|
|
22
41
|
|
|
23
42
|
## Development
|
|
24
43
|
|
|
@@ -61,25 +80,32 @@ motherboardButton.addEventListener("click", () => {
|
|
|
61
80
|
await read(Motherboard, "device", "hardware", 1000)
|
|
62
81
|
await read(Motherboard, "device", "firmware", 1000)
|
|
63
82
|
|
|
64
|
-
//
|
|
65
|
-
await write(Motherboard, "uart", "tx", "C",
|
|
66
|
-
|
|
67
|
-
// Read stream?
|
|
68
|
-
await write(Motherboard, "unknown", "01", "1", 2500)
|
|
69
|
-
await write(Motherboard, "unknown", "02", "0", 2500)
|
|
70
|
-
await write(Motherboard, "uart", "tx", "S30", 5000)
|
|
83
|
+
// read calibration (required before reading data)
|
|
84
|
+
await write(Motherboard, "uart", "tx", "C", 5000)
|
|
71
85
|
|
|
72
|
-
//
|
|
73
|
-
await write(Motherboard, "
|
|
74
|
-
await write(Motherboard, "unknown", "02", "1", 2500)
|
|
75
|
-
await write(Motherboard, "uart", "tx", "S30", 5000)
|
|
86
|
+
// start stream
|
|
87
|
+
await write(Motherboard, "uart", "tx", "S30", 15000)
|
|
76
88
|
|
|
89
|
+
// end stream
|
|
90
|
+
await write(Motherboard, "uart", "tx", "", 0)
|
|
77
91
|
// disconnect from device after we are done
|
|
78
92
|
disconnect(Motherboard)
|
|
79
93
|
})
|
|
80
94
|
})
|
|
81
95
|
```
|
|
82
96
|
|
|
97
|
+
## Credits
|
|
98
|
+
|
|
99
|
+
A special thank you to:
|
|
100
|
+
|
|
101
|
+
- [@CassimLadha](https://github.com/CassimLadha) for sharing insights on reading the Motherboards data.
|
|
102
|
+
- [@donaldharvey](https://github.com/donaldharvey) for a valuable example on connecting to the motherboard.
|
|
103
|
+
|
|
104
|
+
## Disclamer
|
|
105
|
+
|
|
106
|
+
THIS SOFTWARE IS NOT OFFICIALY SUPPORTED, SUPPLIED OR MAINTAINED BY THE DEVICE MANUFACTURER. BY USING THE SOFTWARE YOU
|
|
107
|
+
ARE ACKNOWLEDGEING THIS AND UNDERSTAND THAT USING THIS SOFTWARE WILL INVALIDATE THE MANUFACTURERS WARRANTY.
|
|
108
|
+
|
|
83
109
|
## License
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
BSD 2-Clause © [Stevie-Ray Hartog](https://github.com/Stevie-Ray)
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hangtime/grip-connect",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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": {
|
|
7
7
|
"build": "tsc --build"
|
|
8
8
|
},
|
|
9
9
|
"author": "Stevie-Ray Hartog <mail@stevie-ray.nl>",
|
|
10
|
-
"license": "
|
|
10
|
+
"license": "BSD-2-Clause",
|
|
11
11
|
"devDependencies": {},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
package/src/connect.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { notifyCallback } from "./notify";
|
|
2
|
+
import { handleMotherboardData } from "./devices/moterboard";
|
|
2
3
|
let server;
|
|
4
|
+
const receiveBuffer = [];
|
|
3
5
|
/**
|
|
4
6
|
* onDisconnected
|
|
5
7
|
* @param board
|
|
@@ -17,62 +19,38 @@ const onDisconnected = (event, board) => {
|
|
|
17
19
|
*/
|
|
18
20
|
const handleNotifications = (event, board) => {
|
|
19
21
|
const characteristic = event.target;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const elementKeys = [
|
|
37
|
-
"frames",
|
|
38
|
-
"cycle",
|
|
39
|
-
"unknown",
|
|
40
|
-
"eleven",
|
|
41
|
-
"dynamic1",
|
|
42
|
-
"pressure1",
|
|
43
|
-
"left",
|
|
44
|
-
"dynamic2",
|
|
45
|
-
"pressure2",
|
|
46
|
-
"right",
|
|
47
|
-
];
|
|
48
|
-
const dataObject = {};
|
|
49
|
-
if (parsedDecimalArray) {
|
|
50
|
-
elementKeys.forEach((key, index) => {
|
|
51
|
-
dataObject[key] = parsedDecimalArray[index];
|
|
52
|
-
});
|
|
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
|
+
}
|
|
53
38
|
}
|
|
39
|
+
}
|
|
40
|
+
else if (board.name === "ENTRALPI") {
|
|
41
|
+
// TODO: handle Entralpi notify
|
|
42
|
+
// characteristic.value!.getInt16(0) / 100;
|
|
54
43
|
if (notifyCallback) {
|
|
55
|
-
notifyCallback({ uuid: characteristic.uuid, value:
|
|
44
|
+
notifyCallback({ uuid: characteristic.uuid, value: value });
|
|
56
45
|
}
|
|
57
46
|
}
|
|
58
|
-
else if (
|
|
59
|
-
// TODO: handle
|
|
60
|
-
// notifyCallback({ uuid: characteristic.uuid, value: characteristic.value!.getInt8(0) / 100 })
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else if (board.name === "ENTRALPI") {
|
|
64
|
-
// TODO: handle Entralpi notify
|
|
65
|
-
// characteristic.value!.getInt16(0) / 100;
|
|
66
|
-
if (notifyCallback) {
|
|
67
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString });
|
|
47
|
+
else if (board.name === "Tindeq") {
|
|
48
|
+
// TODO: handle Tindeq notify
|
|
68
49
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
else {
|
|
74
|
-
if (notifyCallback) {
|
|
75
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString });
|
|
50
|
+
else {
|
|
51
|
+
if (notifyCallback) {
|
|
52
|
+
notifyCallback({ uuid: characteristic.uuid, value: value });
|
|
53
|
+
}
|
|
76
54
|
}
|
|
77
55
|
}
|
|
78
56
|
};
|
|
@@ -153,7 +131,7 @@ export const connect = async (board, onSuccess) => {
|
|
|
153
131
|
}
|
|
154
132
|
const device = await navigator.bluetooth.requestDevice({
|
|
155
133
|
filters: filters,
|
|
156
|
-
optionalServices: deviceServices
|
|
134
|
+
optionalServices: deviceServices,
|
|
157
135
|
});
|
|
158
136
|
board.device = device;
|
|
159
137
|
if (!board.device.gatt) {
|
package/src/connect.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Device } from "./devices/types"
|
|
2
2
|
import { notifyCallback } from "./notify"
|
|
3
|
+
import { handleMotherboardData } from "./devices/moterboard"
|
|
3
4
|
|
|
4
5
|
let server: BluetoothRemoteGATTServer
|
|
6
|
+
const receiveBuffer: number[] = []
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* onDisconnected
|
|
@@ -20,61 +22,35 @@ const onDisconnected = (event: Event, board: Device): void => {
|
|
|
20
22
|
*/
|
|
21
23
|
const handleNotifications = (event: Event, board: Device): void => {
|
|
22
24
|
const characteristic = event.target as BluetoothRemoteGATTCharacteristic
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const value = characteristic.value
|
|
26
|
+
if (value) {
|
|
27
|
+
if (board.name === "Motherboard") {
|
|
28
|
+
if (value) {
|
|
29
|
+
for (let i = 0; i < value.byteLength; i++) {
|
|
30
|
+
receiveBuffer.push(value.getUint8(i))
|
|
31
|
+
}
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Split the string into pairs of characters
|
|
36
|
-
const hexPairs: RegExpMatchArray | null = receivedString.match(/.{1,2}/g)
|
|
37
|
-
// Convert each hexadecimal pair to decimal
|
|
38
|
-
const parsedDecimalArray: number[] | undefined = hexPairs?.map((hexPair) => parseInt(hexPair, 16))
|
|
39
|
-
// Handle different types of data
|
|
40
|
-
if (characteristic.value!.byteLength === 20) {
|
|
41
|
-
const elementKeys = [
|
|
42
|
-
"frames",
|
|
43
|
-
"cycle",
|
|
44
|
-
"unknown",
|
|
45
|
-
"eleven",
|
|
46
|
-
"dynamic1",
|
|
47
|
-
"pressure1",
|
|
48
|
-
"left",
|
|
49
|
-
"dynamic2",
|
|
50
|
-
"pressure2",
|
|
51
|
-
"right",
|
|
52
|
-
]
|
|
53
|
-
const dataObject: { [key: string]: number } = {}
|
|
54
|
-
|
|
55
|
-
if (parsedDecimalArray) {
|
|
56
|
-
elementKeys.forEach((key: string, index: number) => {
|
|
57
|
-
dataObject[key] = parsedDecimalArray[index]
|
|
58
|
-
})
|
|
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
|
+
}
|
|
59
41
|
}
|
|
42
|
+
} else if (board.name === "ENTRALPI") {
|
|
43
|
+
// TODO: handle Entralpi notify
|
|
44
|
+
// characteristic.value!.getInt16(0) / 100;
|
|
60
45
|
if (notifyCallback) {
|
|
61
|
-
notifyCallback({ uuid: characteristic.uuid, value:
|
|
46
|
+
notifyCallback({ uuid: characteristic.uuid, value: value })
|
|
47
|
+
}
|
|
48
|
+
} else if (board.name === "Tindeq") {
|
|
49
|
+
// TODO: handle Tindeq notify
|
|
50
|
+
} else {
|
|
51
|
+
if (notifyCallback) {
|
|
52
|
+
notifyCallback({ uuid: characteristic.uuid, value: value })
|
|
62
53
|
}
|
|
63
|
-
} else if (characteristic.value!.byteLength === 14) {
|
|
64
|
-
// TODO: handle 14 byte data
|
|
65
|
-
// notifyCallback({ uuid: characteristic.uuid, value: characteristic.value!.getInt8(0) / 100 })
|
|
66
|
-
}
|
|
67
|
-
} else if (board.name === "ENTRALPI") {
|
|
68
|
-
// TODO: handle Entralpi notify
|
|
69
|
-
// characteristic.value!.getInt16(0) / 100;
|
|
70
|
-
if (notifyCallback) {
|
|
71
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString })
|
|
72
|
-
}
|
|
73
|
-
} else if (board.name === "Tindeq") {
|
|
74
|
-
// TODO: handle Tindeq notify
|
|
75
|
-
} else {
|
|
76
|
-
if (notifyCallback) {
|
|
77
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString })
|
|
78
54
|
}
|
|
79
55
|
}
|
|
80
56
|
}
|
|
@@ -97,7 +73,7 @@ const onConnected = async (board: Device, onSuccess: () => void): Promise<void>
|
|
|
97
73
|
|
|
98
74
|
if (matchingService) {
|
|
99
75
|
// Android bug: Introduce a delay before getting characteristics
|
|
100
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
101
77
|
|
|
102
78
|
const characteristics = await service.getCharacteristics()
|
|
103
79
|
|
|
@@ -166,7 +142,7 @@ export const connect = async (board: Device, onSuccess: () => void): Promise<voi
|
|
|
166
142
|
|
|
167
143
|
const device = await navigator.bluetooth.requestDevice({
|
|
168
144
|
filters: filters,
|
|
169
|
-
optionalServices: deviceServices
|
|
145
|
+
optionalServices: deviceServices,
|
|
170
146
|
})
|
|
171
147
|
|
|
172
148
|
board.device = device
|
|
@@ -181,7 +157,7 @@ export const connect = async (board: Device, onSuccess: () => void): Promise<voi
|
|
|
181
157
|
board.device.addEventListener("gattserverdisconnected", (event) => onDisconnected(event, board))
|
|
182
158
|
|
|
183
159
|
if (server.connected) {
|
|
184
|
-
await onConnected(board, onSuccess)
|
|
160
|
+
await onConnected(board, onSuccess)
|
|
185
161
|
}
|
|
186
162
|
} catch (error) {
|
|
187
163
|
console.error(error)
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { Device } from "./types";
|
|
2
2
|
export declare const Motherboard: Device;
|
|
3
|
+
/**
|
|
4
|
+
* handleMotherboardData
|
|
5
|
+
* @param uuid - Unique identifier
|
|
6
|
+
* @param receivedString - Received data string
|
|
7
|
+
*/
|
|
8
|
+
export declare function handleMotherboardData(uuid: string, receivedString: string): void;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { notifyCallback } from "../notify";
|
|
2
|
+
const PACKET_LENGTH = 32;
|
|
3
|
+
const NUM_SAMPLES = 3;
|
|
4
|
+
const CALIBRATION = [[], [], [], []];
|
|
1
5
|
export const Motherboard = {
|
|
2
6
|
name: "Motherboard",
|
|
3
7
|
companyId: 0x2a29,
|
|
@@ -77,3 +81,102 @@ export const Motherboard = {
|
|
|
77
81
|
},
|
|
78
82
|
],
|
|
79
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* applyCalibration
|
|
86
|
+
* @param sample
|
|
87
|
+
* @param calibration
|
|
88
|
+
*/
|
|
89
|
+
const applyCalibration = (sample, calibration) => {
|
|
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
|
|
95
|
+
let final = 0;
|
|
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;
|
|
102
|
+
}
|
|
103
|
+
// Iterate through the calibration data
|
|
104
|
+
for (let i = 1; i < calibration.length; i++) {
|
|
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
|
|
111
|
+
final =
|
|
112
|
+
calibration[i - 1][1] +
|
|
113
|
+
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) * (calibration[i][1] - calibration[i - 1][1]);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Return the calibrated value with the appropriate sign (positive/negative)
|
|
118
|
+
return sign * final;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* handleMotherboardData
|
|
122
|
+
* @param uuid - Unique identifier
|
|
123
|
+
* @param receivedString - Received data string
|
|
124
|
+
*/
|
|
125
|
+
export function handleMotherboardData(uuid, receivedString) {
|
|
126
|
+
const receivedTime = Date.now();
|
|
127
|
+
// Check if the line is entirely hex characters
|
|
128
|
+
const isAllHex = /^[0-9A-Fa-f]+$/g.test(receivedString);
|
|
129
|
+
// Handle streaming packet
|
|
130
|
+
if (isAllHex && receivedString.length === PACKET_LENGTH) {
|
|
131
|
+
// 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
|
+
// Translate header into packet, number of samples from the packet length
|
|
134
|
+
const packet = {
|
|
135
|
+
received: receivedTime,
|
|
136
|
+
sampleNum: new DataView(new Uint8Array(bytes).buffer).getUint16(0, true),
|
|
137
|
+
battRaw: new DataView(new Uint8Array(bytes).buffer).getUint16(2, true),
|
|
138
|
+
samples: [],
|
|
139
|
+
masses: [],
|
|
140
|
+
};
|
|
141
|
+
const dataView = new DataView(new Uint8Array(bytes).buffer);
|
|
142
|
+
for (let i = 0; i < NUM_SAMPLES; i++) {
|
|
143
|
+
const sampleStart = 4 + 3 * i;
|
|
144
|
+
// Use DataView to read the 24-bit unsigned integer
|
|
145
|
+
const rawValue = dataView.getUint8(sampleStart) |
|
|
146
|
+
(dataView.getUint8(sampleStart + 1) << 8) |
|
|
147
|
+
(dataView.getUint8(sampleStart + 2) << 16);
|
|
148
|
+
// Ensure unsigned 32-bit integer
|
|
149
|
+
packet.samples[i] = rawValue >>> 0;
|
|
150
|
+
if (packet.samples[i] >= 0x7fffff) {
|
|
151
|
+
packet.samples[i] -= 0x1000000;
|
|
152
|
+
}
|
|
153
|
+
// TODO: make sure device is calibrated
|
|
154
|
+
if (!CALIBRATION[0].length)
|
|
155
|
+
return;
|
|
156
|
+
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i]);
|
|
157
|
+
}
|
|
158
|
+
const left = packet.masses[0];
|
|
159
|
+
const center = packet.masses[1];
|
|
160
|
+
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
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else if ((receivedString.match(/,/g) || []).length === 3) {
|
|
172
|
+
console.log(receivedString);
|
|
173
|
+
// if the returned notification is a calibration string add them to the array
|
|
174
|
+
const parts = receivedString.split(",");
|
|
175
|
+
const numericParts = parts.map((x) => parseFloat(x));
|
|
176
|
+
CALIBRATION[numericParts[0]].push(numericParts.slice(1));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// unhanded data
|
|
180
|
+
console.log(receivedString);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Device } from "./types"
|
|
2
|
+
import { notifyCallback } from "../notify"
|
|
3
|
+
|
|
4
|
+
const PACKET_LENGTH: number = 32
|
|
5
|
+
const NUM_SAMPLES: number = 3
|
|
6
|
+
const CALIBRATION = [[], [], [], []]
|
|
2
7
|
|
|
3
8
|
export const Motherboard: Device = {
|
|
4
9
|
name: "Motherboard",
|
|
@@ -79,3 +84,128 @@ export const Motherboard: Device = {
|
|
|
79
84
|
},
|
|
80
85
|
],
|
|
81
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* applyCalibration
|
|
89
|
+
* @param sample
|
|
90
|
+
* @param calibration
|
|
91
|
+
*/
|
|
92
|
+
const applyCalibration = (sample: number, calibration: number[][]): number => {
|
|
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
|
|
100
|
+
let final: number = 0;
|
|
101
|
+
|
|
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;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Iterate through the calibration data
|
|
112
|
+
for (let i = 1; i < calibration.length; i++) {
|
|
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];
|
|
116
|
+
|
|
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
|
|
120
|
+
final =
|
|
121
|
+
calibration[i - 1][1] +
|
|
122
|
+
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) * (calibration[i][1] - calibration[i - 1][1]);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Return the calibrated value with the appropriate sign (positive/negative)
|
|
127
|
+
return sign * final;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface Packet {
|
|
131
|
+
received: number
|
|
132
|
+
sampleNum: number
|
|
133
|
+
battRaw: number
|
|
134
|
+
samples: number[]
|
|
135
|
+
masses: number[]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* handleMotherboardData
|
|
140
|
+
* @param uuid - Unique identifier
|
|
141
|
+
* @param receivedString - Received data string
|
|
142
|
+
*/
|
|
143
|
+
export function handleMotherboardData(uuid: string, receivedString: string): void {
|
|
144
|
+
const receivedTime: number = Date.now()
|
|
145
|
+
|
|
146
|
+
// Check if the line is entirely hex characters
|
|
147
|
+
const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedString);
|
|
148
|
+
|
|
149
|
+
// Handle streaming packet
|
|
150
|
+
if (isAllHex && receivedString.length === PACKET_LENGTH) {
|
|
151
|
+
// 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)}`),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
// Translate header into packet, number of samples from the packet length
|
|
157
|
+
const packet: Packet = {
|
|
158
|
+
received: receivedTime,
|
|
159
|
+
sampleNum: new DataView(new Uint8Array(bytes).buffer).getUint16(0, true),
|
|
160
|
+
battRaw: new DataView(new Uint8Array(bytes).buffer).getUint16(2, true),
|
|
161
|
+
samples: [],
|
|
162
|
+
masses: [],
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const dataView = new DataView(new Uint8Array(bytes).buffer);
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < NUM_SAMPLES; i++) {
|
|
168
|
+
const sampleStart: number = 4 + 3 * i
|
|
169
|
+
// Use DataView to read the 24-bit unsigned integer
|
|
170
|
+
const rawValue = dataView.getUint8(sampleStart) |
|
|
171
|
+
(dataView.getUint8(sampleStart + 1) << 8) |
|
|
172
|
+
(dataView.getUint8(sampleStart + 2) << 16);
|
|
173
|
+
|
|
174
|
+
// Ensure unsigned 32-bit integer
|
|
175
|
+
packet.samples[i] = rawValue >>> 0;
|
|
176
|
+
|
|
177
|
+
if (packet.samples[i] >= 0x7fffff) {
|
|
178
|
+
packet.samples[i] -= 0x1000000;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// TODO: make sure device is calibrated
|
|
182
|
+
if (!CALIBRATION[0].length) return
|
|
183
|
+
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i])
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const left: number = packet.masses[0]
|
|
187
|
+
const center: number = packet.masses[1]
|
|
188
|
+
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)
|
|
201
|
+
// if the returned notification is a calibration string add them to the array
|
|
202
|
+
const parts: string[] = receivedString.split(",")
|
|
203
|
+
const numericParts: number[] = parts.map((x) => parseFloat(x))
|
|
204
|
+
;(CALIBRATION[numericParts[0]] as number[][]).push(numericParts.slice(1))
|
|
205
|
+
} else {
|
|
206
|
+
// unhanded data
|
|
207
|
+
console.log(receivedString)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
package/src/read.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { getCharacteristic } from "./characteristic"
|
|
|
6
6
|
* read
|
|
7
7
|
* @param characteristic
|
|
8
8
|
*/
|
|
9
|
-
export const read = (
|
|
9
|
+
export const read = (
|
|
10
|
+
board: Device,
|
|
11
|
+
serviceId: string,
|
|
12
|
+
characteristicId: string,
|
|
13
|
+
duration: number = 0,
|
|
14
|
+
): Promise<void> => {
|
|
10
15
|
return new Promise((resolve, reject) => {
|
|
11
16
|
if (board.device?.gatt?.connected) {
|
|
12
17
|
const characteristic = getCharacteristic(board, serviceId, characteristicId)
|
package/tsconfig.json
CHANGED
|
@@ -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;
|
package/build/characteristic.js
DELETED
|
@@ -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
|
-
};
|
package/build/connect.d.ts
DELETED
package/build/connect.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { notifyCallback } from "./notify";
|
|
2
|
-
let server;
|
|
3
|
-
/**
|
|
4
|
-
* onDisconnected
|
|
5
|
-
* @param board
|
|
6
|
-
* @param event
|
|
7
|
-
*/
|
|
8
|
-
const onDisconnected = (event, board) => {
|
|
9
|
-
board.device = undefined;
|
|
10
|
-
const device = event.target;
|
|
11
|
-
console.log(`Device ${device.name} is disconnected.`);
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* handleNotifications
|
|
15
|
-
* @param event
|
|
16
|
-
* @param onNotify
|
|
17
|
-
*/
|
|
18
|
-
const handleNotifications = (event, board) => {
|
|
19
|
-
const characteristic = event.target;
|
|
20
|
-
const receivedData = new Uint8Array(characteristic.value.buffer);
|
|
21
|
-
// Create an array to store the parsed decimal values
|
|
22
|
-
const decimalArray = [];
|
|
23
|
-
// Iterate through each byte and convert to decimal
|
|
24
|
-
for (let i = 0; i < receivedData.length; i++) {
|
|
25
|
-
decimalArray.push(receivedData[i]);
|
|
26
|
-
}
|
|
27
|
-
// Convert the decimal array to a string representation
|
|
28
|
-
const receivedString = String.fromCharCode(...decimalArray);
|
|
29
|
-
if (board.name === "Motherboard") {
|
|
30
|
-
// Split the string into pairs of characters
|
|
31
|
-
const hexPairs = receivedString.match(/.{1,2}/g);
|
|
32
|
-
// Convert each hexadecimal pair to decimal
|
|
33
|
-
const parsedDecimalArray = hexPairs?.map((hexPair) => parseInt(hexPair, 16));
|
|
34
|
-
// Handle different types of data
|
|
35
|
-
if (characteristic.value.byteLength === 20) {
|
|
36
|
-
const elementKeys = [
|
|
37
|
-
"frames",
|
|
38
|
-
"cycle",
|
|
39
|
-
"unknown",
|
|
40
|
-
"eleven",
|
|
41
|
-
"dynamic1",
|
|
42
|
-
"pressure1",
|
|
43
|
-
"left",
|
|
44
|
-
"dynamic2",
|
|
45
|
-
"pressure2",
|
|
46
|
-
"right",
|
|
47
|
-
];
|
|
48
|
-
const dataObject = {};
|
|
49
|
-
if (parsedDecimalArray) {
|
|
50
|
-
elementKeys.forEach((key, index) => {
|
|
51
|
-
dataObject[key] = parsedDecimalArray[index];
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (notifyCallback) {
|
|
55
|
-
notifyCallback({ uuid: characteristic.uuid, value: dataObject });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else if (characteristic.value.byteLength === 14) {
|
|
59
|
-
// TODO: handle 14 byte data
|
|
60
|
-
// notifyCallback({ uuid: characteristic.uuid, value: characteristic.value!.getInt8(0) / 100 })
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else if (board.name === "ENTRALPI") {
|
|
64
|
-
// TODO: handle Entralpi notify
|
|
65
|
-
// characteristic.value!.getInt16(0) / 100;
|
|
66
|
-
if (notifyCallback) {
|
|
67
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
else if (board.name === "Tindeq") {
|
|
71
|
-
// TODO: handle Tindeq notify
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
if (notifyCallback) {
|
|
75
|
-
notifyCallback({ uuid: characteristic.uuid, value: receivedString });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* onConnected
|
|
81
|
-
* @param event
|
|
82
|
-
* @param board
|
|
83
|
-
*/
|
|
84
|
-
const onConnected = async (board, onSuccess) => {
|
|
85
|
-
try {
|
|
86
|
-
const services = await server?.getPrimaryServices();
|
|
87
|
-
if (!services || services.length === 0) {
|
|
88
|
-
console.error("No services found");
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
for (const service of services) {
|
|
92
|
-
const matchingService = board.services.find((boardService) => boardService.uuid === service.uuid);
|
|
93
|
-
if (matchingService) {
|
|
94
|
-
// Android bug: Introduce a delay before getting characteristics
|
|
95
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
96
|
-
const characteristics = await service.getCharacteristics();
|
|
97
|
-
for (const characteristic of matchingService.characteristics) {
|
|
98
|
-
const matchingCharacteristic = characteristics.find((char) => char.uuid === characteristic.uuid);
|
|
99
|
-
if (matchingCharacteristic) {
|
|
100
|
-
const element = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid);
|
|
101
|
-
if (element) {
|
|
102
|
-
element.characteristic = matchingCharacteristic;
|
|
103
|
-
// notify
|
|
104
|
-
if (element.id === "rx") {
|
|
105
|
-
matchingCharacteristic.startNotifications();
|
|
106
|
-
matchingCharacteristic.addEventListener("characteristicvaluechanged", (event) => handleNotifications(event, board));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
console.warn(`Characteristic ${characteristic.uuid} not found in service ${service.uuid}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Call the onSuccess callback after successful connection and setup
|
|
117
|
-
onSuccess();
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
console.error(error);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
/**
|
|
124
|
-
* Return all service UUIDs
|
|
125
|
-
* @param device
|
|
126
|
-
*/
|
|
127
|
-
function getAllServiceUUIDs(device) {
|
|
128
|
-
return device.services.map((service) => service.uuid);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Connect to the BluetoothDevice
|
|
132
|
-
* @param device
|
|
133
|
-
* @param onSuccess
|
|
134
|
-
*/
|
|
135
|
-
export const connect = async (board, onSuccess) => {
|
|
136
|
-
try {
|
|
137
|
-
const deviceServices = getAllServiceUUIDs(board);
|
|
138
|
-
// setup filter list
|
|
139
|
-
const filters = [];
|
|
140
|
-
if (board.name) {
|
|
141
|
-
filters.push({
|
|
142
|
-
name: board.name,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
if (board.companyId) {
|
|
146
|
-
filters.push({
|
|
147
|
-
manufacturerData: [
|
|
148
|
-
{
|
|
149
|
-
companyIdentifier: board.companyId,
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const device = await navigator.bluetooth.requestDevice({
|
|
155
|
-
filters: filters,
|
|
156
|
-
optionalServices: deviceServices
|
|
157
|
-
});
|
|
158
|
-
board.device = device;
|
|
159
|
-
if (!board.device.gatt) {
|
|
160
|
-
console.error("GATT is not available on this device");
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
server = await board.device?.gatt?.connect();
|
|
164
|
-
board.device.addEventListener("gattserverdisconnected", (event) => onDisconnected(event, board));
|
|
165
|
-
if (server.connected) {
|
|
166
|
-
await onConnected(board, onSuccess);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
console.error(error);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
@@ -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
|
-
};
|
package/build/devices/index.d.ts
DELETED
package/build/devices/index.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
export const Motherboard = {
|
|
2
|
-
name: "Motherboard",
|
|
3
|
-
companyId: 0x2a29,
|
|
4
|
-
services: [
|
|
5
|
-
{
|
|
6
|
-
name: "Device Information",
|
|
7
|
-
id: "device",
|
|
8
|
-
uuid: "0000180a-0000-1000-8000-00805f9b34fb",
|
|
9
|
-
characteristics: [
|
|
10
|
-
// {
|
|
11
|
-
// name: 'Serial Number (Blocked)',
|
|
12
|
-
// id: 'serial'
|
|
13
|
-
// uuid: '00002a25-0000-1000-8000-00805f9b34fb'
|
|
14
|
-
// },
|
|
15
|
-
{
|
|
16
|
-
name: "Firmware Revision",
|
|
17
|
-
id: "firmware",
|
|
18
|
-
uuid: "00002a26-0000-1000-8000-00805f9b34fb",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: "Hardware Revision",
|
|
22
|
-
id: "hardware",
|
|
23
|
-
uuid: "00002a27-0000-1000-8000-00805f9b34fb",
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: "Manufacturer Name",
|
|
27
|
-
id: "manufacturer",
|
|
28
|
-
uuid: "00002a29-0000-1000-8000-00805f9b34fb",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "Battery Service",
|
|
34
|
-
id: "battery",
|
|
35
|
-
uuid: "0000180f-0000-1000-8000-00805f9b34fb",
|
|
36
|
-
characteristics: [
|
|
37
|
-
{
|
|
38
|
-
name: "Battery Level",
|
|
39
|
-
id: "level",
|
|
40
|
-
uuid: "00002a19-0000-1000-8000-00805f9b34fb",
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "Unknown Service",
|
|
46
|
-
id: "unknown",
|
|
47
|
-
uuid: "10ababcd-15e1-28ff-de13-725bea03b127",
|
|
48
|
-
characteristics: [
|
|
49
|
-
{
|
|
50
|
-
name: "Unknown 01",
|
|
51
|
-
id: "01",
|
|
52
|
-
uuid: "10ab1524-15e1-28ff-de13-725bea03b127",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "Unknown 02",
|
|
56
|
-
id: "02",
|
|
57
|
-
uuid: "10ab1525-15e1-28ff-de13-725bea03b127",
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "UART Nordic Service",
|
|
63
|
-
id: "uart",
|
|
64
|
-
uuid: "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
|
|
65
|
-
characteristics: [
|
|
66
|
-
{
|
|
67
|
-
name: "TX",
|
|
68
|
-
id: "tx",
|
|
69
|
-
uuid: "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "RX",
|
|
73
|
-
id: "rx",
|
|
74
|
-
uuid: "6e400003-b5a3-f393-e0a9-e50e24dcca9e",
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
};
|
|
@@ -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
|
-
};
|
package/build/devices/tindeq.js
DELETED
|
@@ -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
|
-
};
|
package/build/devices/types.d.ts
DELETED
|
@@ -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 {};
|
package/build/devices/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/build/disconnect.d.ts
DELETED
package/build/disconnect.js
DELETED
package/build/index.d.ts
DELETED
package/build/index.js
DELETED
package/build/notify.d.ts
DELETED
package/build/notify.js
DELETED
package/build/read.d.ts
DELETED
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
package/build/write.js
DELETED
|
@@ -1,32 +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
|
-
characteristic
|
|
14
|
-
.writeValue(encoder.encode(message))
|
|
15
|
-
.then(() => {
|
|
16
|
-
setTimeout(() => {
|
|
17
|
-
resolve();
|
|
18
|
-
}, duration);
|
|
19
|
-
})
|
|
20
|
-
.catch((error) => {
|
|
21
|
-
reject(error);
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
reject(new Error("Characteristics is undefined"));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
reject(new Error("Device is not connected"));
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
};
|