UNPKG

ant-plus-next

Version:

A modern Node.js module for working with ANT+ USB sticks and sensors.

1 lines 307 kB
{"version":3,"file":"index.cjs","sources":["../src/types/constants.ts","../src/utils/messages.ts","../src/core/driver/nodeUSBDriver.ts","../src/core/driver/usbDriverUtils.ts","../src/core/driver/webUSBDriver.ts","../src/utils/customPolyfills.ts","../src/sensors/baseSensor.ts","../src/sensors/heartRate/heartRateUtils.ts","../src/sensors/heartRate/heartRateSensorState.ts","../src/sensors/heartRate/heartRateScanState.ts","../src/sensors/antPlusBaseSensor.ts","../src/sensors/antPlusSensor.ts","../src/sensors/heartRate/heartRateSensor.ts","../src/sensors/antPlusScanner.ts","../src/sensors/strideSpeedDistance/strideSpeedDistanceUtils.ts","../src/sensors/strideSpeedDistance/strideSpeedDistanceSensorState.ts","../src/sensors/strideSpeedDistance/strideSpeedDistanceScanState.ts","../src/sensors/strideSpeedDistance/strideSpeedDistanceSensor.ts","../src/sensors/speedCadence/speedCadenceUtils.ts","../src/sensors/speedCadence/speedCadenceSensorState.ts","../src/sensors/speedCadence/speedCadenceScanState.ts","../src/sensors/speedCadence/speedCadenceSensor.ts","../src/sensors/speed/speedUtils.ts","../src/sensors/speed/speedSensorState.ts","../src/sensors/speed/speedScanState.ts","../src/sensors/speed/speedSensor.ts","../src/sensors/cadence/cadenceUtils.ts","../src/sensors/cadence/cadenceSensorState.ts","../src/sensors/cadence/cadenceScanState.ts","../src/sensors/cadence/cadenceSensor.ts","../src/sensors/bicyclePower/bicyclePowerUtils.ts","../src/sensors/bicyclePower/bicyclePowerSensorState.ts","../src/sensors/bicyclePower/bicyclePowerScanState.ts","../src/sensors/bicyclePower/bicyclePowerSensor.ts","../src/sensors/fitnessEquipment/fitnessEquipmentUtils.ts","../src/sensors/fitnessEquipment/fitnessEquipmentSensorState.ts","../src/sensors/fitnessEquipment/fitnessEquipmentScanState.ts","../src/sensors/fitnessEquipment/fitnessEquipmentSensor.ts","../src/sensors/muscleOxygen/muscleOxygenUtils.ts","../src/sensors/muscleOxygen/muscleOxygenSensorState.ts","../src/sensors/muscleOxygen/muscleOxygenScanState.ts","../src/sensors/muscleOxygen/muscleOxygenSensor.ts","../src/sensors/environment/environmentUtils.ts","../src/sensors/environment/environmentSensorState.ts","../src/sensors/environment/environmentScanState.ts","../src/sensors/environment/environmentSensor.ts","../src/sensors/bicyclePower/bicyclePowerScanner.ts","../src/sensors/cadence/cadenceScanner.ts","../src/sensors/environment/environmentScanner.ts","../src/sensors/fitnessEquipment/fitnessEquipmentScanner.ts","../src/core/nodeUsbSticks.ts","../src/sensors/heartRate/heartRateScanner.ts","../src/sensors/muscleOxygen/muscleOxygenScanner.ts","../src/sensors/speedCadence/speedCadenceScanner.ts","../src/sensors/speed/speedScanner.ts","../src/sensors/strideSpeedDistance/strideSpeedDistanceScanner.ts","../src/core/webUsbStick.ts"],"sourcesContent":["/**\n * A collection of constants used in the ANT+ protocol for wireless communication.\n * These constants define various message types, configuration parameters, events,\n * and capabilities used to control and manage channels, transmit data, and handle\n * protocol-specific operations.\n *\n * @class Constants\n * @see {@link https://www.thisisant.com/developer/resources/downloads#documents} for the ANT+ protocol documentation.\n */\nexport class Constants {\n // Message Types\n /**\n * Represents a generic Radio Frequency (RF) message type used in the ANT+ protocol.\n * @type {number}\n */\n static readonly MESSAGE_RF: number = 0x01;\n\n /**\n * Represents a synchronization message used to align data transmission.\n * @type {number}\n */\n static readonly MESSAGE_TX_SYNC: number = 0xa4;\n\n /**\n * Default network number used by ANT+ devices.\n * @type {number}\n */\n static readonly DEFAULT_NETWORK_NUMBER: number = 0x00;\n\n // Configuration Messages\n /**\n * Unassigns a channel, making it available for reassignment or closing.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_UNASSIGN: number = 0x41;\n\n /**\n * Assigns a channel with a specific type and network number.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_ASSIGN: number = 0x42;\n\n /**\n * Sets the unique identifier for a channel.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_ID: number = 0x51;\n\n /**\n * Configures the message period for a channel, defining the frequency of data messages.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_PERIOD: number = 0x43;\n\n /**\n * Sets the timeout period for channel searching operations.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_SEARCH_TIMEOUT: number = 0x44;\n\n /**\n * Configures the frequency of the channel in the RF spectrum.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_FREQUENCY: number = 0x45;\n\n /**\n * Sets the transmission power level for a specific channel.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_TX_POWER: number = 0x60;\n\n /**\n * Sets the network key for secure communication between ANT+ devices.\n * @type {number}\n */\n static readonly MESSAGE_NETWORK_KEY: number = 0x46;\n\n /**\n * Defines the transmit power for the device.\n * @type {number}\n */\n static readonly MESSAGE_TX_POWER: number = 0x47;\n\n /**\n * Enables proximity search for devices within a specified range.\n * @type {number}\n */\n static readonly MESSAGE_PROXIMITY_SEARCH: number = 0x71;\n\n /**\n * Enables extended receive mode for handling additional data.\n * @type {number}\n */\n static readonly MESSAGE_ENABLE_RX_EXT: number = 0x66;\n\n /**\n * Configures the library settings for ANT+ communication.\n * @type {number}\n */\n static readonly MESSAGE_LIB_CONFIG: number = 0x6e;\n\n /**\n * Opens a channel for RX scan mode, allowing the device to search for broadcasts.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_OPEN_RX_SCAN: number = 0x5b;\n\n // Notifications\n /**\n * Indicates a startup event, typically sent after a device reset or initialization.\n * @type {number}\n */\n static readonly MESSAGE_STARTUP: number = 0x6f;\n\n // Control Messages\n /**\n * Resets the ANT+ device system.\n * @type {number}\n */\n static readonly MESSAGE_SYSTEM_RESET: number = 0x4a;\n\n /**\n * Opens a communication channel for data transmission.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_OPEN: number = 0x4b;\n\n /**\n * Closes a previously opened communication channel.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_CLOSE: number = 0x4c;\n\n /**\n * Requests a specific operation or status update for a channel.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_REQUEST: number = 0x4d;\n\n // Data Messages\n /**\n * Transmits broadcast data over an open channel.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_BROADCAST_DATA: number = 0x4e;\n\n /**\n * Sends acknowledged data that requires a response confirmation.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_ACKNOWLEDGED_DATA: number = 0x4f;\n\n /**\n * Sends burst data packets over a channel, useful for high-throughput scenarios.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_BURST_DATA: number = 0x50;\n\n // Channel Event Messages\n /**\n * Represents various events occurring on a channel, such as RX or TX completion.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_EVENT: number = 0x40;\n\n // Requested Response Messages\n /**\n * Queries the status of a channel, returning details like state or assigned ID.\n * @type {number}\n */\n static readonly MESSAGE_CHANNEL_STATUS: number = 0x52;\n\n /**\n * Retrieves the version of the ANT+ device software or protocol.\n * @type {number}\n */\n static readonly MESSAGE_VERSION: number = 0x3e;\n\n /**\n * Retrieves the capabilities of the ANT+ device, such as supported features.\n * @type {number}\n */\n static readonly MESSAGE_CAPABILITIES: number = 0x54;\n\n /**\n * Retrieves the unique serial number of the ANT+ device.\n * @type {number}\n */\n static readonly MESSAGE_SERIAL_NUMBER: number = 0x61;\n\n // Message Parameters\n /**\n * Channel type for two-way data reception.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_TWOWAY_RECEIVE: number = 0x00;\n\n /**\n * Channel type for two-way data transmission.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_TWOWAY_TRANSMIT: number = 0x10;\n\n /**\n * Channel type for shared data reception.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_SHARED_RECEIVE: number = 0x20;\n\n /**\n * Channel type for shared data transmission.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_SHARED_TRANSMIT: number = 0x30;\n\n /**\n * Channel type for one-way data reception.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_ONEWAY_RECEIVE: number = 0x40;\n\n /**\n * Channel type for one-way data transmission.\n * @type {number}\n */\n static readonly CHANNEL_TYPE_ONEWAY_TRANSMIT: number = 0x50;\n\n /**\n * Radio transmit power level: -20 dB.\n * @type {number}\n */\n static readonly RADIO_TX_POWER_MINUS20DB: number = 0x00;\n\n /**\n * Radio transmit power level: -10 dB.\n * @type {number}\n */\n static readonly RADIO_TX_POWER_MINUS10DB: number = 0x01;\n\n /**\n * Radio transmit power level: 0 dB.\n * @type {number}\n */\n static readonly RADIO_TX_POWER_0DB: number = 0x02;\n\n /**\n * Radio transmit power level: +4 dB.\n * @type {number}\n */\n static readonly RADIO_TX_POWER_PLUS4DB: number = 0x03;\n\n // Event Codes\n /**\n * No error occurred in the response.\n * @type {number}\n */\n static readonly RESPONSE_NO_ERROR: number = 0x00;\n\n /**\n * RX search timed out.\n * @type {number}\n */\n static readonly EVENT_RX_SEARCH_TIMEOUT: number = 0x01;\n\n /**\n * RX failed.\n * @type {number}\n */\n static readonly EVENT_RX_FAIL: number = 0x02;\n\n /**\n * TX completed successfully.\n * @type {number}\n */\n static readonly EVENT_TX: number = 0x03;\n\n /**\n * Transfer RX failed.\n * @type {number}\n */\n static readonly EVENT_TRANSFER_RX_FAILED: number = 0x04;\n\n /**\n * Transfer TX completed successfully.\n * @type {number}\n */\n static readonly EVENT_TRANSFER_TX_COMPLETED: number = 0x05;\n\n /**\n * Transfer TX failed.\n * @type {number}\n */\n static readonly EVENT_TRANSFER_TX_FAILED: number = 0x06;\n\n /**\n * Channel was closed.\n * @type {number}\n */\n static readonly EVENT_CHANNEL_CLOSED: number = 0x07;\n\n /**\n * RX failed; channel will go to search.\n * @type {number}\n */\n static readonly EVENT_RX_FAIL_GO_TO_SEARCH: number = 0x08;\n\n /**\n * Channel collision detected.\n * @type {number}\n */\n static readonly EVENT_CHANNEL_COLLISION: number = 0x09;\n\n /**\n * Transfer TX started.\n * @type {number}\n */\n static readonly EVENT_TRANSFER_TX_START: number = 0x0a;\n\n // Error and State Codes\n /**\n * Channel is in the wrong state for the requested operation.\n * @type {number}\n */\n static readonly CHANNEL_IN_WRONG_STATE: number = 0x15;\n\n /**\n * Channel is not opened.\n * @type {number}\n */\n static readonly CHANNEL_NOT_OPENED: number = 0x16;\n\n /**\n * Channel ID is not set.\n * @type {number}\n */\n static readonly CHANNEL_ID_NOT_SET: number = 0x18;\n\n /**\n * Command to close all channels.\n * @type {number}\n */\n static readonly CLOSE_ALL_CHANNELS: number = 0x19;\n\n /**\n * Transfer is currently in progress.\n * @type {number}\n */\n static readonly TRANSFER_IN_PROGRESS: number = 0x1f;\n\n /**\n * Transfer sequence number error.\n * @type {number}\n */\n static readonly TRANSFER_SEQUENCE_NUMBER_ERROR: number = 0x20;\n\n /**\n * Transfer is in an error state.\n * @type {number}\n */\n static readonly TRANSFER_IN_ERROR: number = 0x21;\n\n /**\n * Message size exceeds allowed limit.\n * @type {number}\n */\n static readonly MESSAGE_SIZE_EXCEEDS_LIMIT: number = 0x27;\n\n /**\n * Invalid message received.\n * @type {number}\n */\n static readonly INVALID_MESSAGE: number = 0x28;\n\n /**\n * Invalid network number provided.\n * @type {number}\n */\n static readonly INVALID_NETWORK_NUMBER: number = 0x29;\n\n /**\n * Invalid list ID provided.\n * @type {number}\n */\n static readonly INVALID_LIST_ID: number = 0x30;\n\n /**\n * Invalid scan TX channel specified.\n * @type {number}\n */\n static readonly INVALID_SCAN_TX_CHANNEL: number = 0x31;\n\n /**\n * Invalid parameter provided for a message or operation.\n * @type {number}\n */\n static readonly INVALID_PARAMETER_PROVIDED: number = 0x33;\n\n /**\n * Event queue overflow occurred.\n * @type {number}\n */\n static readonly EVENT_QUEUE_OVERFLOW: number = 0x35;\n\n /**\n * USB string write operation failed.\n * @type {number}\n */\n static readonly USB_STRING_WRITE_FAIL: number = 0x70;\n\n // Channel States\n /**\n * Channel is unassigned.\n * @type {number}\n */\n static readonly CHANNEL_STATE_UNASSIGNED: number = 0x00;\n\n /**\n * Channel is assigned.\n * @type {number}\n */\n static readonly CHANNEL_STATE_ASSIGNED: number = 0x01;\n\n /**\n * Channel is searching for another device.\n * @type {number}\n */\n static readonly CHANNEL_STATE_SEARCHING: number = 0x02;\n\n /**\n * Channel is tracking another device.\n * @type {number}\n */\n static readonly CHANNEL_STATE_TRACKING: number = 0x03;\n\n // Capability Flags\n /**\n * Device has no receive channels.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_RECEIVE_CHANNELS: number = 0x01;\n\n /**\n * Device has no transmit channels.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_TRANSMIT_CHANNELS: number = 0x02;\n\n /**\n * Device has no receive message capability.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_RECEIVE_MESSAGES: number = 0x04;\n\n /**\n * Device has no transmit message capability.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_TRANSMIT_MESSAGES: number = 0x08;\n\n /**\n * Device has no acknowledged message capability.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_ACKNOWLEDGED_MESSAGES: number = 0x10;\n\n /**\n * Device has no burst message capability.\n * @type {number}\n */\n static readonly CAPABILITIES_NO_BURST_MESSAGES: number = 0x20;\n\n /**\n * Device supports network communication.\n * @type {number}\n */\n static readonly CAPABILITIES_NETWORK_ENABLED: number = 0x02;\n\n /**\n * Device serial number feature enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_SERIAL_NUMBER_ENABLED: number = 0x08;\n\n /**\n * Per-channel transmit power adjustment enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_PER_CHANNEL_TX_POWER_ENABLED: number = 0x10;\n\n /**\n * Low priority search capability enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_LOW_PRIORITY_SEARCH_ENABLED: number = 0x20;\n\n /**\n * Script feature enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_SCRIPT_ENABLED: number = 0x40;\n\n /**\n * Search list capability enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_SEARCH_LIST_ENABLED: number = 0x80;\n\n /**\n * LED feature enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_LED_ENABLED: number = 0x01;\n\n /**\n * Extended messaging enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_EXT_MESSAGE_ENABLED: number = 0x02;\n\n /**\n * Scan mode enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_SCAN_MODE_ENABLED: number = 0x04;\n\n /**\n * Proximity search capability enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_PROX_SEARCH_ENABLED: number = 0x10;\n\n /**\n * Extended channel assignment enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_EXT_ASSIGN_ENABLED: number = 0x20;\n\n /**\n * File share (ANT-FS) feature enabled.\n * @type {number}\n */\n static readonly CAPABILITIES_FS_ANTFS_ENABLED: number = 0x40;\n\n // Miscellaneous\n /**\n * Special value indicating that the timeout for an operation is set to never expire.\n * @type {number}\n */\n static readonly TIMEOUT_NEVER: number = 0xff;\n}\n","import { Constants } from \"../types/constants.js\";\n\nexport class Messages {\n static BUFFER_INDEX_MSG_LEN: number = 1;\n static BUFFER_INDEX_MSG_TYPE: number = 2;\n static BUFFER_INDEX_CHANNEL_NUM: number = 3;\n static BUFFER_INDEX_MSG_DATA: number = 4;\n static BUFFER_INDEX_EXT_MSG_BEGIN: number = 12;\n\n static resetSystem(): Uint8Array {\n const payload: number[] = [];\n payload.push(0x00);\n return this.buildMessage(payload, Constants.MESSAGE_SYSTEM_RESET);\n }\n\n static requestMessage(channel: number, messageId: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload.push(messageId);\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_REQUEST);\n }\n\n static setNetworkKey(): Uint8Array {\n const payload: number[] = [];\n payload.push(Constants.DEFAULT_NETWORK_NUMBER);\n payload.push(0xb9);\n payload.push(0xa5);\n payload.push(0x21);\n payload.push(0xfb);\n payload.push(0xbd);\n payload.push(0x72);\n payload.push(0xc3);\n payload.push(0x45);\n return this.buildMessage(payload, Constants.MESSAGE_NETWORK_KEY);\n }\n\n static assignChannel(channel: number, type = \"receive\"): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n if (type === \"receive\") {\n payload.push(Constants.CHANNEL_TYPE_TWOWAY_RECEIVE);\n } else if (type === \"receive_only\") {\n payload.push(Constants.CHANNEL_TYPE_ONEWAY_RECEIVE);\n } else if (type === \"receive_shared\") {\n payload.push(Constants.CHANNEL_TYPE_SHARED_RECEIVE);\n } else if (type === \"transmit\") {\n payload.push(Constants.CHANNEL_TYPE_TWOWAY_TRANSMIT);\n } else if (type === \"transmit_only\") {\n payload.push(Constants.CHANNEL_TYPE_ONEWAY_TRANSMIT);\n } else if (type === \"transmit_shared\") {\n payload.push(Constants.CHANNEL_TYPE_SHARED_TRANSMIT);\n } else {\n throw new Error(\"type not allowed\");\n }\n payload.push(Constants.DEFAULT_NETWORK_NUMBER);\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_ASSIGN);\n }\n\n static setDevice(channel: number, deviceId: number, deviceType: number, transmissionType: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload = payload.concat(this.intToLEHexArray(deviceId, 2));\n payload = payload.concat(this.intToLEHexArray(deviceType));\n payload = payload.concat(this.intToLEHexArray(transmissionType));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_ID);\n }\n\n static searchChannel(channel: number, timeout: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload = payload.concat(this.intToLEHexArray(timeout));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_SEARCH_TIMEOUT);\n }\n\n static setPeriod(channel: number, period: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload = payload.concat(this.intToLEHexArray(period));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_PERIOD);\n }\n\n static setFrequency(channel: number, frequency: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload = payload.concat(this.intToLEHexArray(frequency));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_FREQUENCY);\n }\n\n static setRxExt(): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(0));\n payload = payload.concat(this.intToLEHexArray(1));\n return this.buildMessage(payload, Constants.MESSAGE_ENABLE_RX_EXT);\n }\n\n static libConfig(channel: number, how: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n payload = payload.concat(this.intToLEHexArray(how));\n return this.buildMessage(payload, Constants.MESSAGE_LIB_CONFIG);\n }\n\n static openRxScan(): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(0));\n payload = payload.concat(this.intToLEHexArray(1));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_OPEN_RX_SCAN);\n }\n\n static openChannel(channel: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_OPEN);\n }\n\n static closeChannel(channel: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_CLOSE);\n }\n\n static unassignChannel(channel: number): Uint8Array {\n let payload: number[] = [];\n payload = payload.concat(this.intToLEHexArray(channel));\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_UNASSIGN);\n }\n\n static acknowledgedData(channel: number, payload: number[]): Uint8Array {\n payload = this.intToLEHexArray(channel).concat(payload);\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_ACKNOWLEDGED_DATA);\n }\n\n static broadcastData(channel: number, payload: number[]): Uint8Array {\n payload = this.intToLEHexArray(channel).concat(payload);\n return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_BROADCAST_DATA);\n }\n\n static buildMessage(payload: number[] = [], messageId = 0x00): Uint8Array {\n const message: number[] = [];\n message.push(Constants.MESSAGE_TX_SYNC);\n message.push(payload.length);\n message.push(messageId);\n payload.forEach((byte) => {\n message.push(byte);\n });\n message.push(this.getChecksum(message));\n\n return new Uint8Array(message);\n }\n\n static intToLEHexArray(int: number, numBytes = 1): number[] {\n numBytes = numBytes || 1;\n const a: number[] = [];\n const hexString = this.decimalToHex(int, numBytes * 2);\n for (let i = hexString.length - 2; i >= 0; i -= 2) {\n a.push(parseInt(hexString.substr(i, 2), 16));\n }\n return a;\n }\n\n static decimalToHex(d: number, numDigits: number): string {\n let hex = Number(d).toString(16);\n numDigits = numDigits || 2;\n while (hex.length < numDigits) {\n hex = \"0\" + hex;\n }\n\n return hex;\n }\n\n static getChecksum(message: number[]): number {\n let checksum = 0;\n message.forEach((byte) => {\n checksum = (checksum ^ byte) % 0xff;\n });\n\n return checksum;\n }\n}\n","import { BaseSensor } from \"../../sensors/baseSensor\";\nimport { Constants } from \"../../types/constants\";\nimport { DebugOptions } from \"../../types/debugOptions\";\nimport { USBDriverBase } from \"../../types/usbDriverBase\";\nimport { Messages } from \"../../utils/messages\";\nimport EventEmitter from \"events\";\nimport usb, { LibUSBException } from \"usb\";\n\n/**\n * NodeUSBDriver class handles the connection and communication with USB devices using the node-usb library.\n * It manages device setup, communication, sensor attachment, and data processing.\n *\n * This class extends EventEmitter to handle events and implements the USBDriverBase interface.\n *\n * @extends EventEmitter\n * @implements USBDriverBase\n */\nexport class NodeUSBDriver extends EventEmitter implements USBDriverBase {\n /**\n * List of USB devices currently in use.\n * Tracks all connected devices used by this driver.\n *\n * @type {usb.Device[]}\n * @private\n */\n private static deviceInUse: usb.Device[] = [];\n\n /**\n * The USB device instance.\n * Holds a reference to the connected USB device, if any.\n *\n * @type {usb.Device|undefined}\n * @private\n */\n private device: usb.Device | undefined;\n\n /**\n * The USB interface of the device.\n * Used to interact with the endpoints of the connected USB device.\n *\n * @type {usb.Interface|undefined}\n * @private\n */\n private iface: usb.Interface | undefined;\n\n /**\n * Indicates if the kernel driver was detached.\n * Marks whether the kernel driver was detached during setup for re-attachment upon disconnection.\n *\n * @type {boolean}\n * @private\n */\n private detachedKernelDriver = false;\n\n /**\n * The input endpoint for reading data.\n * Used for receiving data from the USB device.\n *\n * @type {(usb.InEndpoint & EventEmitter)|undefined}\n * @private\n */\n private inEndpoint: (usb.InEndpoint & EventEmitter) | undefined;\n\n /**\n * The output endpoint for sending data.\n * Used for sending data to the USB device.\n *\n * @type {(usb.OutEndpoint & EventEmitter)|undefined}\n * @private\n */\n private outEndpoint: (usb.OutEndpoint & EventEmitter) | undefined;\n\n /**\n * Stores leftover data from previous reads.\n * Used to buffer partial data when reading from the USB device.\n *\n * @type {Uint8Array|undefined}\n * @private\n */\n private leftover: Uint8Array | undefined;\n\n /**\n * The number of channels currently used.\n * Tracks how many channels are actively being used.\n *\n * @type {number}\n */\n usedChannels: number = 0;\n\n /**\n * List of attached sensors.\n * Holds the list of sensors currently connected to the USB driver.\n *\n * @type {BaseSensor[]}\n * @private\n */\n private attachedSensors: BaseSensor[] = [];\n\n /**\n * The maximum number of channels available for communication.\n * Defines the total number of channels the device can handle.\n *\n * @type {number}\n */\n maxChannels: number = 0;\n\n /**\n * Indicates if the device can scan for channels.\n * Represents whether the USB device has scanning capabilities.\n *\n * @type {boolean}\n */\n _canScan: boolean = false;\n\n /**\n * Defines whether to throw LibUSB exceptions when errors occur during USB communication.\n * Default value is set to `false`.\n *\n * @type {boolean}\n */\n throwLibUSBException: boolean = false;\n\n /**\n * Creates an instance of NodeUSBDriver.\n * Initializes the driver with vendor ID, product ID, and optional debug options.\n *\n * @param {number} idVendor - The vendor ID of the USB device.\n * @param {number} idProduct - The product ID of the USB device.\n * @param {DebugOptions} [debugOptions={}] - Optional debug options for USB operations.\n * Includes usbDebugLevel and throwLibUSBException.\n *\n * @example\n * const driver = new NodeUSBDriver(0x1234, 0x5678, { usbDebugLevel: 2, throwLibUSBException: true });\n */\n constructor(\n private idVendor: number,\n private idProduct: number,\n debugOptions: DebugOptions = {}\n ) {\n super();\n this.setMaxListeners(50); // Set maximum number of listeners to 50\n usb.usb.setDebugLevel(debugOptions.usbDebugLevel || 0); // Set USB debug level\n this.throwLibUSBException = debugOptions.throwLibUSBException || false; // Set exception throwing option\n }\n\n /**\n * Checks if a new sensor can be attached to the driver.\n * It verifies whether the current number of used channels is less than the maximum available channels.\n *\n * @returns {Promise<boolean>} Resolves with true if a new sensor can be attached, otherwise false.\n *\n * @example\n * const canAttach = await this.stick.canAttach();\n * if (canAttach) {\n * console.log(\"A new sensor can be attached.\");\n * } else {\n * console.log(\"Cannot attach sensor: Maximum number of channels reached.\");\n * }\n */\n async canAttach(): Promise<boolean> {\n return Promise.resolve(this.usedChannels < this.maxChannels);\n }\n\n /**\n * Checks if the device can scan for channels.\n *\n * @returns {Promise<boolean>} Resolves with true if the device can scan, otherwise false.\n */\n async canScan(): Promise<boolean> {\n return Promise.resolve(this._canScan);\n }\n\n /**\n * Opens a connection to the USB device and sets up endpoints for communication.\n *\n * @returns {Promise<boolean>} Resolves with true if the device is successfully opened, otherwise false.\n * @example\n * const driver = new NodeUSBDriver(1234, 5678);\n * driver.open().then((result) => {\n * if (result) {\n * console.log(\"Device successfully opened\");\n * } else {\n * console.error(\"Failed to open device\");\n * }\n * });\n */\n async open(): Promise<boolean> {\n const devices = this.getDevices();\n\n while (devices.length) {\n try {\n const device = devices.shift();\n if (!device) {\n continue;\n }\n\n this.device = device;\n this.device.open();\n this.iface = this.device.interfaces![0];\n\n try {\n if (this.iface && this.iface.isKernelDriverActive()) {\n this.detachedKernelDriver = true;\n this.iface.detachKernelDriver();\n }\n } catch {\n // Ignore kernel driver errors\n }\n\n this.iface.claim();\n break;\n } catch (error) {\n if (error && this.throwLibUSBException) {\n const errno = (error as LibUSBException).errno;\n switch (errno) {\n case usb.usb.LIBUSB_ERROR_ACCESS:\n throw new Error(\"LIBUSB_ERROR_ACCESS: Access denied (insufficient permissions)\");\n case usb.usb.LIBUSB_ERROR_NO_DEVICE:\n throw new Error(\"LIBUSB_ERROR_NO_DEVICE: Device has been disconnected\");\n case usb.usb.LIBUSB_ERROR_BUSY:\n throw new Error(\"LIBUSB_ERROR_BUSY: Resource busy\");\n default:\n console.error(\"Unknown LIBUSB error:\", error);\n break;\n }\n } else {\n // Ignore errors and try with the next device\n }\n\n if (this.device) {\n this.device.close();\n }\n this.device = undefined;\n this.iface = undefined;\n }\n }\n\n if (!this.device) {\n return Promise.resolve(false);\n }\n\n NodeUSBDriver.deviceInUse.push(this.device);\n\n if (!this.iface) {\n throw new Error(\"Interface not initialized.\");\n }\n\n this.inEndpoint = this.iface.endpoints[0] as usb.InEndpoint;\n this.inEndpoint.on(\"data\", (data: Uint8Array) => {\n this.onData(data).catch((error) => {\n console.error(error);\n });\n });\n\n this.inEndpoint.on(\"error\", (err: Error) => {\n console.error(\"ERROR RECV: \", err);\n });\n\n this.inEndpoint.on(\"end\", () => {\n //console.info(\"STOP RECV\");\n });\n\n this.inEndpoint.startPoll();\n\n this.outEndpoint = this.iface.endpoints[1] as usb.OutEndpoint;\n\n await this.reset();\n\n return Promise.resolve(true);\n }\n\n /**\n * Closes the connection to the USB device and releases the interface.\n *\n * @returns {Promise<void>} Resolves when the device is closed.\n * @example\n * const driver = new NodeUSBDriver(1234, 5678);\n * driver.open().then(() => {\n * driver.close().then(() => console.log(\"Device closed\"));\n * });\n */\n async close(): Promise<void> {\n await this.detachAll();\n\n if (this.inEndpoint) {\n this.inEndpoint.stopPoll(() => {\n if (this.iface) {\n this.iface.release(true, () => {\n if (this.detachedKernelDriver) {\n this.detachedKernelDriver = false;\n try {\n this.iface?.attachKernelDriver();\n } catch {\n // Ignore kernel driver errors\n }\n }\n this.iface = undefined;\n if (this.device) {\n this.device.reset(() => {\n this.device?.close();\n this.emit(\"shutdown\");\n const devIdx = NodeUSBDriver.deviceInUse.indexOf(this.device!);\n if (devIdx >= 0) {\n NodeUSBDriver.deviceInUse.splice(devIdx, 1);\n }\n if (usb.usb.listenerCount(\"attach\")) {\n usb.usb.emit(\"attach\", this.device!);\n }\n this.device = undefined;\n });\n }\n });\n }\n });\n }\n }\n\n /**\n * Reads data from the USB device and processes it.\n *\n * @param {Uint8Array} data - The data received from the USB device.\n * @returns {Promise<void>} Resolves when the data has been processed.\n * @example\n * const data = new Uint8Array([0x01, 0x02, 0x03]);\n * driver.read(data).then(() => console.log(\"Data processed\"));\n */\n async read(data: Uint8Array): Promise<void> {\n const dataView = new DataView(data.buffer);\n const messageId = dataView.getUint8(2);\n\n if (messageId === Constants.MESSAGE_STARTUP) {\n await this.write(Messages.requestMessage(0, Constants.MESSAGE_CAPABILITIES));\n } else if (messageId === Constants.MESSAGE_CAPABILITIES) {\n this.maxChannels = dataView.getUint8(3);\n this._canScan = (dataView.getUint8(7) & 0x06) === 0x06;\n await this.write(Messages.setNetworkKey());\n } else if (messageId === Constants.MESSAGE_CHANNEL_EVENT && dataView.getUint8(4) === Constants.MESSAGE_NETWORK_KEY) {\n this.emit(\"startup\", data);\n } else {\n this.emit(\"read\", data);\n }\n }\n\n /**\n * Writes data to the USB device.\n *\n * @param {Uint8Array} data - The data to be sent to the USB device.\n * @returns {Promise<void>} Resolves when the data has been written.\n * @example\n * const data = new Uint8Array([0x01, 0x02, 0x03]);\n * driver.write(data).then(() => console.log(\"Data sent\"));\n */\n async write(data: Uint8Array): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (this.outEndpoint) {\n this.outEndpoint.transfer(Buffer.from(data), (error) => {\n if (error) {\n console.error(\"ERROR SEND: \", error);\n reject(error);\n } else {\n resolve();\n }\n });\n }\n });\n }\n\n /**\n * Resets the device and its channels, and sends a reset message to the system.\n *\n * @returns {Promise<void>} Resolves when the reset is completed.\n * @example\n * driver.reset().then(() => console.log(\"Device reset\"));\n */\n async reset(): Promise<void> {\n await this.detachAll();\n this.maxChannels = 0;\n this.usedChannels = 0;\n await this.write(Messages.resetSystem());\n }\n\n /**\n * Attaches a sensor to the driver and assigns it a channel.\n *\n * @param {BaseSensor} sensor - The sensor to attach.\n * @param {boolean} forScan - Whether the sensor is being attached for scanning.\n * @returns {Promise<boolean>} Resolves with true if the sensor was successfully attached, otherwise false.\n * @example\n * const sensor = new BaseSensor();\n * driver.attach(sensor, true).then((attached) => {\n * if (attached) console.log(\"Sensor attached\");\n * });\n */\n async attach(sensor: BaseSensor, forScan: boolean): Promise<boolean> {\n if (this.usedChannels < 0) {\n return Promise.resolve(false);\n }\n\n if (forScan && this.usedChannels !== 0) {\n return Promise.resolve(false);\n }\n\n if (!forScan && this.maxChannels <= this.usedChannels) {\n return Promise.resolve(false);\n }\n\n this.usedChannels = forScan ? -1 : this.usedChannels + 1;\n this.attachedSensors.push(sensor);\n\n return Promise.resolve(true);\n }\n\n /**\n * Detaches a sensor from the driver.\n *\n * @param {BaseSensor} sensor - The sensor to detach.\n * @returns {Promise<boolean>} Resolves with true if the sensor was successfully detached, otherwise false.\n * @example\n * const sensor = new BaseSensor();\n * driver.detach(sensor).then((detached) => {\n * if (detached) console.log(\"Sensor detached\");\n * });\n */\n async detach(sensor: BaseSensor): Promise<boolean> {\n const idx = this.attachedSensors.indexOf(sensor);\n if (idx < 0) {\n return Promise.resolve(false);\n }\n\n this.usedChannels = this.usedChannels < 0 ? 0 : this.usedChannels - 1;\n this.attachedSensors.splice(idx, 1);\n\n return Promise.resolve(true);\n }\n\n /**\n * Checks if a USB device is present.\n *\n * @returns {Promise<boolean>} Resolves with true if a device is present, otherwise false.\n */\n async isPresent(): Promise<boolean> {\n return Promise.resolve(this.getDevices().length > 0);\n }\n\n /**\n * Checks if the driver is currently scanning.\n *\n * @returns {Promise<boolean>} Resolves with true if the driver is scanning, otherwise false.\n */\n async isScanning(): Promise<boolean> {\n return Promise.resolve(this.usedChannels === -1);\n }\n\n /**\n * Retrieves a list of USB devices matching the specified vendor and product IDs.\n *\n * @private\n * @returns {usb.usb.Device[]} An array of USB devices that match the specified criteria.\n */\n private getDevices(): usb.usb.Device[] {\n const allDevices = usb.getDeviceList();\n return allDevices.filter((d) => d.deviceDescriptor.idVendor === this.idVendor && d.deviceDescriptor.idProduct === this.idProduct).filter((d) => NodeUSBDriver.deviceInUse.indexOf(d) === -1);\n }\n\n /**\n * Detaches all sensors from the USB driver.\n *\n * @private\n * @returns {Promise<void>} Resolves when all sensors are detached.\n */\n private async detachAll(): Promise<void> {\n const copy = this.attachedSensors;\n for (const sensor of copy) {\n await sensor.detach();\n }\n }\n\n /**\n * Handles data received from the USB device and processes the messages.\n *\n * @private\n * @param {Uint8Array} data - The data received from the USB device.\n * @returns {Promise<void>} Resolves when the data has been processed.\n */\n private async onData(data: Uint8Array) {\n if (!data.length) {\n return;\n }\n\n if (this.leftover) {\n data = this.concatUint8Arrays(this.leftover, data);\n this.leftover = undefined;\n }\n\n const dataView = new DataView(data.buffer);\n\n if (dataView.getUint8(0) !== 0xa4) {\n throw new Error(\"SYNC missing\");\n }\n\n const len = data.length;\n let beginBlock = 0;\n while (beginBlock < len) {\n if (beginBlock + 1 === len) {\n this.leftover = data.slice(beginBlock);\n break;\n }\n const blockLen = dataView.getUint8(beginBlock + 1);\n const endBlock = beginBlock + blockLen + 4;\n if (endBlock > len) {\n this.leftover = data.slice(beginBlock);\n break;\n }\n const readData = data.slice(beginBlock, endBlock);\n await this.read(readData);\n beginBlock = endBlock;\n }\n }\n\n /**\n * Concatenates two Uint8Array objects into one.\n *\n * @private\n * @param {Uint8Array} arr1 - The first array.\n * @param {Uint8Array} arr2 - The second array.\n * @returns {Uint8Array} The concatenated result.\n */\n private concatUint8Arrays(arr1: Uint8Array, arr2: Uint8Array): Uint8Array {\n const result = new Uint8Array(arr1.length + arr2.length);\n result.set(arr1, 0);\n result.set(arr2, arr1.length);\n return result;\n }\n}\n","/**\n * An array of objects representing supported hardware devices, each identified by a vendor ID and product ID.\n * This array is used to filter and identify specific USB devices that are supported by the system.\n *\n * @type {Array<{ vendorId: number, productId: number }>}\n *\n * @example\n * // Example of accessing the supported hardware list\n * console.log(supportHardware);\n * // Output: [{ vendorId: 0x0fcf, productId: 0x1008 }, { vendorId: 0x0fcf, productId: 0x1009 }]\n */\nexport const supportHardware: Array<{ vendorId: number; productId: number }> = [\n { vendorId: 0x0fcf, productId: 0x1008 }, // Device GarminStick2\n { vendorId: 0x0fcf, productId: 0x1009 }, // Device GarminStick3\n];\n","import { BaseSensor } from \"../../sensors/baseSensor\";\nimport { USBDriverBase } from \"../../types/usbDriverBase\";\nimport EventEmitter from \"events\";\nimport { supportHardware } from \"./usbDriverUtils\";\nimport { Messages } from \"../../utils/messages\";\nimport { Constants } from \"../../types/constants\";\n\n/**\n * WebUSBDriver is a class that manages the connection and communication with USB devices using the WebUSB API.\n * It handles device setup, communication, and sensor attachment.\n *\n * @extends EventEmitter\n * @implements USBDriverBase\n */\nexport class WebUSBDriver extends EventEmitter implements USBDriverBase {\n /**\n * Stores the USB devices currently in use.\n * @type {USBDevice[]}\n * @private\n */\n private static deviceInUse: USBDevice[] = [];\n\n /**\n * The current USB device.\n * @type {USBDevice|undefined}\n * @private\n */\n private device: USBDevice | undefined;\n\n /**\n * The USB interface of the connected device.\n * @type {USBInterface|undefined}\n * @private\n */\n private iface: USBInterface | undefined;\n\n /**\n * The input endpoint for communication.\n * @type {USBEndpoint|undefined}\n * @private\n */\n private inEndpoint: USBEndpoint | undefined;\n\n /**\n * The output endpoint for communication.\n * @type {USBEndpoint|undefined}\n * @private\n */\n private outEndpoint: USBEndpoint | undefined;\n\n /**\n * Stores any leftover data from previous USB reads.\n * @type {Uint8Array|undefined}\n * @private\n */\n private leftover: Uint8Array | undefined;\n\n /**\n * The number of channels currently used.\n * @type {number}\n */\n usedChannels: number = 0;\n\n /**\n * The sensors attached to the driver.\n * @type {BaseSensor[]}\n * @private\n */\n private attachedSensors: BaseSensor[] = [];\n\n /**\n * Controller for aborting asynchronous operations.\n * @type {AbortController}\n * @private\n */\n private abortController: AbortController;\n\n /**\n * Signal for aborting asynchronous operations.\n * @type {AbortSignal}\n * @private\n */\n private signal: AbortSignal;\n\n /**\n * The maximum number of channels available for communication.\n * @type {number}\n */\n maxChannels: number = 0;\n\n /**\n * Indicates whether the device can scan.\n * @type {boolean}\n */\n _canScan: boolean = false;\n\n /**\n * Initializes the WebUSBDriver instance, setting up the abort controller and signal.\n */\n constructor() {\n super();\n this.setMaxListeners(50);\n this.abortController = new AbortController();\n this.signal = this.abortController.signal;\n }\n\n /**\n * Checks if a new sensor can be attached to the driver.\n * It verifies whether the current number of used channels is less than the maximum available channels.\n *\n * @returns {Promise<boolean>} Resolves with true if a new sensor can be attached, otherwise false.\n *\n * @example\n * const canAttach = await this.stick.canAttach();\n * if (canAttach) {\n * console.log(\"A new sensor can be attached.\");\n * } else {\n * console.log(\"Cannot attach sensor: Maximum number of channels reached.\");\n * }\n */\n async canAttach(): Promise<boolean> {\n return Promise.resolve(this.usedChannels < this.maxChannels);\n }\n\n /**\n * Checks if the device can scan for channels.\n *\n * @returns {Promise<boolean>} Resolves with true if the device can scan, otherwise false.\n */\n async canScan(): Promise<boolean> {\n return Promise.resolve(this._canScan);\n }\n\n /**\n * Opens a connection to the USB device and initializes the endpoints.\n *\n * @returns {Promise<boolean>} Resolves with true if the device was successfully opened, otherwise false.\n */\n public async open(): Promise<boolean> {\n try {\n if (!this.device) {\n this.device = await navigator.usb.requestDevice({ filters: supportHardware });\n }\n\n await this.device.open();\n this.iface = this.device.configuration?.interfaces[0];\n\n if (!this.iface) {\n throw new Error(\"No interface configuration found.\");\n }\n\n await this.device.claimInterface(this.iface.interfaceNumber);\n\n WebUSBDriver.deviceInUse.push(this.device);\n\n this.inEndpoint = this.iface.alternate.endpoints.find((e) => e.direction === \"in\");\n this.outEndpoint = this.iface.alternate.endpoints.find((e) => e.direction === \"out\");\n\n if (!this.inEndpoint || !this.outEndpoint) {\n throw new Error(\"In or Out endpoint not found.\");\n }\n\n await this.reset();\n await this.readLoop();\n\n return true;\n } catch (error) {\n console.log(error);\n await this.close();\n return false;\n }\n }\n\n /**\n * Continuously reads data from the USB device.\n * Recursively calls itself after each read until aborted.\n *\n * @private\n * @returns {Promise<void>} Resolves when the read loop is completed or aborted.\n */\n private async readLoop(): Promise<void> {\n if (this.signal.aborted || !this.inEndpoint) {\n return;\n }\n\n try {\n const result = await this.device?.transferIn(this.inEndpoint.endpointNumber, this.inEndpoint.packetSize);\n if (!result || !result.data) {\n return this.readLoop();\n }\n\n let buffer = new Uint8Array(result.data.buffer);\n\n if (this.leftover) {\n buffer = this.concatUint8Arrays(this.leftover, buffer);\n