@hangtime/grip-connect 0.0.5

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 ADDED
@@ -0,0 +1,83 @@
1
+ # Force-Sensing Climbing Training
2
+
3
+ The objective of this project is to create a client that can establish connections with various Force-Sensing
4
+ Hangboards/Plates used by climbers for strength measurement. Examples of such hangboards include the
5
+ [Motherboard](https://griptonite.io/shop/motherboard/), [Climbro](https://climbro.com/),
6
+ [SmartBoard](https://www.smartboard-climbing.com/), [Entralpi](https://entralpi.com/) or
7
+ [Tindeq Progressor](https://tindeq.com/)
8
+
9
+ ## Roadmap
10
+
11
+ - ➡️ Connect with devices
12
+ - ✅ Griptonte Motherboard
13
+ - ✅ Tindeq Progressor
14
+ - ✅ Entralpi
15
+ - ➡️ Climbro
16
+ - ➡️ SmartBoard
17
+ - ✅ Read / Write / Notify using Bluetooth
18
+ - ➡️ Calibrate Devices
19
+ - ➡️ Output weight/force stream
20
+
21
+ ## Development
22
+
23
+ ```bash
24
+ git clone https://github.com/Stevie-Ray/hangtime-grip-connect
25
+ cd hangtime-grip-connect
26
+ npm install
27
+ ```
28
+
29
+ ## Install
30
+
31
+ ```sh [npm]
32
+ $ npm install @hangtime/grip-connect
33
+ ```
34
+
35
+ ## Example usage (Motherboard)
36
+
37
+ Simply importing the utilities you need from `@hangtime/grip-connect`. Devices that are currently supported:
38
+ `Motherboard`, `Tindeq` and `Entralpi`.
39
+
40
+ ```html
41
+ <button id="motherboard" type="button">Connect Motherboard</button>
42
+ ```
43
+
44
+ ```js
45
+ import { Motherboard, connect, disconnect, read, write, notify } from "@hangtime/grip-connect"
46
+
47
+ const motherboardButton = document.querySelector("#motherboard")
48
+
49
+ motherboardButton.addEventListener("click", () => {
50
+ connect(Motherboard, async () => {
51
+ // Listen for notifications
52
+ notify((data) => {
53
+ console.log(data)
54
+ })
55
+
56
+ // read battery + device info
57
+ await read(Motherboard, "battery", "level")
58
+ await read(Motherboard, "device", "manufacturer")
59
+ await read(Motherboard, "device", "hardware")
60
+ await read(Motherboard, "device", "firmware")
61
+
62
+ // Calibrate?
63
+ await write(Motherboard, "uart", "tx", "C", 5000)
64
+
65
+ // Read stream?
66
+ await write(Motherboard, "unknown", "01", "1", 2500)
67
+ await write(Motherboard, "unknown", "02", "0", 2500)
68
+ await write(Motherboard, "uart", "tx", "S30", 5000)
69
+
70
+ // Read stream (2x)?
71
+ await write(Motherboard, "unknown", "01", "0", 2500)
72
+ await write(Motherboard, "unknown", "02", "1", 2500)
73
+ await write(Motherboard, "uart", "tx", "S30", 5000)
74
+
75
+ // disconnect from device after we are done
76
+ disconnect(Motherboard)
77
+ })
78
+ })
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT © [Stevie-Ray Hartog](https://github.com/Stevie-Ray)
@@ -0,0 +1,18 @@
1
+ /// <reference types="web-bluetooth" />
2
+ interface Motherboard {
3
+ device?: BluetoothDevice
4
+ devSn?: BluetoothRemoteGATTCharacteristic
5
+ devFr?: BluetoothRemoteGATTCharacteristic
6
+ devHr?: BluetoothRemoteGATTCharacteristic
7
+ devMn?: BluetoothRemoteGATTCharacteristic
8
+ bat?: BluetoothRemoteGATTCharacteristic
9
+ led01?: BluetoothRemoteGATTCharacteristic
10
+ led02?: BluetoothRemoteGATTCharacteristic
11
+ uartTx?: BluetoothRemoteGATTCharacteristic
12
+ uartRx?: BluetoothRemoteGATTCharacteristic
13
+ }
14
+ declare const motherboard: Motherboard
15
+ declare const connect: () => void
16
+ declare const disconnect: () => void
17
+ export default motherboard
18
+ export { disconnect, connect }
package/build/index.js ADDED
@@ -0,0 +1,175 @@
1
+ // Device service
2
+ const DEVICE_SERVICE_UUID = "0000180a-0000-1000-8000-00805f9b34fb"
3
+ const DEVICE_SN_CHARACTERISTIC_UUID = "00002a25-0000-1000-8000-00805f9b34fb"
4
+ const DEVICE_FR_CHARACTERISTIC_UUID = "00002a26-0000-1000-8000-00805f9b34fb"
5
+ const DEVICE_HR_CHARACTERISTIC_UUID = "00002a27-0000-1000-8000-00805f9b34fb"
6
+ const DEVICE_MN_CHARACTERISTIC_UUID = "00002a29-0000-1000-8000-00805f9b34fb"
7
+ // Battery service (Read / Notify)
8
+ const BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb"
9
+ const BATTERY_CHARACTERISTIC_UUID = "00002a19-0000-1000-8000-00805f9b34fb"
10
+ // Led service
11
+ const LED_SERVICE_UUID = "10ababcd-15e1-28ff-de13-725bea03b127"
12
+ const LED_01_CHARACTERISTIC_UUID = "10ab1524-15e1-28ff-de13-725bea03b127"
13
+ const LED_02_CHARACTERISTIC_UUID = "10ab1525-15e1-28ff-de13-725bea03b127"
14
+ // An implementation of Nordic Semicondutor's UART/Serial Port Emulation over Bluetooth low energy
15
+ const UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
16
+ const UART_TX_CHARACTERISTIC_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
17
+ const UART_RX_CHARACTERISTIC_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
18
+ // https://github.com/sepp89117/GoPro_Web_RC/blob/main/GoPro_Web_RC.html
19
+ const motherboard = {}
20
+ const connect = () => {
21
+ navigator.bluetooth
22
+ .requestDevice({
23
+ filters: [
24
+ {
25
+ name: "Motherboard",
26
+ },
27
+ {
28
+ manufacturerData: [
29
+ {
30
+ companyIdentifier: 0x2a29,
31
+ },
32
+ ],
33
+ },
34
+ ],
35
+ optionalServices: [DEVICE_SERVICE_UUID, BATTERY_SERVICE_UUID, UART_SERVICE_UUID, LED_SERVICE_UUID],
36
+ })
37
+ .then(async (device) => {
38
+ motherboard.device = device
39
+ device.addEventListener("gattserverdisconnected", onDisconnected)
40
+ return device.gatt?.connect()
41
+ })
42
+ .then((server) => {
43
+ return server?.getPrimaryServices()
44
+ })
45
+ .then((services) => {
46
+ // console.log(services)
47
+ if (services === null) {
48
+ console.error("getPrimaryServices is 'null'")
49
+ } else {
50
+ if (services) {
51
+ for (const service of services) {
52
+ switch (service.uuid) {
53
+ case DEVICE_SERVICE_UUID:
54
+ // getCharacteristic(service, DEVICE_SN_CHARACTERISTIC_UUID) getCharacteristic(s) called with blocklisted UUID
55
+ getCharacteristic(service, DEVICE_FR_CHARACTERISTIC_UUID)
56
+ getCharacteristic(service, DEVICE_HR_CHARACTERISTIC_UUID)
57
+ getCharacteristic(service, DEVICE_MN_CHARACTERISTIC_UUID)
58
+ break
59
+ case BATTERY_SERVICE_UUID:
60
+ getCharacteristic(service, BATTERY_CHARACTERISTIC_UUID)
61
+ break
62
+ case LED_SERVICE_UUID:
63
+ getCharacteristic(service, LED_01_CHARACTERISTIC_UUID)
64
+ getCharacteristic(service, LED_02_CHARACTERISTIC_UUID)
65
+ break
66
+ case UART_SERVICE_UUID:
67
+ getCharacteristic(service, UART_TX_CHARACTERISTIC_UUID)
68
+ getCharacteristic(service, UART_RX_CHARACTERISTIC_UUID)
69
+ break
70
+ default:
71
+ break
72
+ }
73
+ }
74
+ }
75
+ }
76
+ })
77
+ .catch((error) => {
78
+ console.log(error)
79
+ })
80
+ }
81
+ const disconnect = () => {
82
+ if (!motherboard.device) return
83
+ if (motherboard.device.gatt?.connected) {
84
+ motherboard.device.gatt?.disconnect()
85
+ }
86
+ }
87
+ const onDisconnected = (event) => {
88
+ motherboard.device = undefined
89
+ const device = event.target
90
+ console.log(`Device ${device.name} is disconnected.`)
91
+ }
92
+ const handleNotifications = (event) => {
93
+ const characteristic = event.target
94
+ const receivedData = new Uint8Array(characteristic.value.buffer)
95
+ // Create an array to store the parsed decimal values
96
+ const decimalArray = []
97
+ // Iterate through each byte and convert to decimal
98
+ for (let i = 0; i < receivedData.length; i++) {
99
+ decimalArray.push(receivedData[i])
100
+ }
101
+ // Convert the decimal array to a string representation
102
+ const receivedString = String.fromCharCode(...decimalArray)
103
+ // Split the string into pairs of characters
104
+ const hexPairs = receivedString.match(/.{1,2}/g)
105
+ // Convert each hexadecimal pair to decimal
106
+ const parsedDecimalArray = hexPairs?.map((hexPair) => parseInt(hexPair, 16))
107
+ // Handle different types of data
108
+ if (characteristic.value.byteLength === 20) {
109
+ // Define keys for the elements
110
+ const elementKeys = [
111
+ "frames",
112
+ "cycle",
113
+ "unknown",
114
+ "eleven",
115
+ "trippin1",
116
+ "pressure1",
117
+ "left",
118
+ "trippin2",
119
+ "pressure2",
120
+ "right",
121
+ ]
122
+ // Create a single object with keys and values
123
+ const dataObject = {}
124
+ if (parsedDecimalArray) {
125
+ elementKeys.forEach((key, index) => {
126
+ dataObject[key] = parsedDecimalArray[index]
127
+ })
128
+ }
129
+ // Print the formatted string on the screen
130
+ console.log(dataObject)
131
+ } else if (characteristic.value.byteLength === 14) {
132
+ console.log(characteristic.value.byteLength, parsedDecimalArray)
133
+ } else {
134
+ console.log(characteristic.value.byteLength, parsedDecimalArray)
135
+ }
136
+ }
137
+ const getCharacteristic = (service, uuid) => {
138
+ service.getCharacteristic(uuid).then((characteristic) => {
139
+ switch (characteristic.uuid) {
140
+ case DEVICE_SN_CHARACTERISTIC_UUID:
141
+ motherboard.devSn = characteristic
142
+ break
143
+ case DEVICE_FR_CHARACTERISTIC_UUID:
144
+ motherboard.devFr = characteristic
145
+ break
146
+ case DEVICE_HR_CHARACTERISTIC_UUID:
147
+ motherboard.devHr = characteristic
148
+ break
149
+ case DEVICE_MN_CHARACTERISTIC_UUID:
150
+ motherboard.devMn = characteristic
151
+ break
152
+ case BATTERY_CHARACTERISTIC_UUID:
153
+ motherboard.bat = characteristic
154
+ break
155
+ case LED_01_CHARACTERISTIC_UUID:
156
+ motherboard.led01 = characteristic
157
+ break
158
+ case LED_02_CHARACTERISTIC_UUID:
159
+ motherboard.led02 = characteristic
160
+ break
161
+ case UART_TX_CHARACTERISTIC_UUID:
162
+ motherboard.uartTx = characteristic
163
+ break
164
+ case UART_RX_CHARACTERISTIC_UUID:
165
+ motherboard.uartRx = characteristic
166
+ motherboard.uartRx.startNotifications()
167
+ motherboard.uartRx.addEventListener("characteristicvaluechanged", handleNotifications)
168
+ break
169
+ default:
170
+ break
171
+ }
172
+ })
173
+ }
174
+ export default motherboard
175
+ export { disconnect, connect }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@hangtime/grip-connect",
3
+ "version": "0.0.5",
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
+ "main": "src/index.ts",
6
+ "scripts": {
7
+ "build": "tsc --build"
8
+ },
9
+ "author": "Stevie-Ray Hartog <mail@stevie-ray.nl>",
10
+ "license": "MIT",
11
+ "devDependencies": {},
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Stevie-Ray/hangtime-grip-connect.git"
15
+ },
16
+ "keywords": [
17
+ "climbing",
18
+ "bluetooth",
19
+ "griptonite",
20
+ "hangboard",
21
+ "hangboarding",
22
+ "beastmaker",
23
+ "motherboard"
24
+ ],
25
+ "bugs": {
26
+ "url": "https://github.com/Stevie-Ray/hangtime-grip-connect/issues"
27
+ },
28
+ "homepage": "https://stevie-ray.github.io/hangtime-grip-connect/"
29
+ }
@@ -0,0 +1,19 @@
1
+ import { Device } from "./devices/types"
2
+
3
+ /**
4
+ * getCharacteristic
5
+ * @param board
6
+ * @param serviceId
7
+ * @param characteristicId
8
+ */
9
+ export const getCharacteristic = (board: Device, serviceId: string, characteristicId: string) => {
10
+ const boardService = board.services.find((service) => service.id === serviceId)
11
+ if (boardService) {
12
+ const boardCharacteristic = boardService.characteristics.find(
13
+ (characteristic) => characteristic.id === characteristicId,
14
+ )
15
+ if (boardCharacteristic) {
16
+ return boardCharacteristic.characteristic
17
+ }
18
+ }
19
+ }
package/src/connect.ts ADDED
@@ -0,0 +1,162 @@
1
+ import { Device } from "./devices/types"
2
+ import { notifyCallback } from "./notify"
3
+
4
+ /**
5
+ * onDisconnected
6
+ * @param board
7
+ * @param event
8
+ */
9
+ const onDisconnected = (event: Event, board: Device): void => {
10
+ board.device = undefined
11
+ const device = event.target as BluetoothDevice
12
+ console.log(`Device ${device.name} is disconnected.`)
13
+ }
14
+ /**
15
+ * handleNotifications
16
+ * @param event
17
+ * @param onNotify
18
+ */
19
+ const handleNotifications = (event: Event, board: Device): void => {
20
+ const characteristic = event.target as BluetoothRemoteGATTCharacteristic
21
+ const receivedData = new Uint8Array(characteristic.value!.buffer)
22
+ // Create an array to store the parsed decimal values
23
+ const decimalArray: number[] = []
24
+
25
+ // Iterate through each byte and convert to decimal
26
+ for (let i = 0; i < receivedData.length; i++) {
27
+ decimalArray.push(receivedData[i])
28
+ }
29
+ // Convert the decimal array to a string representation
30
+ const receivedString: string = String.fromCharCode(...decimalArray)
31
+
32
+ if (board.name === "Motherboard") {
33
+ // Split the string into pairs of characters
34
+ const hexPairs: RegExpMatchArray | null = receivedString.match(/.{1,2}/g)
35
+ // Convert each hexadecimal pair to decimal
36
+ const parsedDecimalArray: number[] | undefined = hexPairs?.map((hexPair) => parseInt(hexPair, 16))
37
+ // Handle different types of data
38
+ if (characteristic.value!.byteLength === 20) {
39
+ const elementKeys = [
40
+ "frames",
41
+ "cycle",
42
+ "unknown",
43
+ "eleven",
44
+ "dynamic1",
45
+ "pressure1",
46
+ "left",
47
+ "dynamic2",
48
+ "pressure2",
49
+ "right",
50
+ ]
51
+ const dataObject = {}
52
+
53
+ if (parsedDecimalArray) {
54
+ elementKeys.forEach((key, index) => {
55
+ dataObject[key] = parsedDecimalArray[index]
56
+ })
57
+ }
58
+ if (notifyCallback) {
59
+ notifyCallback({ uuid: characteristic.uuid, value: dataObject })
60
+ }
61
+ } else if (characteristic.value!.byteLength === 14) {
62
+ // TODO: handle 14 byte data
63
+ // notifyCallback({ uuid: characteristic.uuid, value: characteristic.value!.getInt8(0) / 100 })
64
+ }
65
+ } else if (board.name === "ENTRALPI") {
66
+ // TODO: handle Entralpi notify
67
+ // characteristic.value!.getInt16(0) / 100;
68
+ if (notifyCallback) {
69
+ notifyCallback({ uuid: characteristic.uuid, value: receivedString })
70
+ }
71
+ } else if (board.name === "Tindeq") {
72
+ // TODO: handle Tindeq notify
73
+ } else {
74
+ if (notifyCallback) {
75
+ notifyCallback({ uuid: characteristic.uuid, value: receivedString })
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Return all service UUIDs
81
+ * @param device
82
+ */
83
+ function getAllServiceUUIDs(device: Device) {
84
+ return device.services.map((service) => service.uuid)
85
+ }
86
+ /**
87
+ * connect
88
+ * @param device
89
+ * @param onSuccess
90
+ */
91
+ export const connect = async (board: Device, onSuccess: () => void): Promise<void> => {
92
+ try {
93
+ const deviceServices = getAllServiceUUIDs(board)
94
+
95
+ // setup filter list
96
+ const filters = []
97
+
98
+ if (board.name) {
99
+ filters.push({
100
+ name: board.name,
101
+ })
102
+ }
103
+ if (board.companyId) {
104
+ filters.push({
105
+ manufacturerData: [
106
+ {
107
+ companyIdentifier: board.companyId,
108
+ },
109
+ ],
110
+ })
111
+ }
112
+
113
+ const device = await navigator.bluetooth.requestDevice({
114
+ filters: filters,
115
+ optionalServices: deviceServices,
116
+ })
117
+
118
+ board.device = device
119
+
120
+ device.addEventListener("gattserverdisconnected", (event) => onDisconnected(event, board))
121
+
122
+ const server = await device.gatt?.connect()
123
+ const services = await server?.getPrimaryServices()
124
+
125
+ if (!services || services.length === 0) {
126
+ console.error("No services found")
127
+ return
128
+ }
129
+
130
+ for (const service of services) {
131
+ const matchingService = board.services.find((boardService) => boardService.uuid === service.uuid)
132
+
133
+ if (matchingService) {
134
+ const characteristics = await service.getCharacteristics()
135
+
136
+ for (const characteristic of matchingService.characteristics) {
137
+ const matchingCharacteristic = characteristics.find((char) => char.uuid === characteristic.uuid)
138
+
139
+ if (matchingCharacteristic) {
140
+ const element = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid)
141
+ if (element) {
142
+ element.characteristic = matchingCharacteristic
143
+
144
+ // notify
145
+ if (element.id === "rx") {
146
+ matchingCharacteristic.startNotifications()
147
+ matchingCharacteristic.addEventListener("characteristicvaluechanged", (event) =>
148
+ handleNotifications(event, board),
149
+ )
150
+ }
151
+ }
152
+ } else {
153
+ console.warn(`Characteristic ${characteristic.uuid} not found in service ${service.uuid}`)
154
+ }
155
+ }
156
+ }
157
+ }
158
+ onSuccess()
159
+ } catch (error) {
160
+ console.error(error)
161
+ }
162
+ }
@@ -0,0 +1,54 @@
1
+ import { Device } from "./types"
2
+
3
+ export const Entralpi: Device = {
4
+ name: "ENTRALPI",
5
+ services: [
6
+ {
7
+ name: "Device Information",
8
+ id: "device",
9
+ uuid: "0000180a-0000-1000-8000-00805f9b34fb",
10
+ characteristics: [],
11
+ },
12
+ {
13
+ name: "Battery Service",
14
+ id: "battery",
15
+ uuid: "0000180f-0000-1000-8000-00805f9b34fb",
16
+ characteristics: [],
17
+ },
18
+ {
19
+ name: "Generic Attribute",
20
+ id: "attribute",
21
+ uuid: "00001801-0000-1000-8000-00805f9b34fb",
22
+ characteristics: [],
23
+ },
24
+ {
25
+ name: "UART ISSC Transparent Service",
26
+ id: "uart",
27
+ uuid: "0000fff0-0000-1000-8000-00805f9b34fb",
28
+ characteristics: [
29
+ {
30
+ name: "TX",
31
+ id: "tx",
32
+ uuid: "0000fff5-0000-1000-8000-00805f9b34fb",
33
+ },
34
+ {
35
+ name: "RX",
36
+ id: "rx",
37
+ uuid: "0000fff4-0000-1000-8000-00805f9b34fb",
38
+ },
39
+ ],
40
+ },
41
+ {
42
+ name: "Weight Scale",
43
+ id: "weight",
44
+ uuid: "0000181d-0000-1000-8000-00805f9b34fb",
45
+ characteristics: [],
46
+ },
47
+ {
48
+ name: "Generic Access",
49
+ id: "access",
50
+ uuid: "00001800-0000-1000-8000-00805f9b34fb",
51
+ characteristics: [],
52
+ },
53
+ ],
54
+ }
@@ -0,0 +1,5 @@
1
+ export { Motherboard } from "./moterboard"
2
+
3
+ export { Entralpi } from "./entralpi"
4
+
5
+ export { Tindeq } from "./tindeq"
@@ -0,0 +1,81 @@
1
+ import { Device } from "./types"
2
+
3
+ export const Motherboard: Device = {
4
+ name: "Motherboard",
5
+ companyId: 0x2a29,
6
+ services: [
7
+ {
8
+ name: "Device Information",
9
+ id: "device",
10
+ uuid: "0000180a-0000-1000-8000-00805f9b34fb",
11
+ characteristics: [
12
+ // {
13
+ // name: 'Serial Number (Blocked)',
14
+ // id: 'serial'
15
+ // uuid: '00002a25-0000-1000-8000-00805f9b34fb'
16
+ // },
17
+ {
18
+ name: "Firmware Revision",
19
+ id: "firmware",
20
+ uuid: "00002a26-0000-1000-8000-00805f9b34fb",
21
+ },
22
+ {
23
+ name: "Hardware Revision",
24
+ id: "hardware",
25
+ uuid: "00002a27-0000-1000-8000-00805f9b34fb",
26
+ },
27
+ {
28
+ name: "Manufacturer Name",
29
+ id: "manufacturer",
30
+ uuid: "00002a29-0000-1000-8000-00805f9b34fb",
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ name: "Battery Service",
36
+ id: "battery",
37
+ uuid: "0000180f-0000-1000-8000-00805f9b34fb",
38
+ characteristics: [
39
+ {
40
+ name: "Battery Level",
41
+ id: "level",
42
+ uuid: "00002a19-0000-1000-8000-00805f9b34fb",
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ name: "Unknown Service",
48
+ id: "unknown",
49
+ uuid: "10ababcd-15e1-28ff-de13-725bea03b127",
50
+ characteristics: [
51
+ {
52
+ name: "Unknown 01",
53
+ id: "01",
54
+ uuid: "10ab1524-15e1-28ff-de13-725bea03b127",
55
+ },
56
+ {
57
+ name: "Unknown 02",
58
+ id: "02",
59
+ uuid: "10ab1525-15e1-28ff-de13-725bea03b127",
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ name: "UART Nordic Service",
65
+ id: "uart",
66
+ uuid: "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
67
+ characteristics: [
68
+ {
69
+ name: "TX",
70
+ id: "tx",
71
+ uuid: "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
72
+ },
73
+ {
74
+ name: "RX",
75
+ id: "rx",
76
+ uuid: "6e400003-b5a3-f393-e0a9-e50e24dcca9e",
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ }
@@ -0,0 +1,41 @@
1
+ import { Device } from "./types"
2
+
3
+ export const Tindeq: Device = {
4
+ name: "Tindeq",
5
+ services: [
6
+ {
7
+ name: "Progressor Service",
8
+ id: "progressor",
9
+ uuid: "7e4e1701-1ea6-40c9-9dcc-13d34ffead57",
10
+ characteristics: [
11
+ {
12
+ name: "Write",
13
+ id: "tx",
14
+ uuid: "7e4e1703-1ea6-40c9-9dcc-13d34ffead57",
15
+ },
16
+ {
17
+ name: "Notify",
18
+ id: "rx",
19
+ uuid: "7e4e1702-1ea6-40c9-9dcc-13d34ffead57",
20
+ },
21
+ ],
22
+ },
23
+ ],
24
+ }
25
+
26
+ export const Commands = {
27
+ TARE_SCALE: 0x64,
28
+ START_MEASURING: 0x65,
29
+ STOP_MEASURING: 0x66,
30
+ GET_APP_VERSION: 0x6b,
31
+ GET_ERROR_INFO: 0x6c,
32
+ CLEAR_ERR_INFO: 0x6d,
33
+ GET_BATTERY_LEVEL: 0x6f,
34
+ SLEEP: 0x6e,
35
+ }
36
+
37
+ export const NotificationTypes = {
38
+ COMMAND_RESPONSE: 0,
39
+ WEIGHT_MEASURE: 1,
40
+ LOW_BATTERY_WARNING: 2,
41
+ }
@@ -0,0 +1,20 @@
1
+ interface Characteristic {
2
+ name: string
3
+ id: string
4
+ uuid: string
5
+ characteristic?: BluetoothRemoteGATTCharacteristic
6
+ }
7
+
8
+ interface Service {
9
+ name: string
10
+ id: string
11
+ uuid: string
12
+ characteristics: Characteristic[]
13
+ }
14
+
15
+ export interface Device {
16
+ name: string
17
+ companyId?: number
18
+ services: Service[]
19
+ device?: BluetoothDevice
20
+ }
@@ -0,0 +1,12 @@
1
+ import { Device } from "./devices/types"
2
+
3
+ /**
4
+ * disconnect
5
+ * @param board
6
+ */
7
+ export const disconnect = (board: Device): void => {
8
+ if (!board.device) return
9
+ if (board.device.gatt?.connected) {
10
+ board.device.gatt?.disconnect()
11
+ }
12
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { Motherboard, Entralpi, Tindeq } from "./devices/index"
2
+
3
+ export { connect } from "./connect"
4
+
5
+ export { disconnect } from "./disconnect"
6
+
7
+ export { notify } from "./notify"
8
+
9
+ export { read } from "./read"
10
+
11
+ export { write } from "./write"
package/src/notify.ts ADDED
@@ -0,0 +1,10 @@
1
+ // Define the callback function type
2
+ type NotifyCallback = (data: object) => void
3
+
4
+ // Initialize the callback variable
5
+ export let notifyCallback: NotifyCallback
6
+
7
+ // Export a function to set the callback
8
+ export const notify = (callback: NotifyCallback) => {
9
+ notifyCallback = callback
10
+ }
package/src/read.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { Device } from "./devices/types"
2
+ import { notifyCallback } from "./notify"
3
+ import { getCharacteristic } from "./characteristic"
4
+
5
+ /**
6
+ * read
7
+ * @param characteristic
8
+ */
9
+ export const read = (board: Device, serviceId: string, characteristicId: string): Promise<void> => {
10
+ return new Promise((resolve, reject) => {
11
+ if (board.device?.gatt?.connected) {
12
+ const characteristic = getCharacteristic(board, serviceId, characteristicId)
13
+
14
+ if (characteristic) {
15
+ characteristic
16
+ .readValue()
17
+ .then((value) => {
18
+ let decodedValue
19
+ const decoder = new TextDecoder("utf-8")
20
+ switch (characteristicId) {
21
+ case "level":
22
+ decodedValue = value.getUint8(0)
23
+ break
24
+ default:
25
+ decodedValue = decoder.decode(value)
26
+ break
27
+ }
28
+ notifyCallback({ uuid: characteristic.uuid, value: decodedValue })
29
+ resolve()
30
+ })
31
+ .catch((error) => {
32
+ reject(error)
33
+ })
34
+ } else {
35
+ reject(new Error("Characteristic is undefined"))
36
+ }
37
+ } else {
38
+ reject(new Error("Device is not connected"))
39
+ }
40
+ })
41
+ }
package/src/write.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { Device } from "./devices/types"
2
+ import { getCharacteristic } from "./characteristic"
3
+ /**
4
+ * write
5
+ * @param characteristic
6
+ * @param message
7
+ */
8
+ export const write = (
9
+ board: Device,
10
+ serviceId: string,
11
+ characteristicId: string,
12
+ message: string,
13
+ duration: number = 0,
14
+ ): Promise<void> => {
15
+ return new Promise((resolve, reject) => {
16
+ if (board.device?.gatt?.connected) {
17
+ const encoder = new TextEncoder()
18
+
19
+ const characteristic = getCharacteristic(board, serviceId, characteristicId)
20
+
21
+ if (characteristic) {
22
+ characteristic
23
+ .writeValue(encoder.encode(message))
24
+ .then(() => {
25
+ setTimeout(() => {
26
+ resolve()
27
+ }, duration)
28
+ })
29
+ .catch((error) => {
30
+ reject(error)
31
+ })
32
+ } else {
33
+ reject(new Error("Characteristics is undefined"))
34
+ }
35
+ } else {
36
+ reject(new Error("Device is not connected"))
37
+ }
38
+ })
39
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src"],
4
+ "compilerOptions": {
5
+ "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo",
6
+ "types": ["web-bluetooth"],
7
+ "outDir": "./build"
8
+ }
9
+ }