@hangtime/grip-connect 0.6.1 → 0.7.0

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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/deno.json +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -1
  5. package/dist/interfaces/command.interface.d.ts +6 -0
  6. package/dist/interfaces/device/climbro.interface.d.ts +1 -1
  7. package/dist/interfaces/device/entralpi.interface.d.ts +1 -1
  8. package/dist/interfaces/device/forceboard.interface.d.ts +30 -4
  9. package/dist/interfaces/device/kilterboard.interface.d.ts +1 -1
  10. package/dist/interfaces/device/motherboard.interface.d.ts +1 -1
  11. package/dist/interfaces/device/mysmartboard.interface.d.ts +1 -1
  12. package/dist/interfaces/device/progressor.interface.d.ts +1 -1
  13. package/dist/interfaces/device/wh-c06.interface.d.ts +1 -1
  14. package/dist/interfaces/device.interface.d.ts +3 -3
  15. package/dist/interfaces/index.d.ts +8 -8
  16. package/dist/models/base.model.d.ts +1 -1
  17. package/dist/models/device/climbro.model.d.ts +2 -2
  18. package/dist/models/device/climbro.model.js +1 -1
  19. package/dist/models/device/entralpi.model.d.ts +2 -2
  20. package/dist/models/device/entralpi.model.js +1 -1
  21. package/dist/models/device/forceboard.model.d.ts +29 -5
  22. package/dist/models/device/forceboard.model.js +95 -52
  23. package/dist/models/device/kilterboard.model.d.ts +3 -3
  24. package/dist/models/device/kilterboard.model.js +2 -2
  25. package/dist/models/device/motherboard.model.d.ts +2 -2
  26. package/dist/models/device/motherboard.model.js +1 -1
  27. package/dist/models/device/mysmartboard.model.d.ts +2 -2
  28. package/dist/models/device/mysmartboard.model.js +1 -1
  29. package/dist/models/device/progressor.model.d.ts +2 -2
  30. package/dist/models/device/progressor.model.js +1 -1
  31. package/dist/models/device/wh-c06.model.d.ts +2 -2
  32. package/dist/models/device/wh-c06.model.js +1 -1
  33. package/dist/models/device.model.d.ts +13 -23
  34. package/dist/models/device.model.js +51 -64
  35. package/dist/models/index.d.ts +8 -8
  36. package/dist/models/index.js +8 -8
  37. package/package.json +3 -3
  38. package/src/index.ts +2 -2
  39. package/src/interfaces/command.interface.ts +9 -0
  40. package/src/interfaces/device/climbro.interface.ts +1 -1
  41. package/src/interfaces/device/entralpi.interface.ts +1 -1
  42. package/src/interfaces/device/forceboard.interface.ts +34 -4
  43. package/src/interfaces/device/kilterboard.interface.ts +1 -1
  44. package/src/interfaces/device/motherboard.interface.ts +1 -1
  45. package/src/interfaces/device/mysmartboard.interface.ts +1 -1
  46. package/src/interfaces/device/progressor.interface.ts +1 -1
  47. package/src/interfaces/device/wh-c06.interface.ts +1 -1
  48. package/src/interfaces/device.interface.ts +3 -3
  49. package/src/interfaces/index.ts +8 -8
  50. package/src/models/base.model.ts +1 -1
  51. package/src/models/device/climbro.model.ts +2 -2
  52. package/src/models/device/entralpi.model.ts +2 -2
  53. package/src/models/device/forceboard.model.ts +104 -53
  54. package/src/models/device/kilterboard.model.ts +3 -3
  55. package/src/models/device/motherboard.model.ts +3 -3
  56. package/src/models/device/mysmartboard.model.ts +2 -2
  57. package/src/models/device/progressor.model.ts +2 -2
  58. package/src/models/device/wh-c06.model.ts +2 -2
  59. package/src/models/device.model.ts +60 -77
  60. package/src/models/index.ts +8 -8
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IForceBoard } from "../../interfaces/device/forceboard.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IForceBoard } from "../../interfaces/device/forceboard.interface"
3
3
 
4
4
  /**
5
5
  * Represents a PitchSix Force Board device.
@@ -108,13 +108,13 @@ export class ForceBoard extends Device implements IForceBoard {
108
108
  uuid: "9a88d681-8df2-4afe-9e0d-c2bbbe773dd0",
109
109
  },
110
110
  {
111
- name: "Read + Notify",
111
+ name: "Force Data",
112
112
  id: "rx",
113
113
  uuid: "9a88d682-8df2-4afe-9e0d-c2bbbe773dd0",
114
114
  },
115
115
  {
116
- name: "Write",
117
- id: "",
116
+ name: "Tare",
117
+ id: "tare",
118
118
  uuid: "9a88d683-8df2-4afe-9e0d-c2bbbe773dd0",
119
119
  },
120
120
  {
@@ -123,8 +123,8 @@ export class ForceBoard extends Device implements IForceBoard {
123
123
  uuid: "9a88d685-8df2-4afe-9e0d-c2bbbe773dd0",
124
124
  },
125
125
  {
126
- name: "Write",
127
- id: "",
126
+ name: "Threshold",
127
+ id: "threshold",
128
128
  uuid: "9a88d686-8df2-4afe-9e0d-c2bbbe773dd0",
129
129
  },
130
130
  {
@@ -145,12 +145,12 @@ export class ForceBoard extends Device implements IForceBoard {
145
145
  ],
146
146
  },
147
147
  {
148
- name: "Weight Serivce",
148
+ name: "Weight Service",
149
149
  id: "weight",
150
150
  uuid: "467a8516-6e39-11eb-9439-0242ac130002",
151
151
  characteristics: [
152
152
  {
153
- name: "Read + Write",
153
+ name: "Device Mode",
154
154
  id: "tx",
155
155
  uuid: "467a8517-6e39-11eb-9439-0242ac130002",
156
156
  },
@@ -163,7 +163,10 @@ export class ForceBoard extends Device implements IForceBoard {
163
163
  },
164
164
  ],
165
165
  commands: {
166
- STOP_WEIGHT_MEAS: "",
166
+ START_WEIGHT_MEAS: String.fromCharCode(0x04), // Streaming Data Mode: continuously streams force data.
167
+ TARE_SCALE: String.fromCharCode(0x05), // Tare function: zeroes out the current load value
168
+ START_QUICK_MEAS: String.fromCharCode(0x06), // Quick Start Mode: Starts data transmission when a force value exceeds the Threshold and stops data transmission when the force data drops below the Threshold.
169
+ STOP_WEIGHT_MEAS: String.fromCharCode(0x07), // Idle Mode: Force Board is idle.
167
170
  },
168
171
  })
169
172
  }
@@ -191,43 +194,51 @@ export class ForceBoard extends Device implements IForceBoard {
191
194
  if (value.buffer) {
192
195
  const receivedTime: number = Date.now()
193
196
  const dataArray = new Uint8Array(value.buffer)
194
- // Skip the first 2 bytes, which are the command and length
195
- // The data is sent in groups of 3 bytes
196
- for (let i = 2; i < dataArray.length; i += 3) {
197
- const receivedData = (dataArray[i] << 16) | (dataArray[i + 1] << 8) | dataArray[i + 2]
198
- // Convert from LBS to KG
199
- const convertedReceivedData = receivedData * 0.453592
200
- // Tare correction
201
- const numericData = convertedReceivedData - this.applyTare(convertedReceivedData)
202
- // Add data to downloadable Array
203
- this.downloadPackets.push({
204
- received: receivedTime,
205
- sampleNum: this.dataPointCount,
206
- battRaw: 0,
207
- samples: [convertedReceivedData],
208
- masses: [numericData],
209
- })
210
197
 
211
- // Update massMax
212
- this.massMax = Math.max(Number(this.massMax), numericData).toFixed(1)
198
+ // First two bytes contain the number of samples in the packet
199
+ const numSamples = (dataArray[0] << 8) | dataArray[1]
200
+
201
+ // Process each sample (3 bytes per sample)
202
+ for (let i = 0; i < numSamples; i++) {
203
+ const offset = 2 + i * 3 // Skip the first 2 bytes which indicate number of samples
204
+ if (offset + 2 < dataArray.length) {
205
+ // Sample = byte1*32768 + byte2*256 + byte3
206
+ const receivedData = dataArray[offset] * 32768 + dataArray[offset + 1] * 256 + dataArray[offset + 2]
207
+
208
+ // Convert from LBS to KG
209
+ const convertedReceivedData = receivedData * 0.453592
210
+ // Tare correction
211
+ const numericData = convertedReceivedData - this.applyTare(convertedReceivedData)
212
+ // Add data to downloadable Array
213
+ this.downloadPackets.push({
214
+ received: receivedTime,
215
+ sampleNum: this.dataPointCount,
216
+ battRaw: 0,
217
+ samples: [convertedReceivedData],
218
+ masses: [numericData],
219
+ })
213
220
 
214
- // Update running sum and count
215
- const currentMassTotal = Math.max(-1000, numericData)
216
- this.massTotalSum += currentMassTotal
217
- this.dataPointCount++
221
+ // Update massMax
222
+ this.massMax = Math.max(Number(this.massMax), numericData).toFixed(1)
218
223
 
219
- // Calculate the average dynamically
220
- this.massAverage = (this.massTotalSum / this.dataPointCount).toFixed(1)
224
+ // Update running sum and count
225
+ const currentMassTotal = Math.max(-1000, numericData)
226
+ this.massTotalSum += currentMassTotal
227
+ this.dataPointCount++
221
228
 
222
- // Check if device is being used
223
- this.activityCheck(numericData)
229
+ // Calculate the average dynamically
230
+ this.massAverage = (this.massTotalSum / this.dataPointCount).toFixed(1)
224
231
 
225
- // Notify with weight data
226
- this.notifyCallback({
227
- massMax: this.massMax,
228
- massAverage: this.massAverage,
229
- massTotal: Math.max(-1000, numericData).toFixed(1),
230
- })
232
+ // Check if device is being used
233
+ this.activityCheck(numericData)
234
+
235
+ // Notify with weight data
236
+ this.notifyCallback({
237
+ massMax: this.massMax,
238
+ massAverage: this.massAverage,
239
+ massTotal: Math.max(-1000, numericData).toFixed(1),
240
+ })
241
+ }
231
242
  }
232
243
  }
233
244
  }
@@ -250,7 +261,7 @@ export class ForceBoard extends Device implements IForceBoard {
250
261
  }
251
262
 
252
263
  /**
253
- * Stops the data stream on the specified device.
264
+ * Stops the data stream on the specified device by setting it to Idle mode.
254
265
  * @returns {Promise<void>} A promise that resolves when the stream is stopped.
255
266
  */
256
267
  stop = async (): Promise<void> => {
@@ -258,26 +269,66 @@ export class ForceBoard extends Device implements IForceBoard {
258
269
  }
259
270
 
260
271
  /**
261
- * Starts streaming data from the specified device.
272
+ * Starts streaming data from the specified device in Streaming Data Mode.
262
273
  * @param {number} [duration=0] - The duration of the stream in milliseconds. If set to 0, stream will continue indefinitely.
263
274
  * @returns {Promise<void>} A promise that resolves when the streaming operation is completed.
264
275
  */
265
276
  stream = async (duration = 0): Promise<void> => {
266
- // Reset download packets
267
- this.downloadPackets.length = 0
268
- // Start streaming data
269
- await this.write("weight", "tx", new Uint8Array([0x04]), duration) // ASCII control character EOT (End of Transmission)
270
- // Stop streaming if duration is set
271
- if (duration !== 0) {
272
- await this.stop()
273
- }
277
+ // Start streaming data - Streaming Data Mode
278
+ await this.write("weight", "tx", this.commands.START_WEIGHT_MEAS, duration)
279
+ }
280
+
281
+ /**
282
+ * Sets the threshold in Lbs for the Quick Start mode.
283
+ * @param {number} thresholdLbs - The threshold value in pounds.
284
+ * @returns {Promise<void>} A promise that resolves when the threshold is set.
285
+ */
286
+ threshold = async (thresholdLbs: number): Promise<void> => {
287
+ const thresholdHex = thresholdLbs.toString(16).padStart(6, "0")
288
+ // 3-byte array from the hex string
289
+ const bytes = new Uint8Array(3)
290
+ bytes[0] = parseInt(thresholdHex.substring(0, 2), 16)
291
+ bytes[1] = parseInt(thresholdHex.substring(2, 4), 16)
292
+ bytes[2] = parseInt(thresholdHex.substring(4, 6), 16)
293
+
294
+ await this.write("forceboard", "threshold", String.fromCharCode(...bytes), 0)
295
+ }
296
+
297
+ /**
298
+ * Tares the Force Board device using a characteristic to zero out the current load value.
299
+ * @returns {Promise<void>} A promise that resolves when the tare operation is completed.
300
+ */
301
+ tareByCharacteristic = async (): Promise<void> => {
302
+ // Send tare command (0x01) to the tare characteristic
303
+ const tareValue = String.fromCharCode(0x01)
304
+ await this.write("forceboard", "tare", tareValue, 0)
305
+ }
306
+
307
+ /**
308
+ * Initiates a tare routine via the Device Mode characteristic.
309
+ * Writes 0x05 to the Device Mode characteristic to zero out the current load value.
310
+ * @returns {Promise<void>} A promise that resolves when the tare operation is completed.
311
+ */
312
+ tareByMode = async (): Promise<void> => {
313
+ await this.write("weight", "tx", this.commands.TARE_SCALE, 0)
274
314
  }
275
315
 
276
316
  /**
277
317
  * Retrieves temperature information from the device.
278
- * @returns {Promise<string>} A Promise that resolves with the manufacturer information,
318
+ * @returns {Promise<string>} A Promise that resolves with the temperature information,
279
319
  */
280
320
  temperature = async (): Promise<string | undefined> => {
281
321
  return await this.read("temperature", "level", 250)
282
322
  }
323
+
324
+ /**
325
+ * Starts the Force Board in Quick Start mode.
326
+ * Writes 0x06 to the Device Mode characteristic.
327
+ * @param {number} [duration=0] - The duration in milliseconds. If set to 0, mode will continue indefinitely.
328
+ * @returns {Promise<void>} A promise that resolves when the operation is completed.
329
+ */
330
+ quick = async (duration = 0): Promise<void> => {
331
+ // Start in Quick Start mode
332
+ await this.write("weight", "tx", this.commands.START_QUICK_MEAS, duration)
333
+ }
283
334
  }
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IKilterBoard } from "../../interfaces/device/kilterboard.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IKilterBoard } from "../../interfaces/device/kilterboard.interface"
3
3
 
4
4
  /**
5
5
  * For API level 2 and API level 3.
@@ -254,7 +254,7 @@ export class KilterBoard extends Device implements IKilterBoard {
254
254
 
255
255
  /**
256
256
  * Splits a collection into slices of the specified length.
257
- * https://github.com/ramda/ramda/blob/master/source/splitEvery.js
257
+ * https://github.com/ramda/ramda/blob/master/source/splitEvery
258
258
  * @param {Number} n
259
259
  * @param {Array} list
260
260
  * @return {Array<number[]>}
@@ -1,6 +1,6 @@
1
- import { Device } from "../device.model.js"
2
- import type { IMotherboard } from "../../interfaces/device/motherboard.interface.js"
3
- import type { DownloadPacket } from "../../interfaces/download.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IMotherboard } from "../../interfaces/device/motherboard.interface"
3
+ import type { DownloadPacket } from "../../interfaces/download.interface"
4
4
 
5
5
  /**
6
6
  * Represents a Griptonite Motherboard device.
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { ImySmartBoard } from "../../interfaces/device/mysmartboard.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { ImySmartBoard } from "../../interfaces/device/mysmartboard.interface"
3
3
 
4
4
  /**
5
5
  * Represents a Smartboard Climbing mySmartBoard device.
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IProgressor } from "../../interfaces/device/progressor.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IProgressor } from "../../interfaces/device/progressor.interface"
3
3
 
4
4
  /**
5
5
  * Progressor responses
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IWHC06 } from "../../interfaces/device/wh-c06.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IWHC06 } from "../../interfaces/device/wh-c06.interface"
3
3
 
4
4
  /**
5
5
  * Represents a Weiheng - WH-C06 (or MAT Muscle Meter) device.
@@ -1,8 +1,8 @@
1
- import { BaseModel } from "./../models/base.model.js"
2
- import type { IDevice, Service } from "../interfaces/device.interface.js"
3
- import type { ActiveCallback, massObject, NotifyCallback, WriteCallback } from "../interfaces/callback.interface.js"
4
- import type { DownloadPacket } from "../interfaces/download.interface.js"
5
- import type { Commands } from "../interfaces/command.interface.js"
1
+ import { BaseModel } from "./../models/base.model"
2
+ import type { IDevice, Service } from "../interfaces/device.interface"
3
+ import type { ActiveCallback, massObject, NotifyCallback, WriteCallback } from "../interfaces/callback.interface"
4
+ import type { DownloadPacket } from "../interfaces/download.interface"
5
+ import type { Commands } from "../interfaces/command.interface"
6
6
 
7
7
  export abstract class Device extends BaseModel implements IDevice {
8
8
  /**
@@ -29,7 +29,7 @@ export abstract class Device extends BaseModel implements IDevice {
29
29
  * @type {BluetoothDevice | undefined}
30
30
  * @public
31
31
  */
32
- bluetooth?: BluetoothDevice | undefined
32
+ bluetooth?: BluetoothDevice
33
33
 
34
34
  /**
35
35
  * Object representing the set of commands available for this device.
@@ -240,22 +240,16 @@ export abstract class Device extends BaseModel implements IDevice {
240
240
  * @example
241
241
  * await device.activityCheck(5.0);
242
242
  */
243
- protected activityCheck = (input: number): Promise<void> => {
244
- return new Promise((resolve) => {
245
- const startValue = input
246
- const { threshold, duration } = this.activeConfig
247
- setTimeout(() => {
248
- // After waiting for `duration`, check if still active (for a real scenario, you might store a last known input)
249
- const activeNow = startValue > threshold
250
- if (this.isActive !== activeNow) {
251
- this.isActive = activeNow
252
- if (this.activeCallback) {
253
- this.activeCallback(activeNow)
254
- }
255
- }
256
- resolve()
257
- }, duration)
258
- })
243
+ protected activityCheck = async (input: number): Promise<void> => {
244
+ const startValue = input
245
+ const { threshold, duration } = this.activeConfig
246
+ // After waiting for `duration`, check if still active.
247
+ await new Promise((resolve) => setTimeout(resolve, duration))
248
+ const activeNow = startValue > threshold
249
+ if (this.isActive !== activeNow) {
250
+ this.isActive = activeNow
251
+ this.activeCallback?.(activeNow)
252
+ }
259
253
  }
260
254
 
261
255
  /**
@@ -319,7 +313,7 @@ export abstract class Device extends BaseModel implements IDevice {
319
313
  // Remove all notification listeners
320
314
  this.services.forEach((service) => {
321
315
  service.characteristics.forEach((char) => {
322
- // TODO: remove device-specific logic
316
+ // Look for the "rx" characteristic that accepts notifications
323
317
  if (char.characteristic && char.id === "rx") {
324
318
  char.characteristic.stopNotifications()
325
319
  const listener = this.notificationListeners.get(char.uuid)
@@ -494,53 +488,30 @@ export abstract class Device extends BaseModel implements IDevice {
494
488
  }
495
489
 
496
490
  /**
497
- * Attempt to use ES module import rather than require.
498
- * This approach uses an async dynamic import for `webbluetooth`,
499
- * so we can fallback if `navigator.bluetooth` is unavailable.
491
+ * Returns the Bluetooth instance available for the current environment.
492
+ * In browsers, it returns the native Web Bluetooth API (i.e. `navigator.bluetooth`).
493
+ * In a Node, Bun, or Deno environment, it dynamically imports the `webbluetooth` package.
494
+ * {@link https://github.com/thegecko/webbluetooth}
495
+ *
496
+ * @returns {Promise<Bluetooth>} A promise that resolves to the Bluetooth instance.
497
+ * @throws {Error} If Web Bluetooth is not available in the current environment.
500
498
  */
501
- protected async getBluetooth() {
502
- // If we're in a browser with real Web Bluetooth available:
503
- if (typeof navigator !== "undefined" && "bluetooth" in navigator) {
499
+ protected async getBluetooth(): Promise<Bluetooth> {
500
+ // If running in a browser with native Web Bluetooth support:
501
+ if (typeof navigator !== "undefined" && navigator.bluetooth) {
504
502
  return navigator.bluetooth
505
503
  }
506
504
 
507
- // Otherwise, we're likely in Node or an environment without `navigator.bluetooth`.
508
- // Use a dynamic import for the ESM version:
509
- const { bluetooth } = await import("webbluetooth")
510
- return bluetooth
511
- }
505
+ const process = await import("node:process")
512
506
 
513
- /**
514
- * Retrieves the characteristic from the device's service.
515
- * @param {string} serviceId - The UUID of the service.
516
- * @param {string} characteristicId - The UUID of the characteristic.
517
- * @returns {BluetoothRemoteGATTCharacteristic | undefined} The characteristic, if found.
518
- * @protected
519
- *
520
- * @example
521
- * const characteristic = device.getCharacteristic('battery', 'level');
522
- * if (characteristic) {
523
- * console.log('Characteristic found');
524
- * }
525
- */
526
- protected getCharacteristic = (
527
- serviceId: string,
528
- characteristicId: string,
529
- ): BluetoothRemoteGATTCharacteristic | undefined => {
530
- // Find the service with the specified serviceId
531
- const boardService = this.services.find((service) => service.id === serviceId)
532
- if (boardService) {
533
- // If the service is found, find the characteristic with the specified characteristicId
534
- const boardCharacteristic = boardService.characteristics.find(
535
- (characteristic) => characteristic.id === characteristicId,
536
- )
537
- if (boardCharacteristic) {
538
- // If the characteristic is found, return it
539
- return boardCharacteristic.characteristic
540
- }
507
+ // If running in Node, Bun, or Deno environment
508
+ if (typeof process !== "undefined" && process.versions?.node) {
509
+ const { bluetooth } = await import("webbluetooth")
510
+ return bluetooth
541
511
  }
542
- // Return undefined if the service or characteristic is not found
543
- return undefined
512
+
513
+ // If none of the above conditions are met, throw an error.
514
+ throw new Error("Bluetooth not available.")
544
515
  }
545
516
 
546
517
  /**
@@ -573,7 +544,7 @@ export abstract class Device extends BaseModel implements IDevice {
573
544
  */
574
545
  isConnected = (): boolean => {
575
546
  // Check if the device is defined and available
576
- if (!this?.bluetooth) {
547
+ if (!this.bluetooth) {
577
548
  return false
578
549
  }
579
550
  // Check if the device is connected
@@ -612,7 +583,7 @@ export abstract class Device extends BaseModel implements IDevice {
612
583
  throw new Error("GATT server is not available")
613
584
  }
614
585
  // Connect to GATT server and set up characteristics
615
- const services: BluetoothRemoteGATTService[] | undefined = await this.server.getPrimaryServices()
586
+ const services: BluetoothRemoteGATTService[] = await this.server.getPrimaryServices()
616
587
 
617
588
  if (!services || services.length === 0) {
618
589
  throw new Error("No services found")
@@ -622,7 +593,7 @@ export abstract class Device extends BaseModel implements IDevice {
622
593
  const matchingService = this.services.find((boardService) => boardService.uuid === service.uuid)
623
594
 
624
595
  if (matchingService) {
625
- // Android bug: Introduce a delay before getting characteristics
596
+ // Android bug: Add a small delay before getting characteristics
626
597
  await new Promise((resolve) => setTimeout(resolve, 100))
627
598
 
628
599
  const characteristics = await service.getCharacteristics()
@@ -631,21 +602,28 @@ export abstract class Device extends BaseModel implements IDevice {
631
602
  const matchingCharacteristic = characteristics.find((char) => char.uuid === characteristic.uuid)
632
603
 
633
604
  if (matchingCharacteristic) {
634
- const element = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid)
635
- if (element) {
636
- element.characteristic = matchingCharacteristic
637
-
638
- // TODO: remove device-specific logic
639
- if (element.id === "rx") {
605
+ // Find the corresponding characteristic descriptor in the service's characteristics array
606
+ const descriptor = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid)
607
+ if (descriptor) {
608
+ // Assign the actual Bluetooth characteristic object to the descriptor so it can be used later
609
+ descriptor.characteristic = matchingCharacteristic
610
+ // Look for the "rx" characteristic id that accepts notifications
611
+ if (descriptor.id === "rx") {
612
+ // Start receiving notifications for changes on this characteristic
640
613
  matchingCharacteristic.startNotifications()
614
+ // Triggered when the characteristic's value changes
641
615
  const listener = (event: Event) => {
616
+ // Cast the event's target to a BluetoothRemoteGATTCharacteristic to access its properties
642
617
  const target = event.target as BluetoothRemoteGATTCharacteristic
643
618
  if (target && target.value) {
619
+ // Delegate the data to handleNotifications method
644
620
  this.handleNotifications(target)
645
621
  }
646
622
  }
623
+ // Attach the event listener to listen for changes in the characteristic's value
647
624
  matchingCharacteristic.addEventListener("characteristicvaluechanged", listener)
648
- this.notificationListeners.set(element.uuid, listener)
625
+ // Store the listener so it can be referenced (for later removal)
626
+ this.notificationListeners.set(descriptor.uuid, listener)
649
627
  }
650
628
  }
651
629
  } else {
@@ -654,7 +632,6 @@ export abstract class Device extends BaseModel implements IDevice {
654
632
  }
655
633
  }
656
634
  }
657
-
658
635
  // Call the onSuccess callback after successful connection and setup
659
636
  onSuccess()
660
637
  }
@@ -689,7 +666,10 @@ export abstract class Device extends BaseModel implements IDevice {
689
666
  return undefined
690
667
  }
691
668
  // Get the characteristic from the service
692
- const characteristic = this.getCharacteristic(serviceId, characteristicId)
669
+ const characteristic = this.services
670
+ .find((service) => service.id === serviceId)
671
+ ?.characteristics.find((char) => char.id === characteristicId)?.characteristic
672
+
693
673
  if (!characteristic) {
694
674
  throw new Error(`Characteristic "${characteristicId}" not found in service "${serviceId}"`)
695
675
  }
@@ -815,7 +795,10 @@ export abstract class Device extends BaseModel implements IDevice {
815
795
  return Promise.resolve()
816
796
  }
817
797
  // Get the characteristic from the service
818
- const characteristic = this.getCharacteristic(serviceId, characteristicId)
798
+ const characteristic = this.services
799
+ .find((service) => service.id === serviceId)
800
+ ?.characteristics.find((char) => char.id === characteristicId)?.characteristic
801
+
819
802
  if (!characteristic) {
820
803
  throw new Error(`Characteristic "${characteristicId}" not found in service "${serviceId}"`)
821
804
  }
@@ -1,15 +1,15 @@
1
- export { Climbro } from "./device/climbro.model.js"
1
+ export { Climbro } from "./device/climbro.model"
2
2
 
3
- export { Entralpi } from "./device/entralpi.model.js"
3
+ export { Entralpi } from "./device/entralpi.model"
4
4
 
5
- export { ForceBoard } from "./device/forceboard.model.js"
5
+ export { ForceBoard } from "./device/forceboard.model"
6
6
 
7
- export { KilterBoard } from "./device/kilterboard.model.js"
7
+ export { KilterBoard } from "./device/kilterboard.model"
8
8
 
9
- export { Motherboard } from "./device/motherboard.model.js"
9
+ export { Motherboard } from "./device/motherboard.model"
10
10
 
11
- export { mySmartBoard } from "./device/mysmartboard.model.js"
11
+ export { mySmartBoard } from "./device/mysmartboard.model"
12
12
 
13
- export { Progressor } from "./device/progressor.model.js"
13
+ export { Progressor } from "./device/progressor.model"
14
14
 
15
- export { WHC06 } from "./device/wh-c06.model.js"
15
+ export { WHC06 } from "./device/wh-c06.model"