@hangtime/grip-connect 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,18 +3,31 @@
3
3
  **Force-Sensing Climbing Training**
4
4
 
5
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
- [Griptonite Motherboard](https://griptonite.io/shop/motherboard/), [Climbro](https://climbro.com/),
8
- [mySmartBoard](https://www.smartboard-climbing.com/), [Entralpi](https://entralpi.com/),
9
- [Tindeq Progressor](https://tindeq.com/) or [MAT Muscle Meter](https://www.matassessment.com/musclemeter)
6
+ Force-Sensing Hangboards / Plates / LED system boards used by climbers for strength measurement. Examples of such
7
+ hangboards include the [Griptonite Motherboard](https://griptonite.io/shop/motherboard/),
8
+ [Climbro](https://climbro.com/), [mySmartBoard](https://www.smartboard-climbing.com/),
9
+ [Entralpi](https://entralpi.com/), [Tindeq Progressor](https://tindeq.com/) or
10
+ [MAT Muscle Meter](https://www.matassessment.com/musclemeter)
11
+
12
+ And LED system boards from [Aurora Climbing](https://auroraclimbing.com/) like the
13
+ [Kilter Board](https://settercloset.com/pages/the-kilter-board),
14
+ [Tension Board](https://tensionclimbing.com/product/tension-board-2/),
15
+ [Grasshopper Board](https://grasshopperclimbing.com/products/),
16
+ [Decoy Board](https://decoy-holds.com/pages/decoy-board), [Touchstone Board](https://touchstoneboardapp.com/) and
17
+ [So iLL Board](https://apps.apple.com/us/app/so-ill-board/id1358056082).
10
18
 
11
19
  Learn more: [Docs](https://stevie-ray.github.io/hangtime-grip-connect/) -
12
20
  [Browser Support](https://caniuse.com/web-bluetooth)
13
21
 
22
+ > This project is provided "as-is" without any express or implied warranties. By using this software, you assume all
23
+ > risks associated with its use, including but not limited to hardware damage, data loss, or any other issues that may
24
+ > arise. The developers and contributors are not responsible for any harm or loss incurred. Use this software at your
25
+ > own discretion and responsibility.
26
+
14
27
  ## Try it out
15
28
 
16
29
  [Chart](https://grip-connect.vercel.app/) - [Flappy Bird](https://grip-connect-flappy-bird.vercel.app/) -
17
- [Pong](https://grip-connect-pong.vercel.app/)
30
+ [Kilter Board](https://grip-connect-kilter-board.vercel.app/)
18
31
 
19
32
  ## Install
20
33
 
@@ -77,7 +90,8 @@ available services with us.
77
90
 
78
91
  - ✅ Griptonite Motherboard
79
92
  - ✅ Tindeq Progressor
80
- - ☑️ Entralpi (not verified)
93
+ - Entralpi (not verified)
94
+ - ⏳ Kilterboard (write only, see example)
81
95
  - ➡️ Climbro
82
96
  - ➡️ mySmartBoard
83
97
  - ➡️ MAT Muscle Meter
@@ -113,9 +127,13 @@ A special thank you to:
113
127
  - [@donaldharvey](https://github.com/donaldharvey) for a valuable example on connecting to the Motherboard.
114
128
  - [@ecstrema](https://github.com/ecstrema) for providing [examples](https://github.com/ecstrema/entralpi-games) on how
115
129
  to play games with the Entralpi.
116
- - [Tindeq](https://tindeq.com/) for providing an open [Progressor API](https://tindeq.com/progressor_api/)
130
+ - [Tindeq](https://tindeq.com/) for providing an open [Progressor API](https://tindeq.com/progressor_api/).
117
131
  - [@StuartLittlefair](https://github.com/StuartLittlefair) for his
118
132
  [PyTindeq](https://github.com/StuartLittlefair/PyTindeq) implementation.
133
+ - [@Phil9l](https://github.com/phil9l) for his research and providing a [blog](https://bazun.me/blog/kiterboard/) on how
134
+ to connect with the Kilter Board.
135
+ - [@1-max-1](https://github.com/1-max-1) for the docs on his Kilter Board
136
+ [simulator](https://github.com/1-max-1/fake_kilter_board).
119
137
 
120
138
  ## Disclaimer
121
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hangtime/grip-connect",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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 Griptonite Motherboard, Climbro, SmartBoard, Entralpi or Tindeq Progressor",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
package/src/battery.ts CHANGED
@@ -14,12 +14,12 @@ export const battery = async (board: Device): Promise<void> => {
14
14
  // Check if the device is connected
15
15
  if (isConnected(board)) {
16
16
  // If the device is connected and it is a Motherboard device
17
- if (board.name === "Motherboard") {
17
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
18
18
  // Read battery level information from the Motherboard
19
19
  await read(Motherboard, "battery", "level", 250)
20
20
  }
21
21
  // If the device is connected and its name starts with "Progressor"
22
- if (board.name && board.name.startsWith("Progressor")) {
22
+ if (board.filters.some((filter) => filter.namePrefix === "Progressor")) {
23
23
  // Write command to get battery voltage information to the Progressor
24
24
  await write(Progressor, "progressor", "tx", ProgressorCommands.GET_BATT_VLTG, 250)
25
25
  }
@@ -13,7 +13,7 @@ export const calibration = async (board: Device): Promise<void> => {
13
13
  // Check if the device is connected
14
14
  if (isConnected(board)) {
15
15
  // If the device is connected, and it is a Motherboard device
16
- if (board.name === "Motherboard") {
16
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
17
17
  // Write the command to get calibration data to the device
18
18
  await write(Motherboard, "uart", "tx", MotherboardCommands.GET_CALIBRATION, 2500)
19
19
  }
package/src/connect.ts CHANGED
@@ -24,7 +24,8 @@ const handleNotifications = (event: Event, board: Device): void => {
24
24
  const value: DataView | undefined = characteristic.value
25
25
 
26
26
  if (value) {
27
- if (board.name === "Motherboard") {
27
+ // If the device is connected and it is a Motherboard device
28
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
28
29
  for (let i: number = 0; i < value.byteLength; i++) {
29
30
  receiveBuffer.push(value.getUint8(i))
30
31
  }
@@ -37,14 +38,14 @@ const handleNotifications = (event: Event, board: Device): void => {
37
38
  const receivedData: string = decoder.decode(new Uint8Array(line))
38
39
  handleMotherboardData(receivedData)
39
40
  }
40
- } else if (board.name === "ENTRALPI") {
41
+ } else if (board.filters.some((filter) => filter.name === "ENTRALPI")) {
41
42
  if (value.buffer) {
42
43
  const buffer: ArrayBuffer = value.buffer
43
44
  const rawData: DataView = new DataView(buffer)
44
45
  const receivedData: string = (rawData.getUint16(0) / 100).toFixed(1)
45
46
  handleEntralpiData(receivedData)
46
47
  }
47
- } else if (board.name && board.name.startsWith("Progressor")) {
48
+ } else if (board.filters.some((filter) => filter.namePrefix === "Progressor")) {
48
49
  if (value.buffer) {
49
50
  const buffer: ArrayBuffer = value.buffer
50
51
  const rawData: DataView = new DataView(buffer)
@@ -126,25 +127,8 @@ export const connect = async (board: Device, onSuccess: () => void): Promise<voi
126
127
  // Request device and set up connection
127
128
  const deviceServices = getAllServiceUUIDs(board)
128
129
 
129
- // setup filter list
130
- const filters = []
131
-
132
- if (board.name) {
133
- const filterName = board.name === "Progressor" ? { namePrefix: board.name } : { name: board.name }
134
- filters.push(filterName)
135
- }
136
- if (board.companyId) {
137
- filters.push({
138
- manufacturerData: [
139
- {
140
- companyIdentifier: board.companyId,
141
- },
142
- ],
143
- })
144
- }
145
-
146
130
  const device = await navigator.bluetooth.requestDevice({
147
- filters: filters,
131
+ filters: board.filters,
148
132
  optionalServices: deviceServices,
149
133
  })
150
134
 
@@ -5,6 +5,6 @@ import type { Device } from "../types/devices"
5
5
  * TODO: Add services, do you own a Climbro? Help us!
6
6
  */
7
7
  export const Climbro: Device = {
8
- name: "Climbro",
8
+ filters: [{ name: "Climbro" }],
9
9
  services: [],
10
10
  }
@@ -4,7 +4,11 @@ import type { Device } from "../types/devices"
4
4
  * Represents a Entralpi device
5
5
  */
6
6
  export const Entralpi: Device = {
7
- name: "ENTRALPI",
7
+ filters: [
8
+ {
9
+ name: "ENTRALPI",
10
+ },
11
+ ],
8
12
  services: [
9
13
  {
10
14
  name: "Device Information",
@@ -2,6 +2,8 @@ export { Climbro } from "./climbro"
2
2
 
3
3
  export { Entralpi } from "./entralpi"
4
4
 
5
+ export { KilterBoard } from "./kilterboard"
6
+
5
7
  export { Motherboard } from "./motherboard"
6
8
 
7
9
  export { mySmartBoard } from "./mysmartboard"
@@ -0,0 +1,32 @@
1
+ import type { Device } from "../types/devices"
2
+
3
+ /**
4
+ * Represents a Aurora Climbing device
5
+ * Kilter Board, Tension Board, Decoy Board, Touchstone Board, Grasshopper Board, Aurora Board, So iLL Board
6
+ */
7
+ export const KilterBoard: Device = {
8
+ filters: [
9
+ {
10
+ services: ["4488b571-7806-4df6-bcff-a2897e4953ff"], // Aurora Climbing Advertising service
11
+ },
12
+ ],
13
+ services: [
14
+ {
15
+ name: "UART Nordic Service",
16
+ id: "uart",
17
+ uuid: "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
18
+ characteristics: [
19
+ {
20
+ name: "TX",
21
+ id: "tx",
22
+ uuid: "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
23
+ },
24
+ // {
25
+ // name: "RX",
26
+ // id: "rx",
27
+ // uuid: "6e400003-b5a3-f393-e0a9-e50e24dcca9e",
28
+ // },
29
+ ],
30
+ },
31
+ ],
32
+ }
@@ -4,8 +4,7 @@ import type { Device } from "../types/devices"
4
4
  * Represents a Griptonite Motherboard device
5
5
  */
6
6
  export const Motherboard: Device = {
7
- name: "Motherboard",
8
- companyId: 0x2a29,
7
+ filters: [{ name: "Motherboard" }],
9
8
  services: [
10
9
  {
11
10
  name: "Device Information",
@@ -5,6 +5,6 @@ import type { Device } from "../types/devices"
5
5
  * TODO: Add services, do you own a MAT Muscle Meter? Help us!
6
6
  */
7
7
  export const MuscleMeter: Device = {
8
- name: "MAT",
8
+ filters: [{ name: "MAT" }],
9
9
  services: [],
10
10
  }
@@ -5,6 +5,6 @@ import type { Device } from "../types/devices"
5
5
  * TODO: Add services, do you own a mySmartBoard? Help us!
6
6
  */
7
7
  export const mySmartBoard: Device = {
8
- name: "mySmartBoard",
8
+ filters: [{ name: "mySmartBoard" }],
9
9
  services: [],
10
10
  }
@@ -3,7 +3,7 @@ import type { Device } from "../types/devices"
3
3
  * Represents a Tindeq Progressor device
4
4
  */
5
5
  export const Progressor: Device = {
6
- name: "Progressor",
6
+ filters: [{ namePrefix: "Progressor" }],
7
7
  services: [
8
8
  {
9
9
  name: "Progressor Service",
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Export device types
2
- export { Climbro, Entralpi, Motherboard, mySmartBoard, MuscleMeter, Progressor } from "./devices/index"
2
+ export { Climbro, Entralpi, KilterBoard, Motherboard, mySmartBoard, MuscleMeter, Progressor } from "./devices/index"
3
3
 
4
4
  // Export battery related functions
5
5
  export { battery } from "./battery"
package/src/info.ts CHANGED
@@ -12,7 +12,7 @@ import { MotherboardCommands, ProgressorCommands } from "./commands"
12
12
  */
13
13
  export const info = async (board: Device): Promise<void> => {
14
14
  if (isConnected(board)) {
15
- if (board.name === "Motherboard") {
15
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
16
16
  // Read manufacturer information
17
17
  await read(Motherboard, "device", "manufacturer", 250)
18
18
  // Read hardware version
@@ -24,7 +24,7 @@ export const info = async (board: Device): Promise<void> => {
24
24
  // Get serial number from Motherboard
25
25
  await write(Motherboard, "uart", "tx", MotherboardCommands.GET_SERIAL, 250)
26
26
  }
27
- if (board.name && board.name.startsWith("Progressor")) {
27
+ if (board.filters.some((filter) => filter.namePrefix === "Progressor")) {
28
28
  // Get firmware version from Progressor
29
29
  await write(Progressor, "progressor", "tx", ProgressorCommands.GET_FW_VERSION, 250)
30
30
  }
package/src/stop.ts CHANGED
@@ -11,11 +11,11 @@ import { MotherboardCommands, ProgressorCommands } from "./commands"
11
11
  */
12
12
  export const stop = async (board: Device): Promise<void> => {
13
13
  if (isConnected(board)) {
14
- if (board.name === "Motherboard") {
14
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
15
15
  // Stop stream on Motherboard
16
16
  await write(Motherboard, "uart", "tx", MotherboardCommands.STOP_WEIGHT_MEAS, 0)
17
17
  }
18
- if (board.name && board.name.startsWith("Progressor")) {
18
+ if (board.filters.some((filter) => filter.namePrefix === "Progressor")) {
19
19
  // Stop stream on Progressor
20
20
  await write(Progressor, "progressor", "tx", ProgressorCommands.STOP_WEIGHT_MEAS, 0)
21
21
  }
package/src/stream.ts CHANGED
@@ -19,7 +19,7 @@ export const stream = async (board: Device, duration: number = 0): Promise<void>
19
19
  // Reset download packets
20
20
  emptyDownloadPackets()
21
21
  // Device specific logic
22
- if (board.name === "Motherboard") {
22
+ if (board.filters.some((filter) => filter.name === "Motherboard")) {
23
23
  // Read calibration data if not already available
24
24
  if (!CALIBRATION[0].length) {
25
25
  await calibration(Motherboard)
@@ -31,7 +31,7 @@ export const stream = async (board: Device, duration: number = 0): Promise<void>
31
31
  await stop(Motherboard)
32
32
  }
33
33
  }
34
- if (board.name && board.name.startsWith("Progressor")) {
34
+ if (board.filters.some((filter) => filter.namePrefix === "Progressor")) {
35
35
  // Start streaming data
36
36
  await write(Progressor, "progressor", "tx", ProgressorCommands.START_WEIGHT_MEAS, duration)
37
37
  // Stop streaming if duration is set
@@ -30,10 +30,8 @@ interface Service {
30
30
  * Represents a Bluetooth device.
31
31
  */
32
32
  export interface Device {
33
- /** Name of the device */
34
- name: string
35
- /** Optional company identifier of the device */
36
- companyId?: number
33
+ /** Filters to indentify the device */
34
+ filters: BluetoothLEScanFilter[]
37
35
  /** Array of services provided by the device */
38
36
  services: Service[]
39
37
  /** Reference to the BluetoothDevice object representing this device */
package/src/write.ts CHANGED
@@ -55,9 +55,6 @@ export const write = (
55
55
  // Reject if characteristic is undefined
56
56
  reject(new Error("Characteristics is undefined"))
57
57
  }
58
- } else {
59
- // Reject if device is not connected
60
- reject(new Error("Device is not connected"))
61
58
  }
62
59
  })
63
60
  }