Signatur
Beschreibung
transferOut() ist die zentrale Methode, um Nutzdaten vom Browser an ein USB-Gerät zu schicken, z. B. Befehle an einen Mikrocontroller, Druckdaten an einen Belegdrucker oder Pakete an ein eigenes HID-ähnliches Gerät. Die Methode arbeitet mit Bulk- und Interrupt-Endpunkten in Richtung OUT (Host → Gerät). Ob es ein Bulk- oder Interrupt-Transfer wird, ergibt sich aus dem Typ des Endpunkts. Das Gerät legt diesen Typ selbst in seinem Deskriptor fest.
Bevor Sie transferOut() aufrufen, müssen Sie das Gerät mit open() öffnen, mit selectConfiguration() eine Konfiguration wählen und das Interface, zu dem der Endpunkt gehört, mit claimInterface() beanspruchen. Die Endpunktnummer ist die reine Nummer (1–15) ohne das Richtungsbit. Welche Endpunkte es gibt, sehen Sie unter device.configuration.interfaces[n].alternate.endpoints.
Das Promise wird mit einem USBOutTransferResult erfüllt. Dessen Eigenschaft bytesWritten gibt an, wie viele Bytes tatsächlich übertragen wurden. status ist entweder "ok" oder "stall". Bei "stall" hat das Gerät den Endpunkt angehalten. Dann setzen Sie ihn mit clearHalt("out", endpointNumber) zurück, bevor Sie erneut senden.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $endpointNumber Pflicht | number | Nummer des OUT-Endpunkts (1–15, ohne Richtungsbit 0x80). Der Endpunkt muss zu einem beanspruchten Interface gehören. |
|
| $data Pflicht | BufferSource (ArrayBuffer | TypedArray | DataView) | Die zu sendenden Bytes, z. B. ein Uint8Array oder das Ergebnis von TextEncoder.encode(). |
Rückgabewert
USBOutTransferResult erfüllt wird. Es enthält bytesWritten (Anzahl gesendeter Bytes) und status ("ok" oder "stall"). Bei ungültigem Zustand oder Endpunkt, Übertragungsfehler oder getrenntem Gerät wird es mit einer DOMException abgelehnt (z. B. InvalidStateError, NotFoundError, NetworkError).Beispiele
Gerät vorbereiten und einen Befehl senden
// Muss durch eine Nutzeraktion (z. B. Klick) ausgelöst werden
const button = document.querySelector('#connect');
button.addEventListener('click', async () => {
const device = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x2341 }] // z. B. Arduino
});
await device.open();
if (device.configuration === null) {
await device.selectConfiguration(1);
}
await device.claimInterface(0);
const command = new TextEncoder().encode('LED ON\n');
const result = await device.transferOut(2, command);
console.log(`Status: ${result.status}, gesendet: ${result.bytesWritten} Bytes`);
});
OUT-Endpunkt automatisch ermitteln
const findOutEndpoint = (device, interfaceNumber = 0) => {
const iface = device.configuration.interfaces
.find((i) => i.interfaceNumber === interfaceNumber);
const ep = iface.alternate.endpoints
.find((e) => e.direction === 'out' && (e.type === 'bulk' || e.type === 'interrupt'));
if (!ep) throw new Error('Kein passender OUT-Endpunkt gefunden');
return ep.endpointNumber;
};
const sendBytes = async (device, bytes) => {
const endpoint = findOutEndpoint(device);
return device.transferOut(endpoint, new Uint8Array(bytes));
};
// Nutzung (Gerät bereits geöffnet und Interface beansprucht):
// const res = await sendBytes(device, [0x01, 0x02, 0x03]);
// console.log(res.bytesWritten); // 3
Stall behandeln und erneut senden
const safeTransferOut = async (device, endpoint, data) => {
let result = await device.transferOut(endpoint, data);
if (result.status === 'stall') {
console.warn('Endpunkt angehalten – setze zurück …');
await device.clearHalt('out', endpoint);
result = await device.transferOut(endpoint, data);
}
if (result.bytesWritten < data.byteLength) {
console.warn(`Nur ${result.bytesWritten} von ${data.byteLength} Bytes gesendet`);
}
return result;
};
// const res = await safeTransferOut(device, 1, new Uint8Array(64));
Große Daten in Blöcken senden
// Beispiel: Druckdaten an einen Belegdrucker (Bulk-Endpunkt)
const sendInChunks = async (device, endpoint, data, chunkSize = 4096) => {
let total = 0;
for (let offset = 0; offset < data.byteLength; offset += chunkSize) {
const chunk = data.subarray(offset, offset + chunkSize);
const { bytesWritten, status } = await device.transferOut(endpoint, chunk);
if (status !== 'ok') throw new Error(`Transfer fehlgeschlagen: ${status}`);
total += bytesWritten;
}
return total;
};
// const sent = await sendInChunks(device, 1, new Uint8Array(10000));
// console.log(sent); // 10000
// Wichtig · Fallstricke
- Browser-Unterstützung: WebUSB gibt es nur in Chromium-basierten Browsern (Chrome, Edge, Opera, auch Android). Firefox und Safari unterstützen die API nicht. Prüfen Sie vorher mit
'usb' in navigator. - Sicherer Kontext: WebUSB funktioniert nur über HTTPS (oder
localhost). Die Freigabe des Geräts perrequestDevice()muss durch eine Nutzeraktion ausgelöst werden. Mit der Permissions Policyusblässt sich die API in iframes sperren. - Voraussetzungen: Ohne
open(), gewählte Konfiguration undclaimInterface()wird das Promise mitInvalidStateErrorbzw.NotFoundErrorabgelehnt. Wenn ein Betriebssystemtreiber das Interface schon belegt (z. B. HID, Massenspeicher, unter Windows ohne WinUSB), schlägtclaimInterface()fehl. - Endpunktnummer: Geben Sie nur die Nummer an, also
2statt0x02/0x82. Für IN-Richtung nutzen SietransferIn(), für isochrone EndpunkteisochronousTransferOut()und für Control-TransferscontrolTransferOut(). - Teilübertragungen: Prüfen Sie immer
bytesWrittenundstatus. Ein erfülltes Promise bedeutet nicht automatisch, dass alle Daten angekommen sind. - Parallele Transfers: Mehrere gleichzeitige
transferOut()-Aufrufe auf demselben Endpunkt werden zwar in eine Warteschlange gestellt. Für eine klare Reihenfolge sollten Sie trotzdem nacheinander mitawaitsenden. - Blockierte Geräteklassen: Einige Geräte und Klassen (z. B. Tastaturen, Sicherheitsschlüssel) sperrt der Browser aus Sicherheitsgründen für WebUSB.