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

interfaces-endpoints.mddocs/

Interface and Endpoint Management

Interface and endpoint management provides methods to claim USB interfaces and manage data endpoints for bulk and interrupt transfers. This includes interface claiming/releasing, alternate setting management, and endpoint access.

Capabilities

Interface Access

Access and manage USB interfaces on a device.

/**
 * Return the interface with the specified interface number
 * The device must be open to use this method
 * @param addr - Interface number (default: 0)
 * @returns Interface object
 */
interface(addr: number): Interface;

/**
 * USB Interface class for device communication
 */
interface Interface {
  /** Integer interface number */
  interfaceNumber: number;
  
  /** Integer alternate setting number */
  altSetting: number;
  
  /** Object with fields from the interface descriptor */
  descriptor: InterfaceDescriptor;
  
  /** List of endpoints on this interface: InEndpoint and OutEndpoint objects */
  endpoints: Endpoint[];
}

Usage Examples:

import { findByIds } from 'usb';

const device = findByIds(0x1234, 0x5678);
if (device) {
  device.open();
  
  // Get the first interface (interface 0)
  const interface0 = device.interface(0);
  console.log(`Interface Number: ${interface0.interfaceNumber}`);
  console.log(`Alternate Setting: ${interface0.altSetting}`);
  console.log(`Interface Class: ${interface0.descriptor.bInterfaceClass}`);
  console.log(`Number of Endpoints: ${interface0.endpoints.length}`);
  
  // Access interface descriptor properties
  const desc = interface0.descriptor;
  console.log('Interface Descriptor:');
  console.log(`  Class: ${desc.bInterfaceClass}`);
  console.log(`  Subclass: ${desc.bInterfaceSubClass}`);
  console.log(`  Protocol: ${desc.bInterfaceProtocol}`);
  console.log(`  Endpoints: ${desc.bNumEndpoints}`);
  
  device.close();
}

Interface Claiming and Releasing

Claim interfaces for exclusive access and release them when done.

/**
 * Claims the interface. This method must be called before using any endpoints of this interface.
 * The device must be open to use this method.
 */
claim(): void;

/**
 * Releases the interface and resets the alternate setting. Calls callback when complete.
 * It is an error to release an interface with pending transfers.
 * @param callback - Completion callback
 */
release(callback?: (error?: LibUSBException) => void): void;

/**
 * Releases the interface with optional endpoint stream control
 * @param closeEndpoints - If true, any active endpoint streams are stopped
 * @param callback - Completion callback  
 */
release(closeEndpoints?: boolean, callback?: (error?: LibUSBException) => void): void;

/**
 * Async version of release
 */
releaseAsync(): Promise<void>;

Usage Examples:

import { findByIds } from 'usb';

const device = findByIds(0x1234, 0x5678);
if (device) {
  device.open();
  
  const interface0 = device.interface(0);
  
  try {
    // Claim the interface for exclusive access
    interface0.claim();
    console.log('Interface claimed successfully');
    
    // Now you can use the interface endpoints
    console.log(`Interface has ${interface0.endpoints.length} endpoints`);
    
    // Use endpoints here...
    
    // Release the interface when done (callback version)
    interface0.release((error) => {
      if (error) {
        console.error('Failed to release interface:', error.message);
      } else {
        console.log('Interface released successfully');
      }
      device.close();
    });
    
  } catch (error) {
    console.error('Failed to claim interface:', error);
    device.close();
  }
}

// Using async/await pattern
async function useInterfaceAsync() {
  const device = findByIds(0x1234, 0x5678);
  if (!device) return;
  
  device.open();
  const interface0 = device.interface(0);
  
  try {
    interface0.claim();
    console.log('Interface claimed');
    
    // Use interface...
    
    // Release with async
    await interface0.releaseAsync();
    console.log('Interface released');
    
  } catch (error) {
    console.error('Interface error:', error);
  } finally {
    device.close();
  }
}

// Release with endpoint stream closure
const deviceWithStreams = findByIds(0x5678, 0x1234);
if (deviceWithStreams) {
  deviceWithStreams.open();
  const interface0 = deviceWithStreams.interface(0);
  
  interface0.claim();
  
  // ... start some endpoint streams/polling ...
  
  // Release and stop all endpoint streams
  interface0.release(true, (error) => {
    if (!error) {
      console.log('Interface released and all endpoint streams stopped');
    }
    deviceWithStreams.close();
  });
}

Alternate Setting Management

Manage alternate interface settings.

/**
 * Sets the alternate setting. It updates the interface.endpoints array 
 * to reflect the endpoints found in the alternate setting.
 * The device must be open to use this method.
 * @param altSetting - Alternate setting number
 * @param callback - Completion callback
 */
setAltSetting(altSetting: number, callback?: (error?: LibUSBException) => void): void;

/**
 * Async version of setAltSetting
 * @param alternateSetting - Alternate setting number
 */
setAltSettingAsync(alternateSetting: number): Promise<void>;

Usage Examples:

import { findByIds } from 'usb';

const device = findByIds(0x1234, 0x5678);
if (device) {
  device.open();
  const interface0 = device.interface(0);
  
  interface0.claim();
  
  console.log(`Current alternate setting: ${interface0.altSetting}`);
  console.log(`Current endpoints: ${interface0.endpoints.length}`);
  
  // Set alternate setting 1
  interface0.setAltSetting(1, (error) => {
    if (error) {
      console.error('Failed to set alternate setting:', error.message);
      return;
    }
    
    console.log(`New alternate setting: ${interface0.altSetting}`);
    console.log(`New endpoints: ${interface0.endpoints.length}`);
    
    // The endpoints array has been updated to reflect the new setting
    interface0.endpoints.forEach((endpoint, index) => {
      console.log(`  Endpoint ${index}: 0x${endpoint.address.toString(16)}, ${endpoint.direction}`);
    });
    
    interface0.release(() => device.close());
  });
}

// Using async/await
async function setAltSettingAsync() {
  const device = findByIds(0x1234, 0x5678);
  if (!device) return;
  
  device.open();
  const interface0 = device.interface(0);
  
  try {
    interface0.claim();
    
    // Set alternate setting using async method
    await interface0.setAltSettingAsync(2);
    console.log('Alternate setting 2 activated');
    
    await interface0.releaseAsync();
  } catch (error) {
    console.error('Error setting alternate setting:', error);
  } finally {
    device.close();
  }
}

Kernel Driver Management

Manage kernel driver attachment/detachment for interfaces.

/**
 * Returns false if a kernel driver is not active; true if active.
 * The device must be open to use this method.
 */
isKernelDriverActive(): boolean;

/**
 * Detaches the kernel driver from the interface.
 * The device must be open to use this method.
 */
detachKernelDriver(): void;

/**
 * Re-attaches the kernel driver for the interface.
 * The device must be open to use this method.
 */
attachKernelDriver(): void;

Usage Examples:

import { findByIds } from 'usb';

const device = findByIds(0x1234, 0x5678);
if (device) {
  device.open();
  const interface0 = device.interface(0);
  
  // Check if kernel driver is active
  if (interface0.isKernelDriverActive()) {
    console.log('Kernel driver is active, need to detach');
    
    try {
      // Detach kernel driver
      interface0.detachKernelDriver();
      console.log('Kernel driver detached');
      
      // Now we can claim the interface
      interface0.claim();
      console.log('Interface claimed after detaching kernel driver');
      
      // Use interface...
      
      // Release interface
      interface0.release((error) => {
        if (!error) {
          // Reattach kernel driver
          try {
            interface0.attachKernelDriver();
            console.log('Kernel driver reattached');
          } catch (attachError) {
            console.warn('Could not reattach kernel driver:', attachError);
          }
        }
        device.close();
      });
      
    } catch (error) {
      console.error('Failed to detach kernel driver:', error);
      device.close();
    }
  } else {
    console.log('No kernel driver active');
    interface0.claim();
    // ... use interface ...
    interface0.release(() => device.close());
  }
}

// Safe kernel driver management pattern
function safeInterfaceAccess(device: Device, interfaceNumber: number) {
  const interface0 = device.interface(interfaceNumber);
  let kernelDriverWasActive = false;
  
  try {
    // Check and detach kernel driver if needed
    if (interface0.isKernelDriverActive()) {
      kernelDriverWasActive = true;
      interface0.detachKernelDriver();
      console.log('Kernel driver detached');
    }
    
    // Claim interface
    interface0.claim();
    console.log('Interface claimed');
    
    return {
      interface: interface0,
      cleanup: (callback?: () => void) => {
        interface0.release((error) => {
          if (!error && kernelDriverWasActive) {
            try {
              interface0.attachKernelDriver();
              console.log('Kernel driver reattached');
            } catch (attachError) {
              console.warn('Could not reattach kernel driver');
            }
          }
          if (callback) callback();
        });
      }
    };
    
  } catch (error) {
    console.error('Failed to access interface:', error);
    throw error;
  }
}

Endpoint Access

Access and manage endpoints on interfaces.

/**
 * Return the InEndpoint or OutEndpoint with the specified address.
 * The device must be open to use this method.
 * @param addr - Endpoint address
 * @returns Endpoint object or undefined if not found
 */
endpoint(addr: number): Endpoint | undefined;

/**
 * Base endpoint interface
 */
interface Endpoint {
  /** Endpoint address */
  address: number;
  
  /** Endpoint direction: "in" or "out" */
  direction: 'in' | 'out';
  
  /** Endpoint type: BULK, INTERRUPT, etc. */
  transferType: number;
  
  /** Sets the timeout in milliseconds for transfers on this endpoint */
  timeout: number;
  
  /** Object with fields from the endpoint descriptor */
  descriptor: EndpointDescriptor;
}

Usage Examples:

import { findByIds, LIBUSB_TRANSFER_TYPE_BULK, LIBUSB_TRANSFER_TYPE_INTERRUPT } from 'usb';

const device = findByIds(0x1234, 0x5678);
if (device) {
  device.open();
  const interface0 = device.interface(0);
  interface0.claim();
  
  // List all endpoints
  console.log('Available endpoints:');
  interface0.endpoints.forEach((endpoint, index) => {
    console.log(`  Endpoint ${index}:`);
    console.log(`    Address: 0x${endpoint.address.toString(16)}`);
    console.log(`    Direction: ${endpoint.direction}`);
    console.log(`    Type: ${endpoint.transferType === LIBUSB_TRANSFER_TYPE_BULK ? 'BULK' : 
                        endpoint.transferType === LIBUSB_TRANSFER_TYPE_INTERRUPT ? 'INTERRUPT' : 'OTHER'}`);
    console.log(`    Max Packet Size: ${endpoint.descriptor.wMaxPacketSize}`);
  });
  
  // Find specific endpoint by address
  const endpoint1 = interface0.endpoint(0x81); // IN endpoint 1
  if (endpoint1) {
    console.log(`Found IN endpoint: 0x${endpoint1.address.toString(16)}`);
    console.log(`Direction: ${endpoint1.direction}`);
    
    // Configure endpoint timeout
    endpoint1.timeout = 5000; // 5 second timeout
    console.log(`Set endpoint timeout to ${endpoint1.timeout}ms`);
  }
  
  // Find OUT endpoint
  const outEndpoint = interface0.endpoint(0x02); // OUT endpoint 2
  if (outEndpoint) {
    console.log(`Found OUT endpoint: 0x${outEndpoint.address.toString(16)}`);
    console.log(`Max packet size: ${outEndpoint.descriptor.wMaxPacketSize}`);
  }
  
  // Find endpoints by direction
  const inEndpoints = interface0.endpoints.filter(ep => ep.direction === 'in');
  const outEndpoints = interface0.endpoints.filter(ep => ep.direction === 'out');
  
  console.log(`Found ${inEndpoints.length} IN endpoints and ${outEndpoints.length} OUT endpoints`);
  
  interface0.release(() => device.close());
}

// Helper function to find endpoints by type
function findEndpointsByType(interface0: Interface, transferType: number) {
  return interface0.endpoints.filter(endpoint => endpoint.transferType === transferType);
}

// Usage
const device2 = findByIds(0x5678, 0x1234);
if (device2) {
  device2.open();
  const interface0 = device2.interface(0);
  interface0.claim();
  
  // Find all bulk endpoints
  const bulkEndpoints = findEndpointsByType(interface0, LIBUSB_TRANSFER_TYPE_BULK);
  console.log(`Found ${bulkEndpoints.length} bulk endpoints`);
  
  // Find all interrupt endpoints
  const interruptEndpoints = findEndpointsByType(interface0, LIBUSB_TRANSFER_TYPE_INTERRUPT);
  console.log(`Found ${interruptEndpoints.length} interrupt endpoints`);
  
  interface0.release(() => device2.close());
}

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