@hangtime/grip-connect 0.1.2 → 0.2.1
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 +22 -25
- package/package.json +2 -2
- package/src/battery.ts +21 -0
- package/src/calibration.ts +4 -4
- package/src/commands/index.ts +2 -2
- package/src/commands/{tindeq.ts → progressor.ts} +7 -3
- package/src/commands/types.ts +5 -5
- package/src/connect.ts +74 -9
- package/src/data.ts +133 -0
- package/src/devices/entralpi.ts +0 -17
- package/src/devices/index.ts +1 -1
- package/src/devices/motherboard.ts +0 -134
- package/src/devices/{tindeq.ts → progressor.ts} +17 -5
- package/src/disconnect.ts +3 -3
- package/src/index.ts +10 -2
- package/src/info.ts +25 -0
- package/src/is-connected.ts +11 -0
- package/src/notify.ts +2 -2
- package/src/read.ts +2 -1
- package/src/stop.ts +7 -7
- package/src/stream.ts +16 -11
- package/src/write.ts +10 -5
- package/tsconfig.json +2 -2
- package/src/calibration.d.ts +0 -6
- package/src/calibration.js +0 -16
- package/src/characteristic.d.ts +0 -9
- package/src/characteristic.js +0 -15
- package/src/commands/climbro.d.ts +0 -6
- package/src/commands/climbro.js +0 -5
- package/src/commands/entralpi.d.ts +0 -6
- package/src/commands/entralpi.js +0 -5
- package/src/commands/index.d.ts +0 -5
- package/src/commands/index.js +0 -5
- package/src/commands/motherboard.d.ts +0 -6
- package/src/commands/motherboard.js +0 -13
- package/src/commands/smartboard.d.ts +0 -6
- package/src/commands/smartboard.js +0 -5
- package/src/commands/tindeq.d.ts +0 -11
- package/src/commands/tindeq.js +0 -23
- package/src/commands/types.d.ts +0 -18
- package/src/commands/types.js +0 -1
- package/src/connect.d.ts +0 -7
- package/src/connect.js +0 -150
- package/src/devices/climbro.d.ts +0 -2
- package/src/devices/climbro.js +0 -4
- package/src/devices/entralpi.d.ts +0 -8
- package/src/devices/entralpi.js +0 -68
- package/src/devices/index.d.ts +0 -5
- package/src/devices/index.js +0 -5
- package/src/devices/motherboard.d.ts +0 -8
- package/src/devices/motherboard.js +0 -189
- package/src/devices/smartboard.d.ts +0 -2
- package/src/devices/smartboard.js +0 -4
- package/src/devices/tindeq.d.ts +0 -2
- package/src/devices/tindeq.js +0 -22
- package/src/devices/types.d.ts +0 -20
- package/src/devices/types.js +0 -1
- package/src/disconnect.d.ts +0 -6
- package/src/disconnect.js +0 -11
- package/src/index.d.ts +0 -8
- package/src/index.js +0 -8
- package/src/notify.d.ts +0 -4
- package/src/notify.js +0 -6
- package/src/read.d.ts +0 -6
- package/src/read.js +0 -44
- package/src/stop.d.ts +0 -6
- package/src/stop.js +0 -19
- package/src/stream.d.ts +0 -6
- package/src/stream.js +0 -31
- package/src/write.d.ts +0 -7
- package/src/write.js +0 -34
package/README.md
CHANGED
|
@@ -13,30 +13,29 @@ Force-Sensing Hangboards / Plates used by climbers for strength measurement. Exa
|
|
|
13
13
|
|
|
14
14
|
## Roadmap
|
|
15
15
|
|
|
16
|
+
**Help wanted:** Do you own any of these devices? Use Google Chrome's Bluetooth Internals
|
|
17
|
+
`chrome://bluetooth-internals/#devices` and press `Start Scan` to look for your device, click on `Inspect` and share all
|
|
18
|
+
available services with us.
|
|
19
|
+
|
|
16
20
|
- ✅ Griptonite Motherboard
|
|
17
21
|
- ✅️ Connect with devices
|
|
18
22
|
- ✅️ Read / Write / Notify using Bluetooth
|
|
19
|
-
- ➡️ Calibrate Devices
|
|
20
23
|
- ✅️ Output weight/force stream
|
|
21
24
|
- ✅ Tindeq Progressor
|
|
22
25
|
- ✅️ Connect with devices
|
|
23
26
|
- ✅️ Read / Write / Notify using Bluetooth
|
|
24
|
-
-
|
|
25
|
-
- ➡️ Output weight/force stream
|
|
27
|
+
- ✅️ Output weight/force stream
|
|
26
28
|
- ✅ Entralpi
|
|
27
29
|
- ✅️ Connect with devices
|
|
28
30
|
- ✅️ Read / Write / Notify using Bluetooth
|
|
29
|
-
-
|
|
30
|
-
- ➡️ Output weight/force stream
|
|
31
|
+
- ✅️️ Output weight/force stream
|
|
31
32
|
- ➡️ Climbro
|
|
32
33
|
- ➡️ Connect with devices
|
|
33
34
|
- ➡️ Read / Write / Notify using Bluetooth
|
|
34
|
-
- ➡️ Calibrate Devices
|
|
35
35
|
- ➡️ Output weight/force stream
|
|
36
36
|
- ➡️ SmartBoard
|
|
37
37
|
- ➡️ Connect with devices
|
|
38
38
|
- ➡️ Read / Write / Notify using Bluetooth
|
|
39
|
-
- ➡️ Calibrate Devices
|
|
40
39
|
- ➡️ Output weight/force stream
|
|
41
40
|
|
|
42
41
|
## Development
|
|
@@ -53,37 +52,32 @@ npm install
|
|
|
53
52
|
$ npm install @hangtime/grip-connect
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
## Example usage (Motherboard)
|
|
55
|
+
## Example usage (with a Motherboard)
|
|
57
56
|
|
|
58
|
-
Simply importing the utilities you need from `@hangtime/grip-connect`.
|
|
59
|
-
`Motherboard`, `Tindeq` and `Entralpi`.
|
|
57
|
+
Simply importing the utilities you need from `@hangtime/grip-connect`.
|
|
60
58
|
|
|
61
59
|
```html
|
|
62
60
|
<button id="motherboard" type="button">Connect Motherboard</button>
|
|
63
61
|
```
|
|
64
62
|
|
|
65
63
|
```js
|
|
66
|
-
import { Motherboard,
|
|
64
|
+
import { Motherboard, battery, connect, disconnect, info, notify, stream } from "@hangtime/grip-connect"
|
|
67
65
|
|
|
68
66
|
const motherboardButton = document.querySelector("#motherboard")
|
|
69
67
|
|
|
70
68
|
motherboardButton.addEventListener("click", () => {
|
|
71
69
|
connect(Motherboard, async () => {
|
|
72
|
-
// Listen for notifications
|
|
70
|
+
// Listen for stream notifications
|
|
73
71
|
notify((data) => {
|
|
72
|
+
// data: { massTotal: 0, massLeft: 0, massRight: 0, massCenter: 0 }
|
|
74
73
|
console.log(data)
|
|
75
74
|
})
|
|
76
75
|
|
|
77
76
|
// Read battery + device info
|
|
78
|
-
await
|
|
79
|
-
await
|
|
80
|
-
await read(Motherboard, "device", "hardware", 250)
|
|
81
|
-
await read(Motherboard, "device", "firmware", 250)
|
|
82
|
-
|
|
83
|
-
// Read calibration (required before reading data)
|
|
84
|
-
await calibration(Motherboard)
|
|
77
|
+
await battery(Motherboard)
|
|
78
|
+
await info(Motherboard)
|
|
85
79
|
|
|
86
|
-
// Start streaming (for a minute) remove parameter for a continues stream
|
|
80
|
+
// Start weight streaming (for a minute) remove parameter for a continues stream
|
|
87
81
|
await stream(Motherboard, 60000)
|
|
88
82
|
|
|
89
83
|
// Manually call stop method if stream is continues
|
|
@@ -100,13 +94,16 @@ motherboardButton.addEventListener("click", () => {
|
|
|
100
94
|
A special thank you to:
|
|
101
95
|
|
|
102
96
|
- [@CassimLadha](https://github.com/CassimLadha) for sharing insights on reading the Motherboards data.
|
|
103
|
-
- [@donaldharvey](https://github.com/donaldharvey) for a valuable example on connecting to the
|
|
104
|
-
- [@ecstrema](https://github.com/ecstrema) for providing
|
|
97
|
+
- [@donaldharvey](https://github.com/donaldharvey) for a valuable example on connecting to the Motherboard.
|
|
98
|
+
- [@ecstrema](https://github.com/ecstrema) for providing [examples](https://github.com/ecstrema/entralpi-games) on how
|
|
99
|
+
to play games with the Entralpi.
|
|
100
|
+
- [@StuartLittlefair](https://github.com/StuartLittlefair) for his
|
|
101
|
+
[PyTindeq](https://github.com/StuartLittlefair/PyTindeq) implementation.
|
|
105
102
|
|
|
106
|
-
##
|
|
103
|
+
## Disclaimer
|
|
107
104
|
|
|
108
|
-
THIS SOFTWARE IS NOT
|
|
109
|
-
ARE
|
|
105
|
+
THIS SOFTWARE IS NOT OFFICIALLY SUPPORTED, SUPPLIED OR MAINTAINED BY THE DEVICE MANUFACTURER. BY USING THE SOFTWARE YOU
|
|
106
|
+
ARE ACKNOWLEDGING THIS AND UNDERSTAND THAT USING THIS SOFTWARE WILL INVALIDATE THE MANUFACTURERS WARRANTY.
|
|
110
107
|
|
|
111
108
|
## License
|
|
112
109
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hangtime/grip-connect",
|
|
3
|
-
"version": "0.1
|
|
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",
|
|
3
|
+
"version": "0.2.1",
|
|
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 Griptonite Motherboard, Climbro, SmartBoard, Entralpi or Tindeq Progressor",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc --build"
|
package/src/battery.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Device } from "./devices/types"
|
|
2
|
+
import { write } from "./write"
|
|
3
|
+
import { read } from "./read"
|
|
4
|
+
import { isConnected } from "./is-connected"
|
|
5
|
+
import { Motherboard, Progressor } from "./devices"
|
|
6
|
+
import { ProgressorCommands } from "./commands"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get Battery / Voltage information
|
|
10
|
+
* @param board
|
|
11
|
+
*/
|
|
12
|
+
export const battery = async (board: Device): Promise<void> => {
|
|
13
|
+
if (isConnected(board)) {
|
|
14
|
+
if (board.name === "Motherboard") {
|
|
15
|
+
await read(Motherboard, "battery", "level", 250)
|
|
16
|
+
}
|
|
17
|
+
if (board.name && board.name.startsWith("Progressor")) {
|
|
18
|
+
await write(Progressor, "progressor", "tx", ProgressorCommands.GET_BATT_VLTG, 250)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/calibration.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Device } from "./devices/types"
|
|
2
|
+
import { isConnected } from "./is-connected"
|
|
3
|
+
import { write } from "./write"
|
|
2
4
|
import { Motherboard } from "./devices"
|
|
3
5
|
import { MotherboardCommands } from "./commands"
|
|
4
|
-
import { write } from "./write"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* write command to get calibration
|
|
8
9
|
* @param board
|
|
9
10
|
*/
|
|
10
11
|
export const calibration = async (board: Device): Promise<void> => {
|
|
11
|
-
if (
|
|
12
|
-
if (board.device.gatt?.connected) {
|
|
12
|
+
if (isConnected(board)) {
|
|
13
13
|
if (board.name === "Motherboard") {
|
|
14
|
-
await write(Motherboard, "uart", "tx",
|
|
14
|
+
await write(Motherboard, "uart", "tx", MotherboardCommands.GET_CALIBRATION, 2500)
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
}
|
package/src/commands/index.ts
CHANGED
|
@@ -4,6 +4,6 @@ export { EntralpiCommands } from "./entralpi"
|
|
|
4
4
|
|
|
5
5
|
export { MotherboardCommands } from "./motherboard"
|
|
6
6
|
|
|
7
|
-
export {
|
|
7
|
+
export { ProgressorCommands } from "./progressor"
|
|
8
8
|
|
|
9
|
-
export {
|
|
9
|
+
export { SmartBoardCommands } from "./smartboard"
|
|
@@ -3,7 +3,7 @@ import { Commands } from "../commands/types"
|
|
|
3
3
|
* Warning:
|
|
4
4
|
* Using other commands can seriously harm your device
|
|
5
5
|
*/
|
|
6
|
-
export const
|
|
6
|
+
export const ProgressorCommands: Commands = {
|
|
7
7
|
TARE_SCALE: "d", // 0x64,
|
|
8
8
|
START_WEIGHT_MEAS: "e", // 0x65,
|
|
9
9
|
STOP_WEIGHT_MEAS: "f", // 0x66,
|
|
@@ -11,14 +11,18 @@ export const TindeqCommands: Commands = {
|
|
|
11
11
|
START_PEAK_RFD_MEAS_SERIES: "h", // 0x68,
|
|
12
12
|
ADD_CALIB_POINT: "i", // 0x69,
|
|
13
13
|
SAVE_CALIB: "j", // 0x6a,
|
|
14
|
-
|
|
14
|
+
GET_FW_VERSION: "k", // 0x6b,
|
|
15
15
|
GET_ERR_INFO: "l", // 0x6c,
|
|
16
16
|
CLR_ERR_INFO: "m", // 0x6d,
|
|
17
17
|
SLEEP: "n", // 0x6e,
|
|
18
18
|
GET_BATT_VLTG: "o", // 0x6f,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The Progressor returns a Uint8Array.
|
|
23
|
+
* The first item [0] is the type of response it returns
|
|
24
|
+
*/
|
|
25
|
+
export const ProgressorResponses = {
|
|
22
26
|
COMMAND_RESPONSE: 0,
|
|
23
27
|
WEIGHT_MEASURE: 1,
|
|
24
28
|
LOW_BATTERY_WARNING: 2,
|
package/src/commands/types.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
export interface Commands {
|
|
2
|
-
// Motherboard,
|
|
2
|
+
// Motherboard, Progressor
|
|
3
3
|
START_WEIGHT_MEAS?: string
|
|
4
4
|
STOP_WEIGHT_MEAS?: string
|
|
5
5
|
SLEEP?: number | string
|
|
6
|
-
// Motherboard
|
|
7
|
-
GET_TEXT?: string
|
|
8
6
|
GET_SERIAL?: string
|
|
7
|
+
// Griptonite Motherboard
|
|
8
|
+
GET_TEXT?: string
|
|
9
9
|
DEBUG_STREAM?: string
|
|
10
10
|
GET_CALIBRATION?: string
|
|
11
|
-
// Tindeq
|
|
11
|
+
// Tindeq Progressor
|
|
12
12
|
TARE_SCALE?: string
|
|
13
13
|
START_PEAK_RFD_MEAS?: string
|
|
14
14
|
START_PEAK_RFD_MEAS_SERIES?: string
|
|
15
15
|
ADD_CALIB_POINT?: string
|
|
16
16
|
SAVE_CALIB?: string
|
|
17
|
-
|
|
17
|
+
GET_FW_VERSION?: string
|
|
18
18
|
GET_ERR_INFO?: string
|
|
19
19
|
CLR_ERR_INFO?: string
|
|
20
20
|
GET_BATT_VLTG?: string
|
package/src/connect.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Device } from "./devices/types"
|
|
2
|
+
import { ProgressorCommands, ProgressorResponses } from "./commands/progressor"
|
|
2
3
|
import { notifyCallback } from "./notify"
|
|
3
|
-
import { handleMotherboardData } from "./
|
|
4
|
-
import {
|
|
4
|
+
import { handleMotherboardData } from "./data"
|
|
5
|
+
import { lastWrite } from "./write"
|
|
5
6
|
|
|
6
7
|
let server: BluetoothRemoteGATTServer
|
|
7
8
|
const receiveBuffer: number[] = []
|
|
@@ -24,6 +25,19 @@ const onDisconnected = (event: Event, board: Device): void => {
|
|
|
24
25
|
const handleNotifications = (event: Event, board: Device): void => {
|
|
25
26
|
const characteristic: BluetoothRemoteGATTCharacteristic = event.target as BluetoothRemoteGATTCharacteristic
|
|
26
27
|
const value: DataView | undefined = characteristic.value
|
|
28
|
+
|
|
29
|
+
function _unpackFloat(bytes: Uint8Array) {
|
|
30
|
+
const view = new DataView(new ArrayBuffer(4))
|
|
31
|
+
for (let i = 0; i < 4; i++) {
|
|
32
|
+
view.setUint8(i, bytes[i])
|
|
33
|
+
}
|
|
34
|
+
return view.getFloat32(0, true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// function _unpackInt(bytes: Uint8Array) {
|
|
38
|
+
// return (bytes[1] << 8) + bytes[0];
|
|
39
|
+
// }
|
|
40
|
+
|
|
27
41
|
if (value) {
|
|
28
42
|
if (board.name === "Motherboard") {
|
|
29
43
|
for (let i: number = 0; i < value.byteLength; i++) {
|
|
@@ -43,10 +57,62 @@ const handleNotifications = (event: Event, board: Device): void => {
|
|
|
43
57
|
const buffer: ArrayBuffer = value.buffer
|
|
44
58
|
const rawData: DataView = new DataView(buffer)
|
|
45
59
|
const receivedData: number = rawData.getUint16(0) / 100
|
|
46
|
-
|
|
60
|
+
if (notifyCallback) {
|
|
61
|
+
notifyCallback({
|
|
62
|
+
uuid: characteristic.uuid,
|
|
63
|
+
value: {
|
|
64
|
+
massTotal: receivedData,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} else if (board.name && board.name.startsWith("Progressor")) {
|
|
70
|
+
if (value.buffer) {
|
|
71
|
+
const buffer: ArrayBuffer = value.buffer
|
|
72
|
+
const rawData: Uint8Array = new Uint8Array(buffer)
|
|
73
|
+
const kind: number = rawData[0]
|
|
74
|
+
const tare: number = 0 // todo: add tare
|
|
75
|
+
if (kind === ProgressorResponses.WEIGHT_MEASURE) {
|
|
76
|
+
for (let i = 2; i < rawData.length; i += 6) {
|
|
77
|
+
const weight = _unpackFloat(rawData.slice(i, i + 4))
|
|
78
|
+
// let useconds = _unpackInt(rawData.slice(i + 4, i + 6));
|
|
79
|
+
// let now = useconds / 1.0e6;
|
|
80
|
+
|
|
81
|
+
if (notifyCallback) {
|
|
82
|
+
notifyCallback({
|
|
83
|
+
uuid: characteristic.uuid,
|
|
84
|
+
value: {
|
|
85
|
+
massTotal: weight - tare,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} else if (kind === ProgressorResponses.COMMAND_RESPONSE) {
|
|
91
|
+
if (!lastWrite) return
|
|
92
|
+
|
|
93
|
+
let value: string = ""
|
|
94
|
+
|
|
95
|
+
if (lastWrite === ProgressorCommands.GET_BATT_VLTG) {
|
|
96
|
+
const vdd = new DataView(rawData.buffer, 2).getUint32(0, true)
|
|
97
|
+
value = `Battery level = ${vdd} [mV]`
|
|
98
|
+
} else if (lastWrite === ProgressorCommands.GET_FW_VERSION) {
|
|
99
|
+
value = new TextDecoder().decode(rawData.slice(2))
|
|
100
|
+
} else if (lastWrite === ProgressorCommands.GET_ERR_INFO) {
|
|
101
|
+
value = new TextDecoder().decode(rawData.slice(2))
|
|
102
|
+
}
|
|
103
|
+
if (notifyCallback) {
|
|
104
|
+
notifyCallback({ uuid: characteristic.uuid, value: value })
|
|
105
|
+
}
|
|
106
|
+
} else if (kind === ProgressorResponses.LOW_BATTERY_WARNING) {
|
|
107
|
+
if (notifyCallback) {
|
|
108
|
+
notifyCallback({ uuid: characteristic.uuid, value: "low power warning" })
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (notifyCallback) {
|
|
112
|
+
notifyCallback({ uuid: characteristic.uuid, value: `unknown message kind ${kind}` })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
47
115
|
}
|
|
48
|
-
} else if (board.name === "Tindeq") {
|
|
49
|
-
// TODO: handle Tindeq notify
|
|
50
116
|
} else {
|
|
51
117
|
if (notifyCallback) {
|
|
52
118
|
notifyCallback({ uuid: characteristic.uuid, value: value })
|
|
@@ -110,7 +176,7 @@ const onConnected = async (board: Device, onSuccess: () => void): Promise<void>
|
|
|
110
176
|
* Return all service UUIDs
|
|
111
177
|
* @param device
|
|
112
178
|
*/
|
|
113
|
-
|
|
179
|
+
const getAllServiceUUIDs = (device: Device) => {
|
|
114
180
|
return device.services.map((service) => service.uuid)
|
|
115
181
|
}
|
|
116
182
|
/**
|
|
@@ -126,9 +192,8 @@ export const connect = async (board: Device, onSuccess: () => void): Promise<voi
|
|
|
126
192
|
const filters = []
|
|
127
193
|
|
|
128
194
|
if (board.name) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
})
|
|
195
|
+
const filterName = board.name === "Progressor" ? { namePrefix: board.name } : { name: board.name }
|
|
196
|
+
filters.push(filterName)
|
|
132
197
|
}
|
|
133
198
|
if (board.companyId) {
|
|
134
199
|
filters.push({
|
package/src/data.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { notifyCallback } from "./notify"
|
|
2
|
+
|
|
3
|
+
const PACKET_LENGTH: number = 32
|
|
4
|
+
const NUM_SAMPLES: number = 3
|
|
5
|
+
export const CALIBRATION = [[], [], [], []]
|
|
6
|
+
/**
|
|
7
|
+
* applyCalibration
|
|
8
|
+
* @param sample
|
|
9
|
+
* @param calibration
|
|
10
|
+
*/
|
|
11
|
+
const applyCalibration = (sample: number, calibration: number[][]): number => {
|
|
12
|
+
// Extract the calibrated value for the zero point
|
|
13
|
+
const zeroCalibration: number = calibration[0][2]
|
|
14
|
+
|
|
15
|
+
// Initialize sign as positive
|
|
16
|
+
let sign: number = 1
|
|
17
|
+
|
|
18
|
+
// Initialize the final calibrated value
|
|
19
|
+
let final: number = 0
|
|
20
|
+
|
|
21
|
+
// If the sample value is less than the zero calibration point
|
|
22
|
+
if (sample < zeroCalibration) {
|
|
23
|
+
// Change the sign to negative
|
|
24
|
+
sign = -1
|
|
25
|
+
|
|
26
|
+
// Reflect the sample around the zero calibration point
|
|
27
|
+
sample = /* 2 * zeroCalibration */ -sample
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Iterate through the calibration data
|
|
31
|
+
for (let i = 1; i < calibration.length; i++) {
|
|
32
|
+
// Extract the lower and upper bounds of the current calibration range
|
|
33
|
+
const calibrationStart: number = calibration[i - 1][2]
|
|
34
|
+
const calibrationEnd: number = calibration[i][2]
|
|
35
|
+
|
|
36
|
+
// If the sample value is within the current calibration range
|
|
37
|
+
if (sample < calibrationEnd) {
|
|
38
|
+
// Interpolate to get the calibrated value within the range
|
|
39
|
+
final =
|
|
40
|
+
calibration[i - 1][1] +
|
|
41
|
+
((sample - calibrationStart) / (calibrationEnd - calibrationStart)) *
|
|
42
|
+
(calibration[i][1] - calibration[i - 1][1])
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Return the calibrated value with the appropriate sign (positive/negative)
|
|
47
|
+
return sign * final
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Packet {
|
|
51
|
+
received: number
|
|
52
|
+
sampleNum: number
|
|
53
|
+
battRaw: number
|
|
54
|
+
samples: number[]
|
|
55
|
+
masses: number[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* handleMotherboardData
|
|
60
|
+
*
|
|
61
|
+
* @param uuid - Unique identifier
|
|
62
|
+
* @param receivedData - Received data string
|
|
63
|
+
*/
|
|
64
|
+
export const handleMotherboardData = (uuid: string, receivedData: string): void => {
|
|
65
|
+
const receivedTime: number = Date.now()
|
|
66
|
+
|
|
67
|
+
// Check if the line is entirely hex characters
|
|
68
|
+
const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedData)
|
|
69
|
+
|
|
70
|
+
// Handle streaming packet
|
|
71
|
+
if (isAllHex && receivedData.length === PACKET_LENGTH) {
|
|
72
|
+
// Base-16 decode the string: convert hex pairs to byte values
|
|
73
|
+
const bytes: number[] = Array.from({ length: receivedData.length / 2 }, (_, i) =>
|
|
74
|
+
Number(`0x${receivedData.substring(i * 2, i * 2 + 2)}`),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Translate header into packet, number of samples from the packet length
|
|
78
|
+
const packet: Packet = {
|
|
79
|
+
received: receivedTime,
|
|
80
|
+
sampleNum: new DataView(new Uint8Array(bytes).buffer).getUint16(0, true),
|
|
81
|
+
battRaw: new DataView(new Uint8Array(bytes).buffer).getUint16(2, true),
|
|
82
|
+
samples: [],
|
|
83
|
+
masses: [],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const dataView = new DataView(new Uint8Array(bytes).buffer)
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < NUM_SAMPLES; i++) {
|
|
89
|
+
const sampleStart: number = 4 + 3 * i
|
|
90
|
+
// Use DataView to read the 24-bit unsigned integer
|
|
91
|
+
const rawValue =
|
|
92
|
+
dataView.getUint8(sampleStart) |
|
|
93
|
+
(dataView.getUint8(sampleStart + 1) << 8) |
|
|
94
|
+
(dataView.getUint8(sampleStart + 2) << 16)
|
|
95
|
+
|
|
96
|
+
// Ensure unsigned 32-bit integer
|
|
97
|
+
packet.samples[i] = rawValue >>> 0
|
|
98
|
+
|
|
99
|
+
if (packet.samples[i] >= 0x7fffff) {
|
|
100
|
+
packet.samples[i] -= 0x1000000
|
|
101
|
+
}
|
|
102
|
+
// if (!CALIBRATION[0].length) return
|
|
103
|
+
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i])
|
|
104
|
+
}
|
|
105
|
+
// invert center and right values
|
|
106
|
+
packet.masses[1] *= -1
|
|
107
|
+
packet.masses[2] *= -1
|
|
108
|
+
// map to variables
|
|
109
|
+
const left: number = packet.masses[0]
|
|
110
|
+
const center: number = packet.masses[1]
|
|
111
|
+
const right: number = packet.masses[2]
|
|
112
|
+
if (notifyCallback) {
|
|
113
|
+
notifyCallback({
|
|
114
|
+
uuid,
|
|
115
|
+
value: {
|
|
116
|
+
massTotal: Math.max(-1000, left + right + center).toFixed(1),
|
|
117
|
+
massLeft: Math.max(-1000, left).toFixed(1),
|
|
118
|
+
massRight: Math.max(-1000, right).toFixed(1),
|
|
119
|
+
massCenter: Math.max(-1000, center).toFixed(1),
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
} else if ((receivedData.match(/,/g) || []).length === 3) {
|
|
124
|
+
console.log(receivedData)
|
|
125
|
+
// if the returned notification is a calibration string add them to the array
|
|
126
|
+
const parts: string[] = receivedData.split(",")
|
|
127
|
+
const numericParts: number[] = parts.map((x) => parseFloat(x))
|
|
128
|
+
;(CALIBRATION[numericParts[0]] as number[][]).push(numericParts.slice(1))
|
|
129
|
+
} else {
|
|
130
|
+
// unhanded data
|
|
131
|
+
console.log(receivedData)
|
|
132
|
+
}
|
|
133
|
+
}
|
package/src/devices/entralpi.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Device } from "./types"
|
|
2
|
-
import { notifyCallback } from "../notify"
|
|
3
2
|
|
|
4
3
|
export const Entralpi: Device = {
|
|
5
4
|
name: "ENTRALPI",
|
|
@@ -53,19 +52,3 @@ export const Entralpi: Device = {
|
|
|
53
52
|
},
|
|
54
53
|
],
|
|
55
54
|
}
|
|
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
|
-
}
|
package/src/devices/index.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
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 = [[], [], [], []]
|
|
7
2
|
|
|
8
3
|
export const Motherboard: Device = {
|
|
9
4
|
name: "Motherboard",
|
|
@@ -84,132 +79,3 @@ export const Motherboard: Device = {
|
|
|
84
79
|
},
|
|
85
80
|
],
|
|
86
81
|
}
|
|
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)) *
|
|
123
|
-
(calibration[i][1] - calibration[i - 1][1])
|
|
124
|
-
break
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Return the calibrated value with the appropriate sign (positive/negative)
|
|
128
|
-
return sign * final
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
interface Packet {
|
|
132
|
-
received: number
|
|
133
|
-
sampleNum: number
|
|
134
|
-
battRaw: number
|
|
135
|
-
samples: number[]
|
|
136
|
-
masses: number[]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* handleMotherboardData
|
|
141
|
-
* @param uuid - Unique identifier
|
|
142
|
-
* @param receivedData - Received data string
|
|
143
|
-
*/
|
|
144
|
-
export function handleMotherboardData(uuid: string, receivedData: string): void {
|
|
145
|
-
const receivedTime: number = Date.now()
|
|
146
|
-
|
|
147
|
-
// Check if the line is entirely hex characters
|
|
148
|
-
const isAllHex: boolean = /^[0-9A-Fa-f]+$/g.test(receivedData)
|
|
149
|
-
|
|
150
|
-
// Handle streaming packet
|
|
151
|
-
if (isAllHex && receivedData.length === PACKET_LENGTH) {
|
|
152
|
-
// Base-16 decode the string: convert hex pairs to byte values
|
|
153
|
-
const bytes: number[] = Array.from({ length: receivedData.length / 2 }, (_, i) =>
|
|
154
|
-
Number(`0x${receivedData.substring(i * 2, i * 2 + 2)}`),
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
// Translate header into packet, number of samples from the packet length
|
|
158
|
-
const packet: Packet = {
|
|
159
|
-
received: receivedTime,
|
|
160
|
-
sampleNum: new DataView(new Uint8Array(bytes).buffer).getUint16(0, true),
|
|
161
|
-
battRaw: new DataView(new Uint8Array(bytes).buffer).getUint16(2, true),
|
|
162
|
-
samples: [],
|
|
163
|
-
masses: [],
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const dataView = new DataView(new Uint8Array(bytes).buffer)
|
|
167
|
-
|
|
168
|
-
for (let i = 0; i < NUM_SAMPLES; i++) {
|
|
169
|
-
const sampleStart: number = 4 + 3 * i
|
|
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
|
|
178
|
-
|
|
179
|
-
if (packet.samples[i] >= 0x7fffff) {
|
|
180
|
-
packet.samples[i] -= 0x1000000
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// TODO: make sure device is calibrated
|
|
184
|
-
if (!CALIBRATION[0].length) return
|
|
185
|
-
packet.masses[i] = applyCalibration(packet.samples[i], CALIBRATION[i])
|
|
186
|
-
}
|
|
187
|
-
// invert center and right values
|
|
188
|
-
packet.masses[1] *= -1
|
|
189
|
-
packet.masses[2] *= -1
|
|
190
|
-
// map to variables
|
|
191
|
-
const left: number = packet.masses[0]
|
|
192
|
-
const center: number = packet.masses[1]
|
|
193
|
-
const right: number = packet.masses[2]
|
|
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)
|
|
207
|
-
// if the returned notification is a calibration string add them to the array
|
|
208
|
-
const parts: string[] = receivedData.split(",")
|
|
209
|
-
const numericParts: number[] = parts.map((x) => parseFloat(x))
|
|
210
|
-
;(CALIBRATION[numericParts[0]] as number[][]).push(numericParts.slice(1))
|
|
211
|
-
} else {
|
|
212
|
-
// unhanded data
|
|
213
|
-
console.log(receivedData)
|
|
214
|
-
}
|
|
215
|
-
}
|