diff --git a/packages/uhk-common/src/models/uhk-products.ts b/packages/uhk-common/src/models/uhk-products.ts index 5c093bec..11fcb745 100644 --- a/packages/uhk-common/src/models/uhk-products.ts +++ b/packages/uhk-common/src/models/uhk-products.ts @@ -15,6 +15,8 @@ export interface VidPidPair { export interface UhkDeviceProduct { id: UHK_DEVICE_IDS_TYPE; + // The reference of the device when provided as CLI argument + asCliArg: string; firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS_TYPE, // Use it in logs instead of the name because UHK 80 left and right have the same name. // But we have to differentiate them in the logs @@ -28,6 +30,7 @@ export interface UhkDeviceProduct { export const UNKNOWN_DEVICE: UhkDeviceProduct = { id: 0 as UHK_DEVICE_IDS_TYPE, + asCliArg: '', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.KBOOT, logName: 'Unknown', name: 'Unknown', @@ -39,6 +42,7 @@ export const UNKNOWN_DEVICE: UhkDeviceProduct = { export const UHK_60_DEVICE: UhkDeviceProduct = { id: UHK_DEVICE_IDS.UHK60V1_RIGHT, + asCliArg: 'uhk60v1', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.KBOOT, logName: 'UHK 60 v1', name: 'UHK 60 v1', @@ -73,6 +77,7 @@ export const UHK_60_DEVICE: UhkDeviceProduct = { export const UHK_60_V2_DEVICE: UhkDeviceProduct = { id: UHK_DEVICE_IDS.UHK60V2_RIGHT, + asCliArg: 'uhk60v2', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.KBOOT, logName: 'UHK 60 v2', name: 'UHK 60 v2', @@ -107,6 +112,7 @@ export const UHK_60_V2_DEVICE: UhkDeviceProduct = { export const UHK_80_DEVICE_LEFT: UhkDeviceProduct = { id: UHK_DEVICE_IDS.UHK80_LEFT, + asCliArg: 'uhk80left', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.MCUBOOT, logName: 'UHK 80 left', name: 'UHK 80', @@ -122,13 +128,13 @@ export const UHK_80_DEVICE_LEFT: UhkDeviceProduct = { pid: 0x0006, // decimal 6 }, ], - // TODO: Implement when we know buspal: [], reportId: 4, }; export const UHK_80_DEVICE: UhkDeviceProduct = { id: UHK_DEVICE_IDS.UHK80_RIGHT, + asCliArg: 'uhk80', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.MCUBOOT, logName: 'UHK 80 right', name: 'UHK 80', @@ -144,13 +150,13 @@ export const UHK_80_DEVICE: UhkDeviceProduct = { pid: 0x0008, // decimal 8 }, ], - // TODO: Implement when we know buspal: [], reportId: 4, }; export const UHK_DONGLE: UhkDeviceProduct = { id: UHK_DEVICE_IDS.UHK_DONGLE, + asCliArg: 'dongle', firmwareUpgradeMethod: FIRMWARE_UPGRADE_METHODS.MCUBOOT, logName: 'UHK Dongle', name: 'UHK Dongle', @@ -166,7 +172,6 @@ export const UHK_DONGLE: UhkDeviceProduct = { pid: 0x0004, // decimal 4 }, ], - // TODO: Implement when we know buspal: [], reportId: 4, }; diff --git a/packages/usb/wait-for-device.ts b/packages/usb/wait-for-device.ts new file mode 100755 index 00000000..6265c01f --- /dev/null +++ b/packages/usb/wait-for-device.ts @@ -0,0 +1,118 @@ +#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning + +import { devicesAsync } from 'node-hid'; +import { SerialPort } from 'serialport'; +import { + ALL_UHK_DEVICES, + FIRMWARE_UPGRADE_METHODS, + VidPidPair, +} from 'uhk-common'; +import { + isUhkCommunicationUsage, + snooze, +} from 'uhk-usb'; + +import { yargs } from './src/index.js'; + +const REENUMERATION_MODES = ['device', 'bootloader', 'buspal']; +const reenumerationOptions = REENUMERATION_MODES.join('|'); +const devicesOptions = ALL_UHK_DEVICES.map(uhkDevice => uhkDevice.asCliArg).join('|'); + +const argv = yargs + .scriptName('./wait-for-device.ts') + .usage(`Usage: $0 {${devicesOptions}} {${reenumerationOptions}} timeout`) + .demandCommand(2, 'Device and enumeration mode are required. Timeout in seconds is optional, default value 5 seconds.') + .argv; + +const deviceArg = argv._[0] as string; +const enumerationModeArg = argv._[1] as string; +const timeoutArg = argv._[2] as string; + +const uhkDeviceProduct = ALL_UHK_DEVICES.find(uhkDevice => uhkDevice.asCliArg === deviceArg); + +if (!uhkDeviceProduct) { + console.error(`Invalid device: ${deviceArg}. Available options: ${devicesOptions}`); + process.exit(1); +} + +const reenumerationMode = REENUMERATION_MODES.find(value => value === enumerationModeArg); + +if (!reenumerationMode) { + console.error(`Invalid reenumeration mode: ${enumerationModeArg}. Available options: ${reenumerationOptions}`); + process.exit(1); +} + +if (reenumerationMode === 'buspal' && uhkDeviceProduct.buspal.length === 0) { + console.error(`${deviceArg} does not support buspal reenumeration mode.`); + process.exit(1); +} + +let timeout = 5000; + +if (timeoutArg) { + const tmpTimeout = Number(timeoutArg); + if (Number.isNaN(tmpTimeout)) { + console.error(`Invalid timeout: ${timeoutArg}. Please provide a number.`); + process.exit(1); + } + + timeout = tmpTimeout; +} + + +let vidPids: VidPidPair[]; + +if (reenumerationMode === 'device') { + vidPids = uhkDeviceProduct.keyboard; +} +else if (reenumerationMode === 'bootloader') { + vidPids = uhkDeviceProduct.bootloader; +} +else if (reenumerationMode === 'buspal') { + vidPids = uhkDeviceProduct.buspal; +} +else { + console.error(`Not implemented reenumeration mode mapping: ${reenumerationMode}`); +} + +const startTime = new Date(); + +let found = false; + +while (new Date().getTime() - startTime.getTime() < timeout && !found) { + + if (reenumerationMode === 'bootloader' && uhkDeviceProduct.firmwareUpgradeMethod === FIRMWARE_UPGRADE_METHODS.MCUBOOT) { + const serialDevices = await SerialPort.list(); + + for (const serialDevice of serialDevices) { + found = vidPids.some(vidPid => Number.parseInt(serialDevice.vendorId, 16) === vidPid.vid && Number.parseInt(serialDevice.productId, 16) === vidPid.pid); + + if (found) { + break; + } + } + } + else { + const hidDevices = await devicesAsync(); + for (const hidDevice of hidDevices) { + found = vidPids.some(vidPid => { + return vidPid.vid === hidDevice.vendorId && vidPid.pid === hidDevice.productId + && (reenumerationMode !== 'device' || isUhkCommunicationUsage(hidDevice)); + }); + + if (found) { + break; + } + } + } + + await snooze(100); +} + +if (found) { + process.exit(0); +} +else { + console.error('Cannot find device within timeout'); + process.exit(1); +}