@hangtime/grip-connect 0.6.1 → 0.6.2

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 (57) hide show
  1. package/README.md +2 -3
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.js +1 -1
  4. package/dist/interfaces/device/climbro.interface.d.ts +1 -1
  5. package/dist/interfaces/device/entralpi.interface.d.ts +1 -1
  6. package/dist/interfaces/device/forceboard.interface.d.ts +1 -1
  7. package/dist/interfaces/device/kilterboard.interface.d.ts +1 -1
  8. package/dist/interfaces/device/motherboard.interface.d.ts +1 -1
  9. package/dist/interfaces/device/mysmartboard.interface.d.ts +1 -1
  10. package/dist/interfaces/device/progressor.interface.d.ts +1 -1
  11. package/dist/interfaces/device/wh-c06.interface.d.ts +1 -1
  12. package/dist/interfaces/device.interface.d.ts +3 -3
  13. package/dist/interfaces/index.d.ts +8 -8
  14. package/dist/models/base.model.d.ts +1 -1
  15. package/dist/models/device/climbro.model.d.ts +2 -2
  16. package/dist/models/device/climbro.model.js +1 -1
  17. package/dist/models/device/entralpi.model.d.ts +2 -2
  18. package/dist/models/device/entralpi.model.js +1 -1
  19. package/dist/models/device/forceboard.model.d.ts +2 -2
  20. package/dist/models/device/forceboard.model.js +1 -1
  21. package/dist/models/device/kilterboard.model.d.ts +3 -3
  22. package/dist/models/device/kilterboard.model.js +2 -2
  23. package/dist/models/device/motherboard.model.d.ts +2 -2
  24. package/dist/models/device/motherboard.model.js +1 -1
  25. package/dist/models/device/mysmartboard.model.d.ts +2 -2
  26. package/dist/models/device/mysmartboard.model.js +1 -1
  27. package/dist/models/device/progressor.model.d.ts +2 -2
  28. package/dist/models/device/progressor.model.js +1 -1
  29. package/dist/models/device/wh-c06.model.d.ts +2 -2
  30. package/dist/models/device/wh-c06.model.js +1 -1
  31. package/dist/models/device.model.d.ts +13 -23
  32. package/dist/models/device.model.js +51 -64
  33. package/dist/models/index.d.ts +8 -8
  34. package/dist/models/index.js +8 -8
  35. package/package.json +3 -3
  36. package/src/index.ts +2 -2
  37. package/src/interfaces/device/climbro.interface.ts +1 -1
  38. package/src/interfaces/device/entralpi.interface.ts +1 -1
  39. package/src/interfaces/device/forceboard.interface.ts +1 -1
  40. package/src/interfaces/device/kilterboard.interface.ts +1 -1
  41. package/src/interfaces/device/motherboard.interface.ts +1 -1
  42. package/src/interfaces/device/mysmartboard.interface.ts +1 -1
  43. package/src/interfaces/device/progressor.interface.ts +1 -1
  44. package/src/interfaces/device/wh-c06.interface.ts +1 -1
  45. package/src/interfaces/device.interface.ts +3 -3
  46. package/src/interfaces/index.ts +8 -8
  47. package/src/models/base.model.ts +1 -1
  48. package/src/models/device/climbro.model.ts +2 -2
  49. package/src/models/device/entralpi.model.ts +2 -2
  50. package/src/models/device/forceboard.model.ts +2 -2
  51. package/src/models/device/kilterboard.model.ts +3 -3
  52. package/src/models/device/motherboard.model.ts +3 -3
  53. package/src/models/device/mysmartboard.model.ts +2 -2
  54. package/src/models/device/progressor.model.ts +2 -2
  55. package/src/models/device/wh-c06.model.ts +2 -2
  56. package/src/models/device.model.ts +60 -77
  57. package/src/models/index.ts +8 -8
package/README.md CHANGED
@@ -35,9 +35,8 @@ Learn more: [Documentation](https://stevie-ray.github.io/hangtime-grip-connect/)
35
35
 
36
36
  ## Install
37
37
 
38
- This package is compatible with both Node.js and browser environments. It can be found in the
39
- [NPM](https://www.npmjs.com/package/@hangtime/grip-connect) and [JSR](https://jsr.io/@hangtime/grip-connect) package
40
- registries.
38
+ This package works with Node.js, Bun, Deno, and the web, and is available on both
39
+ [NPM](https://www.npmjs.com/package/@hangtime/grip-connect) and [JSR](https://jsr.io/@hangtime/grip-connect).
41
40
 
42
41
  ```sh [npm]
43
42
  $ npm install @hangtime/grip-connect
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { IClimbro, IEntralpi, IForceBoard, IKilterBoard, IMotherboard, ImySmartBoard, IProgressor, IWHC06, } from "./interfaces/index.js";
2
- export { Climbro, Entralpi, ForceBoard, KilterBoard, Motherboard, mySmartBoard, Progressor, WHC06, } from "./models/index.js";
1
+ export type { IClimbro, IEntralpi, IForceBoard, IKilterBoard, IMotherboard, ImySmartBoard, IProgressor, IWHC06, } from "./interfaces/index";
2
+ export { Climbro, Entralpi, ForceBoard, KilterBoard, Motherboard, mySmartBoard, Progressor, WHC06, } from "./models/index";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { Climbro, Entralpi, ForceBoard, KilterBoard, Motherboard, mySmartBoard, Progressor, WHC06, } from "./models/index.js";
1
+ export { Climbro, Entralpi, ForceBoard, KilterBoard, Motherboard, mySmartBoard, Progressor, WHC06, } from "./models/index";
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Climbro device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Entralpi device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the PitchSix ForceBoard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the KilterBoard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Griptonite Motherboard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Smartboard Climbing mySmartBoard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Tindeq Progressor device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js";
1
+ import type { IDevice } from "../device.interface";
2
2
  /**
3
3
  * Interface representing the Weiheng WH-C06 device, extending the base Device interface.
4
4
  */
@@ -1,6 +1,6 @@
1
- import type { IBase } from "./base.interface.js";
2
- import type { massObject } from "./callback.interface.js";
3
- import type { Commands } from "./command.interface.js";
1
+ import type { IBase } from "./base.interface";
2
+ import type { massObject } from "./callback.interface";
3
+ import type { Commands } from "./command.interface";
4
4
  /**
5
5
  * Represents a characteristic of a Bluetooth service.
6
6
  */
@@ -1,8 +1,8 @@
1
- export type { IClimbro } from "./device/climbro.interface.js";
2
- export type { IEntralpi } from "./device/entralpi.interface.js";
3
- export type { IForceBoard } from "./device/forceboard.interface.js";
4
- export type { IKilterBoard } from "./device/kilterboard.interface.js";
5
- export type { IMotherboard } from "./device/motherboard.interface.js";
6
- export type { ImySmartBoard } from "./device/mysmartboard.interface.js";
7
- export type { IProgressor } from "./device/progressor.interface.js";
8
- export type { IWHC06 } from "./device/wh-c06.interface.js";
1
+ export type { IClimbro } from "./device/climbro.interface";
2
+ export type { IEntralpi } from "./device/entralpi.interface";
3
+ export type { IForceBoard } from "./device/forceboard.interface";
4
+ export type { IKilterBoard } from "./device/kilterboard.interface";
5
+ export type { IMotherboard } from "./device/motherboard.interface";
6
+ export type { ImySmartBoard } from "./device/mysmartboard.interface";
7
+ export type { IProgressor } from "./device/progressor.interface";
8
+ export type { IWHC06 } from "./device/wh-c06.interface";
@@ -1,4 +1,4 @@
1
- import type { IBase } from "../interfaces/base.interface.js";
1
+ import type { IBase } from "../interfaces/base.interface";
2
2
  export declare abstract class BaseModel {
3
3
  id?: string;
4
4
  createdAt?: Date;
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js";
2
- import type { IClimbro } from "../../interfaces/device/climbro.interface.js";
1
+ import { Device } from "../device.model";
2
+ import type { IClimbro } from "../../interfaces/device/climbro.interface";
3
3
  /**
4
4
  * Represents a Climbro device.
5
5
  * TODO: Add services, do you own a Climbro? Help us!
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a Climbro device.
4
4
  * TODO: Add services, do you own a Climbro? Help us!
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js";
2
- import type { IEntralpi } from "../../interfaces/device/entralpi.interface.js";
1
+ import { Device } from "../device.model";
2
+ import type { IEntralpi } from "../../interfaces/device/entralpi.interface";
3
3
  /**
4
4
  * Represents a Entralpi device.
5
5
  * {@link https://entralpi.com}
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a Entralpi device.
4
4
  * {@link https://entralpi.com}
@@ -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
  * Represents a PitchSix Force Board device.
5
5
  * {@link https://pitchsix.com}
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a PitchSix Force Board device.
4
4
  * {@link https://pitchsix.com}
@@ -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
  * For API level 2 and API level 3.
5
5
  * The first byte in the data is dependent on where the packet is in the message as a whole.
@@ -113,7 +113,7 @@ export declare class KilterBoard extends Device implements IKilterBoard {
113
113
  private prepBytesV3;
114
114
  /**
115
115
  * Splits a collection into slices of the specified length.
116
- * https://github.com/ramda/ramda/blob/master/source/splitEvery.js
116
+ * https://github.com/ramda/ramda/blob/master/source/splitEvery
117
117
  * @param {Number} n
118
118
  * @param {Array} list
119
119
  * @return {Array<number[]>}
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * For API level 2 and API level 3.
4
4
  * The first byte in the data is dependent on where the packet is in the message as a whole.
@@ -234,7 +234,7 @@ export class KilterBoard extends Device {
234
234
  }
235
235
  /**
236
236
  * Splits a collection into slices of the specified length.
237
- * https://github.com/ramda/ramda/blob/master/source/splitEvery.js
237
+ * https://github.com/ramda/ramda/blob/master/source/splitEvery
238
238
  * @param {Number} n
239
239
  * @param {Array} list
240
240
  * @return {Array<number[]>}
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js";
2
- import type { IMotherboard } from "../../interfaces/device/motherboard.interface.js";
1
+ import { Device } from "../device.model";
2
+ import type { IMotherboard } from "../../interfaces/device/motherboard.interface";
3
3
  /**
4
4
  * Represents a Griptonite Motherboard device.
5
5
  * {@link https://griptonite.io}
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a Griptonite Motherboard device.
4
4
  * {@link https://griptonite.io}
@@ -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
  * Represents a Smartboard Climbing mySmartBoard device.
5
5
  * TODO: Add services, do you own a mySmartBoard? Help us!
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a Smartboard Climbing mySmartBoard device.
4
4
  * TODO: Add services, do you own a mySmartBoard? Help us!
@@ -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
  * Represents a Tindeq Progressor device.
5
5
  * {@link https://tindeq.com}
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Progressor responses
4
4
  */
@@ -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
  * Represents a Weiheng - WH-C06 (or MAT Muscle Meter) device.
5
5
  * To use this device enable: `chrome://flags/#enable-experimental-web-platform-features`.
@@ -1,4 +1,4 @@
1
- import { Device } from "../device.model.js";
1
+ import { Device } from "../device.model";
2
2
  /**
3
3
  * Represents a Weiheng - WH-C06 (or MAT Muscle Meter) device.
4
4
  * To use this device enable: `chrome://flags/#enable-experimental-web-platform-features`.
@@ -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, 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, NotifyCallback, WriteCallback } from "../interfaces/callback.interface";
4
+ import type { DownloadPacket } from "../interfaces/download.interface";
5
+ import type { Commands } from "../interfaces/command.interface";
6
6
  export declare abstract class Device extends BaseModel implements IDevice {
7
7
  /**
8
8
  * Filters to identify the device during Bluetooth scanning.
@@ -26,7 +26,7 @@ export declare abstract class Device extends BaseModel implements IDevice {
26
26
  * @type {BluetoothDevice | undefined}
27
27
  * @public
28
28
  */
29
- bluetooth?: BluetoothDevice | undefined;
29
+ bluetooth?: BluetoothDevice;
30
30
  /**
31
31
  * Object representing the set of commands available for this device.
32
32
  * These commands allow communication with the device to perform various operations such as starting measurements, retrieving data, or calibrating the device.
@@ -275,25 +275,15 @@ export declare abstract class Device extends BaseModel implements IDevice {
275
275
  */
276
276
  protected getAllServiceUUIDs: () => string[];
277
277
  /**
278
- * Attempt to use ES module import rather than require.
279
- * This approach uses an async dynamic import for `webbluetooth`,
280
- * so we can fallback if `navigator.bluetooth` is unavailable.
281
- */
282
- protected getBluetooth(): Promise<Bluetooth | import("webbluetooth").Bluetooth>;
283
- /**
284
- * Retrieves the characteristic from the device's service.
285
- * @param {string} serviceId - The UUID of the service.
286
- * @param {string} characteristicId - The UUID of the characteristic.
287
- * @returns {BluetoothRemoteGATTCharacteristic | undefined} The characteristic, if found.
288
- * @protected
278
+ * Returns the Bluetooth instance available for the current environment.
279
+ * In browsers, it returns the native Web Bluetooth API (i.e. `navigator.bluetooth`).
280
+ * In a Node, Bun, or Deno environment, it dynamically imports the `webbluetooth` package.
281
+ * {@link https://github.com/thegecko/webbluetooth}
289
282
  *
290
- * @example
291
- * const characteristic = device.getCharacteristic('battery', 'level');
292
- * if (characteristic) {
293
- * console.log('Characteristic found');
294
- * }
283
+ * @returns {Promise<Bluetooth>} A promise that resolves to the Bluetooth instance.
284
+ * @throws {Error} If Web Bluetooth is not available in the current environment.
295
285
  */
296
- protected getCharacteristic: (serviceId: string, characteristicId: string) => BluetoothRemoteGATTCharacteristic | undefined;
286
+ protected getBluetooth(): Promise<Bluetooth>;
297
287
  /**
298
288
  * Handles notifications received from a characteristic.
299
289
  * @param {BluetoothRemoteGATTCharacteristic} characteristic - The notification event.
@@ -1,4 +1,4 @@
1
- import { BaseModel } from "./../models/base.model.js";
1
+ import { BaseModel } from "./../models/base.model";
2
2
  export class Device extends BaseModel {
3
3
  /**
4
4
  * Filters to identify the device during Bluetooth scanning.
@@ -208,22 +208,16 @@ export class Device extends BaseModel {
208
208
  * @example
209
209
  * await device.activityCheck(5.0);
210
210
  */
211
- activityCheck = (input) => {
212
- return new Promise((resolve) => {
213
- const startValue = input;
214
- const { threshold, duration } = this.activeConfig;
215
- setTimeout(() => {
216
- // After waiting for `duration`, check if still active (for a real scenario, you might store a last known input)
217
- const activeNow = startValue > threshold;
218
- if (this.isActive !== activeNow) {
219
- this.isActive = activeNow;
220
- if (this.activeCallback) {
221
- this.activeCallback(activeNow);
222
- }
223
- }
224
- resolve();
225
- }, duration);
226
- });
211
+ activityCheck = async (input) => {
212
+ const startValue = input;
213
+ const { threshold, duration } = this.activeConfig;
214
+ // After waiting for `duration`, check if still active.
215
+ await new Promise((resolve) => setTimeout(resolve, duration));
216
+ const activeNow = startValue > threshold;
217
+ if (this.isActive !== activeNow) {
218
+ this.isActive = activeNow;
219
+ this.activeCallback?.(activeNow);
220
+ }
227
221
  };
228
222
  /**
229
223
  * Connects to a Bluetooth device.
@@ -277,7 +271,7 @@ export class Device extends BaseModel {
277
271
  // Remove all notification listeners
278
272
  this.services.forEach((service) => {
279
273
  service.characteristics.forEach((char) => {
280
- // TODO: remove device-specific logic
274
+ // Look for the "rx" characteristic that accepts notifications
281
275
  if (char.characteristic && char.id === "rx") {
282
276
  char.characteristic.stopNotifications();
283
277
  const listener = this.notificationListeners.get(char.uuid);
@@ -437,47 +431,28 @@ export class Device extends BaseModel {
437
431
  return this.services.filter((service) => service?.uuid).map((service) => service.uuid);
438
432
  };
439
433
  /**
440
- * Attempt to use ES module import rather than require.
441
- * This approach uses an async dynamic import for `webbluetooth`,
442
- * so we can fallback if `navigator.bluetooth` is unavailable.
434
+ * Returns the Bluetooth instance available for the current environment.
435
+ * In browsers, it returns the native Web Bluetooth API (i.e. `navigator.bluetooth`).
436
+ * In a Node, Bun, or Deno environment, it dynamically imports the `webbluetooth` package.
437
+ * {@link https://github.com/thegecko/webbluetooth}
438
+ *
439
+ * @returns {Promise<Bluetooth>} A promise that resolves to the Bluetooth instance.
440
+ * @throws {Error} If Web Bluetooth is not available in the current environment.
443
441
  */
444
442
  async getBluetooth() {
445
- // If we're in a browser with real Web Bluetooth available:
446
- if (typeof navigator !== "undefined" && "bluetooth" in navigator) {
443
+ // If running in a browser with native Web Bluetooth support:
444
+ if (typeof navigator !== "undefined" && navigator.bluetooth) {
447
445
  return navigator.bluetooth;
448
446
  }
449
- // Otherwise, we're likely in Node or an environment without `navigator.bluetooth`.
450
- // Use a dynamic import for the ESM version:
451
- const { bluetooth } = await import("webbluetooth");
452
- return bluetooth;
453
- }
454
- /**
455
- * Retrieves the characteristic from the device's service.
456
- * @param {string} serviceId - The UUID of the service.
457
- * @param {string} characteristicId - The UUID of the characteristic.
458
- * @returns {BluetoothRemoteGATTCharacteristic | undefined} The characteristic, if found.
459
- * @protected
460
- *
461
- * @example
462
- * const characteristic = device.getCharacteristic('battery', 'level');
463
- * if (characteristic) {
464
- * console.log('Characteristic found');
465
- * }
466
- */
467
- getCharacteristic = (serviceId, characteristicId) => {
468
- // Find the service with the specified serviceId
469
- const boardService = this.services.find((service) => service.id === serviceId);
470
- if (boardService) {
471
- // If the service is found, find the characteristic with the specified characteristicId
472
- const boardCharacteristic = boardService.characteristics.find((characteristic) => characteristic.id === characteristicId);
473
- if (boardCharacteristic) {
474
- // If the characteristic is found, return it
475
- return boardCharacteristic.characteristic;
476
- }
447
+ const process = await import("node:process");
448
+ // If running in Node, Bun, or Deno environment
449
+ if (typeof process !== "undefined" && process.versions?.node) {
450
+ const { bluetooth } = await import("webbluetooth");
451
+ return bluetooth;
477
452
  }
478
- // Return undefined if the service or characteristic is not found
479
- return undefined;
480
- };
453
+ // If none of the above conditions are met, throw an error.
454
+ throw new Error("Bluetooth not available.");
455
+ }
481
456
  /**
482
457
  * Handles notifications received from a characteristic.
483
458
  * @param {BluetoothRemoteGATTCharacteristic} characteristic - The notification event.
@@ -507,7 +482,7 @@ export class Device extends BaseModel {
507
482
  */
508
483
  isConnected = () => {
509
484
  // Check if the device is defined and available
510
- if (!this?.bluetooth) {
485
+ if (!this.bluetooth) {
511
486
  return false;
512
487
  }
513
488
  // Check if the device is connected
@@ -550,26 +525,34 @@ export class Device extends BaseModel {
550
525
  for (const service of services) {
551
526
  const matchingService = this.services.find((boardService) => boardService.uuid === service.uuid);
552
527
  if (matchingService) {
553
- // Android bug: Introduce a delay before getting characteristics
528
+ // Android bug: Add a small delay before getting characteristics
554
529
  await new Promise((resolve) => setTimeout(resolve, 100));
555
530
  const characteristics = await service.getCharacteristics();
556
531
  for (const characteristic of matchingService.characteristics) {
557
532
  const matchingCharacteristic = characteristics.find((char) => char.uuid === characteristic.uuid);
558
533
  if (matchingCharacteristic) {
559
- const element = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid);
560
- if (element) {
561
- element.characteristic = matchingCharacteristic;
562
- // TODO: remove device-specific logic
563
- if (element.id === "rx") {
534
+ // Find the corresponding characteristic descriptor in the service's characteristics array
535
+ const descriptor = matchingService.characteristics.find((char) => char.uuid === matchingCharacteristic.uuid);
536
+ if (descriptor) {
537
+ // Assign the actual Bluetooth characteristic object to the descriptor so it can be used later
538
+ descriptor.characteristic = matchingCharacteristic;
539
+ // Look for the "rx" characteristic id that accepts notifications
540
+ if (descriptor.id === "rx") {
541
+ // Start receiving notifications for changes on this characteristic
564
542
  matchingCharacteristic.startNotifications();
543
+ // Triggered when the characteristic's value changes
565
544
  const listener = (event) => {
545
+ // Cast the event's target to a BluetoothRemoteGATTCharacteristic to access its properties
566
546
  const target = event.target;
567
547
  if (target && target.value) {
548
+ // Delegate the data to handleNotifications method
568
549
  this.handleNotifications(target);
569
550
  }
570
551
  };
552
+ // Attach the event listener to listen for changes in the characteristic's value
571
553
  matchingCharacteristic.addEventListener("characteristicvaluechanged", listener);
572
- this.notificationListeners.set(element.uuid, listener);
554
+ // Store the listener so it can be referenced (for later removal)
555
+ this.notificationListeners.set(descriptor.uuid, listener);
573
556
  }
574
557
  }
575
558
  }
@@ -611,7 +594,9 @@ export class Device extends BaseModel {
611
594
  return undefined;
612
595
  }
613
596
  // Get the characteristic from the service
614
- const characteristic = this.getCharacteristic(serviceId, characteristicId);
597
+ const characteristic = this.services
598
+ .find((service) => service.id === serviceId)
599
+ ?.characteristics.find((char) => char.id === characteristicId)?.characteristic;
615
600
  if (!characteristic) {
616
601
  throw new Error(`Characteristic "${characteristicId}" not found in service "${serviceId}"`);
617
602
  }
@@ -723,7 +708,9 @@ export class Device extends BaseModel {
723
708
  return Promise.resolve();
724
709
  }
725
710
  // Get the characteristic from the service
726
- const characteristic = this.getCharacteristic(serviceId, characteristicId);
711
+ const characteristic = this.services
712
+ .find((service) => service.id === serviceId)
713
+ ?.characteristics.find((char) => char.id === characteristicId)?.characteristic;
727
714
  if (!characteristic) {
728
715
  throw new Error(`Characteristic "${characteristicId}" not found in service "${serviceId}"`);
729
716
  }
@@ -1,8 +1,8 @@
1
- export { Climbro } from "./device/climbro.model.js";
2
- export { Entralpi } from "./device/entralpi.model.js";
3
- export { ForceBoard } from "./device/forceboard.model.js";
4
- export { KilterBoard } from "./device/kilterboard.model.js";
5
- export { Motherboard } from "./device/motherboard.model.js";
6
- export { mySmartBoard } from "./device/mysmartboard.model.js";
7
- export { Progressor } from "./device/progressor.model.js";
8
- export { WHC06 } from "./device/wh-c06.model.js";
1
+ export { Climbro } from "./device/climbro.model";
2
+ export { Entralpi } from "./device/entralpi.model";
3
+ export { ForceBoard } from "./device/forceboard.model";
4
+ export { KilterBoard } from "./device/kilterboard.model";
5
+ export { Motherboard } from "./device/motherboard.model";
6
+ export { mySmartBoard } from "./device/mysmartboard.model";
7
+ export { Progressor } from "./device/progressor.model";
8
+ export { WHC06 } from "./device/wh-c06.model";
@@ -1,8 +1,8 @@
1
- export { Climbro } from "./device/climbro.model.js";
2
- export { Entralpi } from "./device/entralpi.model.js";
3
- export { ForceBoard } from "./device/forceboard.model.js";
4
- export { KilterBoard } from "./device/kilterboard.model.js";
5
- export { Motherboard } from "./device/motherboard.model.js";
6
- export { mySmartBoard } from "./device/mysmartboard.model.js";
7
- export { Progressor } from "./device/progressor.model.js";
8
- export { WHC06 } from "./device/wh-c06.model.js";
1
+ export { Climbro } from "./device/climbro.model";
2
+ export { Entralpi } from "./device/entralpi.model";
3
+ export { ForceBoard } from "./device/forceboard.model";
4
+ export { KilterBoard } from "./device/kilterboard.model";
5
+ export { Motherboard } from "./device/motherboard.model";
6
+ export { mySmartBoard } from "./device/mysmartboard.model";
7
+ export { Progressor } from "./device/progressor.model";
8
+ export { WHC06 } from "./device/wh-c06.model";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hangtime/grip-connect",
3
- "version": "0.6.1",
4
- "description": "Griptonite Motherboard, Tindeq Progressor, PitchSix Force Board, WHC-06, Entralpi, Climbro, mySmartBoard: Web + Node Bluetooth API Force-Sensing strength analysis for climbers",
3
+ "version": "0.6.2",
4
+ "description": "Griptonite Motherboard, Tindeq Progressor, PitchSix Force Board, WHC-06, Entralpi, Climbro, mySmartBoard: Bluetooth API Force-Sensing strength analysis for climbers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "homepage": "https://stevie-ray.github.io/hangtime-grip-connect/",
37
37
  "dependencies": {
38
- "webbluetooth": "^3.2.1"
38
+ "webbluetooth": "^3.3.2"
39
39
  }
40
40
  }
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ export type {
7
7
  ImySmartBoard,
8
8
  IProgressor,
9
9
  IWHC06,
10
- } from "./interfaces/index.js"
10
+ } from "./interfaces/index"
11
11
 
12
12
  export {
13
13
  Climbro,
@@ -18,4 +18,4 @@ export {
18
18
  mySmartBoard,
19
19
  Progressor,
20
20
  WHC06,
21
- } from "./models/index.js"
21
+ } from "./models/index"
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
  /**
3
3
  * Interface representing the Climbro device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
 
3
3
  /**
4
4
  * Interface representing the Entralpi device, extending the base Device interface.
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
 
3
3
  /**
4
4
  * Interface representing the PitchSix ForceBoard device, extending the base Device interface.
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
  /**
3
3
  * Interface representing the KilterBoard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
 
3
3
  /**
4
4
  * Interface representing the Griptonite Motherboard device, extending the base Device interface.
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
  /**
3
3
  * Interface representing the Smartboard Climbing mySmartBoard device, extending the base Device interface.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
 
3
3
  /**
4
4
  * Interface representing the Tindeq Progressor device, extending the base Device interface.
@@ -1,4 +1,4 @@
1
- import type { IDevice } from "../device.interface.js"
1
+ import type { IDevice } from "../device.interface"
2
2
  /**
3
3
  * Interface representing the Weiheng WH-C06 device, extending the base Device interface.
4
4
  */
@@ -1,6 +1,6 @@
1
- import type { IBase } from "./base.interface.js"
2
- import type { massObject } from "./callback.interface.js"
3
- import type { Commands } from "./command.interface.js"
1
+ import type { IBase } from "./base.interface"
2
+ import type { massObject } from "./callback.interface"
3
+ import type { Commands } from "./command.interface"
4
4
 
5
5
  /**
6
6
  * Represents a characteristic of a Bluetooth service.
@@ -1,15 +1,15 @@
1
- export type { IClimbro } from "./device/climbro.interface.js"
1
+ export type { IClimbro } from "./device/climbro.interface"
2
2
 
3
- export type { IEntralpi } from "./device/entralpi.interface.js"
3
+ export type { IEntralpi } from "./device/entralpi.interface"
4
4
 
5
- export type { IForceBoard } from "./device/forceboard.interface.js"
5
+ export type { IForceBoard } from "./device/forceboard.interface"
6
6
 
7
- export type { IKilterBoard } from "./device/kilterboard.interface.js"
7
+ export type { IKilterBoard } from "./device/kilterboard.interface"
8
8
 
9
- export type { IMotherboard } from "./device/motherboard.interface.js"
9
+ export type { IMotherboard } from "./device/motherboard.interface"
10
10
 
11
- export type { ImySmartBoard } from "./device/mysmartboard.interface.js"
11
+ export type { ImySmartBoard } from "./device/mysmartboard.interface"
12
12
 
13
- export type { IProgressor } from "./device/progressor.interface.js"
13
+ export type { IProgressor } from "./device/progressor.interface"
14
14
 
15
- export type { IWHC06 } from "./device/wh-c06.interface.js"
15
+ export type { IWHC06 } from "./device/wh-c06.interface"
@@ -1,4 +1,4 @@
1
- import type { IBase } from "../interfaces/base.interface.js"
1
+ import type { IBase } from "../interfaces/base.interface"
2
2
 
3
3
  export abstract class BaseModel {
4
4
  id?: string
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IClimbro } from "../../interfaces/device/climbro.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IClimbro } from "../../interfaces/device/climbro.interface"
3
3
 
4
4
  /**
5
5
  * Represents a Climbro device.
@@ -1,5 +1,5 @@
1
- import { Device } from "../device.model.js"
2
- import type { IEntralpi } from "../../interfaces/device/entralpi.interface.js"
1
+ import { Device } from "../device.model"
2
+ import type { IEntralpi } from "../../interfaces/device/entralpi.interface"
3
3
 
4
4
  /**
5
5
  * Represents a Entralpi device.
@@ -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.
@@ -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"