Start · Sprachen · JavaScript · Referenz · USBDevice.controlTransferOut()

USBDevice.controlTransferOut()

Methode

Sendet eine Control-Transfer-Anfrage (Befehl oder Daten) vom Host an das USB-Gerät und löst mit einem <code>USBOutTransferResult</code> auf.

seit JavaScript WebUSB: Chromium-Browser (Chrome/Edge 61+), nur in Kategorie: webapi

Signatur

USBDevice.prototype.controlTransferOut(setup, data)

Beschreibung

controlTransferOut() schickt einen Control Transfer in Richtung Host → Gerät über den Standard-Endpunkt 0. Mit solchen Transfers sendet man Konfigurationsbefehle, herstellerspezifische Kommandos (Vendor Requests) oder klassenspezifische Anfragen, z. B. SET_LINE_CODING und SET_CONTROL_LINE_STATE bei CDC-ACM-Geräten (USB-Seriell-Wandler) oder das Umschalten einer LED auf Mikrocontroller-Boards.

Das Objekt setup entspricht dem 8 Byte langen Setup-Paket aus der USB-Spezifikation: requestType, recipient, request, value und index. Die Länge (wLength) berechnet der Browser selbst aus dem optionalen data-Puffer. Ohne data wird ein reiner Befehl ohne Datenphase gesendet.

Vorher muss das Gerät mit open() geöffnet werden. Ist recipient "interface" oder "endpoint", muss die betreffende Schnittstelle zusätzlich mit claimInterface() beansprucht sein. Das zurückgegebene Promise löst mit einem USBOutTransferResult auf. Dessen status ("ok" oder "stall") und bytesWritten sollte man immer auswerten.

Parameter

Name Typ Default Beschreibung
$setup Pflicht USBControlTransferParameters Objekt mit den Setup-Feldern: requestType ("standard" | "class" | "vendor"), recipient ("device" | "interface" | "endpoint" | "other"), request (Zahl, bRequest), value (Zahl, wValue) und index (Zahl, wIndex, z. B. Nummer der Schnittstelle).
$data ArrayBuffer | TypedArray | DataView Optionale Nutzdaten für die Datenphase. Ohne Angabe wird der Transfer mit wLength = 0 gesendet.

Rückgabewert

Typ
Promise<USBOutTransferResult>
Beschreibung
Promise, das mit einem USBOutTransferResult auflöst. Es hat die Eigenschaften bytesWritten (Anzahl gesendeter Bytes) und status ("ok" oder "stall"). Bei ungültigem Zustand oder Übertragungsfehlern wird das Promise mit einer DOMException abgelehnt, z. B. InvalidStateError, NotFoundError oder NetworkError.

Beispiele

Vendor-Befehl ohne Datenphase senden (LED einschalten)

// Muss durch eine Nutzeraktion ausgelöst werden, z. B. einen Klick
const button = document.querySelector('#connect');

button.addEventListener('click', async () => {
  const device = await navigator.usb.requestDevice({
    filters: [{ vendorId: 0x2341 }]
  });
  await device.open();
  if (device.configuration === null) {
    await device.selectConfiguration(1);
  }
  await device.claimInterface(0);

  const result = await device.controlTransferOut({
    requestType: 'vendor',
    recipient: 'interface',
    request: 0x01, // herstellerspezifisch: LED setzen
    value: 0x0001, // 1 = an
    index: 0       // Nummer der Schnittstelle
  });

  console.log(result.status, result.bytesWritten);
});
ok 0

CDC-ACM: Baudrate per SET_LINE_CODING setzen

const setLineCoding = async (device, baudRate = 115200, interfaceNumber = 0) => {
  // 7 Byte: dwDTERate (LE), bCharFormat, bParityType, bDataBits
  const buffer = new ArrayBuffer(7);
  const view = new DataView(buffer);
  view.setUint32(0, baudRate, true);
  view.setUint8(4, 0); // 1 Stoppbit
  view.setUint8(5, 0); // keine Parität
  view.setUint8(6, 8); // 8 Datenbits

  const result = await device.controlTransferOut({
    requestType: 'class',
    recipient: 'interface',
    request: 0x20, // SET_LINE_CODING
    value: 0,
    index: interfaceNumber
  }, buffer);

  if (result.status !== 'ok') {
    throw new Error(`SET_LINE_CODING fehlgeschlagen: ${result.status}`);
  }
  return result.bytesWritten;
};

// Nutzung (Gerät bereits geöffnet, Schnittstelle beansprucht):
// console.log(await setLineCoding(device, 9600));
7

DTR/RTS setzen und Stall-Fehler abfangen

const setControlLines = async (device, { dtr = true, rts = true } = {}) => {
  try {
    const result = await device.controlTransferOut({
      requestType: 'class',
      recipient: 'interface',
      request: 0x22, // SET_CONTROL_LINE_STATE
      value: (dtr ? 1 : 0) | (rts ? 2 : 0),
      index: 0
    });

    if (result.status === 'stall') {
      // Gerät lehnt die Anfrage ab, der Endpunkt 0 wird automatisch zurückgesetzt
      console.warn('Gerät unterstützt die Anfrage nicht');
      return false;
    }
    return true;
  } catch (err) {
    console.error(`${err.name}: ${err.message}`);
    return false;
  }
};

// Wichtig · Fallstricke

  • Browser-Unterstützung: WebUSB gibt es nur in Chromium-Browsern (Chrome, Edge, Opera) auf dem Desktop und in Chrome für Android. Firefox und Safari unterstützen WebUSB nicht. Die Unterstützung vorher mit 'usb' in navigator prüfen.
  • Nur in sicheren Kontexten (HTTPS oder localhost) verfügbar. Sie kann per Permissions-Policy usb gesperrt sein, z. B. in iframes.
  • Das Gerät muss mit open() geöffnet sein, sonst wird das Promise mit InvalidStateError abgelehnt. Bei recipient: "interface" oder "endpoint" muss die Schnittstelle vorher mit claimInterface() beansprucht sein.
  • Ein "stall" ist kein Fehler im Sinne einer Ablehnung des Promise. Man muss result.status also selbst prüfen.
  • Geräte bestimmter geschützter Klassen (z. B. HID, Massenspeicher, Audio, Video) sind für WebUSB gesperrt. Unter Windows braucht das Gerät oft den WinUSB-Treiber.
  • Mehrbyte-Werte in den Nutzdaten sind in der USB-Spezifikation meist Little Endian. Daher bei DataView den Parameter littleEndian = true setzen.
  • Sicherheit: Falsche Vendor-Befehle können die Firmware beschädigen oder das Gerät in einen undefinierten Zustand bringen. Nur dokumentierte Anfragen senden.