CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-usb

Cross-platform Node.js library for USB device communication with both legacy and WebUSB-compatible APIs.

Pending
Overview
Eval results
Files

device-management.mddocs/

Device Discovery and Management

Device discovery and management provides functions to find, enumerate, and manage USB devices connected to the system. This includes listing all devices, finding specific devices by identifiers, and accessing device metadata.

Capabilities

Device Enumeration

Get a list of all USB devices currently attached to the system.

/**
 * Return a list of Device objects for the USB devices attached to the system
 * @returns Array of Device objects representing connected USB devices
 */
function getDeviceList(): Device[];

Usage Examples:

import { getDeviceList } from 'usb';

// Get all connected USB devices
const devices = getDeviceList();
console.log(`Found ${devices.length} USB devices`);

// Iterate through devices
devices.forEach((device, index) => {
  console.log(`Device ${index}:`);
  console.log(`  Vendor ID: 0x${device.deviceDescriptor.idVendor.toString(16).padStart(4, '0')}`);
  console.log(`  Product ID: 0x${device.deviceDescriptor.idProduct.toString(16).padStart(4, '0')}`);
  console.log(`  Bus: ${device.busNumber}, Address: ${device.deviceAddress}`);
});

Device Search by IDs

Find a specific device using vendor ID and product ID.

/**
 * Convenience method to get the first device with the specified VID and PID
 * @param vid - USB vendor ID
 * @param pid - USB product ID  
 * @returns Device object if found, undefined otherwise
 */
function findByIds(vid: number, pid: number): Device | undefined;

Usage Examples:

import { findByIds } from 'usb';

// Find Arduino Uno (example IDs)
const arduino = findByIds(0x2341, 0x0043);
if (arduino) {
  console.log('Arduino Uno found!');
  console.log(`Bus: ${arduino.busNumber}, Address: ${arduino.deviceAddress}`);
} else {
  console.log('Arduino Uno not connected');
}

// Find device by hex IDs
const customDevice = findByIds(0x1234, 0xABCD);

Device Search by Serial Number

Find a device using its serial number string.

/**
 * Convenience method to get the device with the specified serial number
 * @param serialNumber - Device serial number string
 * @returns Promise resolving to Device if found, undefined otherwise
 */
function findBySerialNumber(serialNumber: string): Promise<Device | undefined>;

Usage Examples:

import { findBySerialNumber } from 'usb';

// Find device by serial number
const device = await findBySerialNumber('ABC123DEF456');
if (device) {
  console.log('Device found by serial number');
  console.log(`Vendor: 0x${device.deviceDescriptor.idVendor.toString(16)}`);
} else {
  console.log('Device with serial number not found');
}

// Handle with try-catch
try {
  const myDevice = await findBySerialNumber('MY_DEVICE_SERIAL');
  if (myDevice) {
    // Use device
    myDevice.open();
    // ... perform operations
    myDevice.close();
  }
} catch (error) {
  console.error('Error searching for device:', error);
}

Device Object Properties

Each Device object contains metadata and properties for USB device access.

/**
 * USB Device class with properties and methods
 */
interface Device {
  /** Integer USB bus number */
  busNumber: number;
  
  /** Integer USB device address */
  deviceAddress: number;  
  
  /** Array containing USB device port numbers, or undefined if not supported */
  portNumbers: number[];
  
  /** Object with properties for the fields of the device descriptor */
  deviceDescriptor: DeviceDescriptor;
  
  /** List of Interface objects for the interfaces of the default configuration */
  interfaces?: Interface[];
  
  /** Timeout in milliseconds to use for control transfers */
  timeout: number;
  
  /** Object with properties for the fields of the active configuration descriptor */
  readonly configDescriptor?: ConfigDescriptor;
  
  /** Contains all config descriptors of the device */
  readonly allConfigDescriptors: ConfigDescriptor[];
  
  /** Contains the parent of the device, such as a hub. If no parent, this is null */
  readonly parent: Device;
}

Usage Examples:

import { getDeviceList } from 'usb';

const devices = getDeviceList();
const device = devices[0];

// Access device properties
console.log('Device Info:');
console.log(`  Bus Number: ${device.busNumber}`);
console.log(`  Device Address: ${device.deviceAddress}`);
console.log(`  Port Numbers: [${device.portNumbers.join(', ')}]`);

// Access device descriptor
const desc = device.deviceDescriptor;
console.log(`  Vendor ID: 0x${desc.idVendor.toString(16)}`);
console.log(`  Product ID: 0x${desc.idProduct.toString(16)}`);
console.log(`  Device Class: ${desc.bDeviceClass}`);
console.log(`  USB Version: ${desc.bcdUSB.toString(16)}`);

// Check if device has parent (hub)
if (device.parent) {
  console.log(`  Connected through hub: Bus ${device.parent.busNumber}`);
}

// Access configuration information
if (device.configDescriptor) {
  console.log(`  Configuration Value: ${device.configDescriptor.bConfigurationValue}`);
  console.log(`  Number of Interfaces: ${device.configDescriptor.bNumInterfaces}`);
  console.log(`  Max Power: ${device.configDescriptor.bMaxPower * 2}mA`);
}

Device Filtering and Selection

Filter devices based on various criteria.

Usage Examples:

import { getDeviceList } from 'usb';

const devices = getDeviceList();

// Filter by device class (e.g., HID devices)
const hidDevices = devices.filter(device => 
  device.deviceDescriptor.bDeviceClass === 3 // HID class
);

// Filter by manufacturer (requires opening device to get string)
const filterByManufacturer = async (manufacturerName: string) => {
  const matchingDevices = [];
  
  for (const device of devices) {
    try {
      device.open();
      if (device.deviceDescriptor.iManufacturer > 0) {
        device.getStringDescriptor(device.deviceDescriptor.iManufacturer, (error, value) => {
          if (!error && value === manufacturerName) {
            matchingDevices.push(device);
          }
        });
      }
      device.close();
    } catch (error) {
      // Skip devices that can't be opened
      continue;
    }
  }
  
  return matchingDevices;
};

// Filter by vendor ID range
const vendorDevices = devices.filter(device => 
  device.deviceDescriptor.idVendor >= 0x1000 && 
  device.deviceDescriptor.idVendor < 0x2000
);

// Find devices with specific interface class
const devicesWithAudioInterface = devices.filter(device => {
  if (!device.configDescriptor) return false;
  
  return device.configDescriptor.interfaces.some(interfaceGroup =>
    interfaceGroup.some(altSetting => altSetting.bInterfaceClass === 1) // Audio class
  );
});

Utility Functions

Additional utility functions for USB management and debugging.

/**
 * Set the libusb debug level (between 0 and 4)
 * Higher levels provide more verbose logging
 * @param level - Debug level (0=none, 1=error, 2=warning, 3=info, 4=debug)
 */
function setDebugLevel(level: number): void;

/**
 * Use USBDK Backend (Windows only)
 * On Windows, use the USBDK backend of libusb instead of WinUSB
 */
function useUsbDkBackend(): void;

/**
 * Restore (re-reference) the hotplug events unreferenced by unrefHotplugEvents()
 * Hotplug events are referenced by default
 */
function refHotplugEvents(): void;

/**
 * Unreference the hotplug events from the event loop
 * Allows the process to exit even when listening for attach and detach events
 */
function unrefHotplugEvents(): void;

Usage Examples:

import { setDebugLevel, useUsbDkBackend, refHotplugEvents, unrefHotplugEvents, usb } from 'usb';

// Enable debug logging
console.log('Enabling USB debug logging...');
setDebugLevel(3); // Info level logging

// On Windows, optionally use USBDK backend
if (process.platform === 'win32') {
  console.log('Using USBDK backend on Windows');
  useUsbDkBackend();
}

// Set up device monitoring with proper cleanup
function setupDeviceMonitoring() {
  let deviceCount = 0;
  
  // Set up event handlers
  usb.on('attach', (device) => {
    deviceCount++;
    console.log(`Device attached (${deviceCount} total):`, {
      vendorId: `0x${device.deviceDescriptor.idVendor.toString(16)}`,
      productId: `0x${device.deviceDescriptor.idProduct.toString(16)}`,
      busNumber: device.busNumber,
      deviceAddress: device.deviceAddress
    });
  });
  
  usb.on('detach', (device) => {
    deviceCount--;
    console.log(`Device detached (${deviceCount} total):`, {
      vendorId: `0x${device.deviceDescriptor.idVendor.toString(16)}`,
      productId: `0x${device.deviceDescriptor.idProduct.toString(16)}`
    });
  });
  
  console.log('Device monitoring started');
  console.log('Note: Process will not exit due to hotplug event listeners');
  
  // Allow process to exit after some time
  setTimeout(() => {
    console.log('Unreferencing hotplug events to allow process exit...');
    unrefHotplugEvents();
    
    // Process can now exit naturally
    console.log('Process can now exit. Device monitoring continues.');
    
    // Later, if you want to prevent exit again
    setTimeout(() => {
      console.log('Re-referencing hotplug events...');
      refHotplugEvents();
      console.log('Process will now stay alive for hotplug events');
      
      // Clean up after demo
      setTimeout(() => {
        console.log('Demo complete, stopping monitoring...');
        usb.removeAllListeners('attach');
        usb.removeAllListeners('detach');
      }, 5000);
      
    }, 3000);
  }, 10000);
}

// Debug level examples
function demonstrateDebugLevels() {
  console.log('Testing different debug levels:');
  
  // Test each debug level
  [0, 1, 2, 3, 4].forEach((level, index) => {
    setTimeout(() => {
      console.log(`\n--- Setting debug level to ${level} ---`);
      setDebugLevel(level);
      
      // Perform some USB operations to generate debug output
      const devices = getDeviceList();
      console.log(`Found ${devices.length} devices at debug level ${level}`);
      
      if (devices.length > 0) {
        const device = devices[0];
        console.log(`First device: VID:PID = ${device.deviceDescriptor.idVendor.toString(16)}:${device.deviceDescriptor.idProduct.toString(16)}`);
      }
    }, index * 2000);
  });
}

// Windows USBDK backend example
function demonstrateUsbDkBackend() {
  if (process.platform !== 'win32') {
    console.log('USBDK backend is only available on Windows');
    return;
  }
  
  console.log('Testing USBDK backend on Windows...');
  
  // Get devices with default WinUSB backend
  const devicesWinUsb = getDeviceList();
  console.log(`Devices found with WinUSB: ${devicesWinUsb.length}`);
  
  // Switch to USBDK backend
  useUsbDkBackend();
  console.log('Switched to USBDK backend');
  
  // Get devices with USBDK backend
  const devicesUsbDk = getDeviceList();
  console.log(`Devices found with USBDK: ${devicesUsbDk.length}`);
  
  // Compare results
  if (devicesUsbDk.length !== devicesWinUsb.length) {
    console.log('Different device counts detected between backends');
    console.log('This might indicate driver compatibility differences');
  } else {
    console.log('Same device count with both backends');
  }
}

// Graceful shutdown pattern
function setupGracefulShutdown() {
  let shutdownInProgress = false;
  
  // Set up device monitoring
  usb.on('attach', (device) => {
    if (!shutdownInProgress) {
      console.log('Device attached:', device.deviceDescriptor.idVendor.toString(16));
    }
  });
  
  usb.on('detach', (device) => {
    if (!shutdownInProgress) {
      console.log('Device detached:', device.deviceDescriptor.idVendor.toString(16));
    }
  });
  
  // Handle shutdown signals
  const gracefulShutdown = (signal: string) => {
    if (shutdownInProgress) return;
    shutdownInProgress = true;
    
    console.log(`\nReceived ${signal}, shutting down gracefully...`);
    
    // Remove all USB event listeners
    usb.removeAllListeners('attach');
    usb.removeAllListeners('detach');
    
    // Unreference hotplug events to allow exit
    unrefHotplugEvents();
    
    console.log('USB monitoring stopped');
    process.exit(0);
  };
  
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
  
  console.log('Graceful shutdown handlers installed');
  console.log('Press Ctrl+C to shutdown gracefully');
}

// Run demonstrations
if (require.main === module) {
  console.log('USB Utility Functions Demo');
  console.log('===========================\n');
  
  demonstrateDebugLevels();
  
  setTimeout(() => {
    demonstrateUsbDkBackend();
  }, 12000);
  
  setTimeout(() => {
    setupDeviceMonitoring();
  }, 15000);
  
  setTimeout(() => {
    setupGracefulShutdown();
  }, 18000);
}

Install with Tessl CLI

npx tessl i tessl/npm-usb

docs

constants-errors.md

data-transfers.md

descriptors.md

device-communication.md

device-management.md

event-handling.md

index.md

interfaces-endpoints.md

webusb-api.md

tile.json