Zum Inhalt springen

Einen Etikettendrucker per WebUSB ansteuern

Veröffentlicht am

Ich wollte Etiketten mit QR-Code direkt aus der Weboberfläche drucken, ohne Druckdialog, ohne CUPS und ohne Druckserver. Der Browser schickt die Druckbefehle dazu über WebUSB roh an den Drucker. Vor ein paar Jahren hätte das noch einen Umweg über lpr gebraucht und einen Dienst, den jemand am Laufen hält.

Der Drucker

Es ist ein TOSHIBA B-FV4T-TS14-QM-R, ein Desktop-Etikettendrucker aus der B-FV4-Serie.

Der Drucker von vorn, mit der Etikettenbahn im Auswurf

Merkmal Was dahintersteckt
Druckverfahren Thermotransfer (mit Farbband) und Thermodirekt (ohne). Das T in B-FV4T steht für Thermotransfer, die B-FV4D kann nur Thermodirekt.
Auflösung TS14 = 300 dpi, also rund 12 Punkte je Millimeter. Die GS14-Variante hat 203 dpi.
Druckbreite rund 105,7 mm. „4 Zoll" ist nur der Name der Baugröße, 4 × 25,4 wären 101,6 mm.
Schnittstellen USB 2.0, Ethernet, seriell
Befehlssprache TPCL, dazu Emulationen für ZPL2, EPL, DPL und IPL

Bevor überhaupt etwas ankommt, müssen im B-FV Setting Tool zwei Einstellungen stimmen: Befehlssprache TPCL und Auflösung 300 DPI. Steht das Gerät auf einer Emulation, nimmt es die Befehle ohne Meldung an und druckt nichts Brauchbares.

Das B-FV Setting Tool: Befehlssprache TPCL, 300 DPI, Forward Feed Wait auf OFF

Im selben Tool steht auch der USB-Gerätepfad mit vid_08a6&pid_b000. Das sind Vendor- und Produkt-ID, nach denen du später filtern kannst.

Die Druckdaten

Der Drucker bekommt weder Bilder noch PDF. Er bekommt Textbefehle in TPCL, eingepackt in STX (0x02) und ETX (0x03). Diese Funktion baut ein Etikett mit QR-Code:

JavaScriptDownload
function qrLabel(url, anzahl = 1) {
    const STX = '\x02';
    const ETX = '\x03';

    return (
        STX +
        '{D0295,0295,0295|}\n' +
        '{C|}\n' +
        `{XB01;0051,0051,T,M,06,A,0,M2=${url}|}\n` +
        `{XS;I,${String(anzahl).padStart(4, '0')},0002D4000|}\n` +
        ETX +
        '\n'
    );
}

Ein 25-mm-Etikett mit einem QR-Code, der auf www.prefabcortex.com zeigt

Das Ergebnis ist eine gewöhnliche Zeichenkette. Die Seite baut sie selbst zusammen, ein Server ist am Druck nicht beteiligt.

Der Knopf über dem Block lädt das, was qrLabel('https://www.prefabcortex.com') zurückgibt, also 111 Byte. STX und ETX sind darin echt enthalten. Aus einem Codeblock mit fertigen Befehlen kopierst du die beiden unsichtbaren Zeichen nämlich nicht mit, und dann druckt der Drucker einfach nichts. Es ist dieselbe Datei wie bei Beispiel 2 im TPCL-Artikel.

Braucht der Drucker einen Treiber?

Nein. WebUSB spricht das Gerät direkt an, ohne Druckertreiber, Druckdialog, CUPS oder Druckserver. Die Seite erzeugt den Befehlstext, der Browser schiebt ihn über USB hinaus, der Drucker führt ihn aus. Unter Windows muss der Druckertreiber sogar weg, dazu unten mehr.

Läuft das in Firefox oder Safari?

Nein. WebUSB gibt es nur in Chromium-Browsern (Chrome, Edge, Opera, Brave) und nur in einem sicheren Kontext, also über https:// oder localhost. Mozilla und Apple haben beide öffentlich erklärt, dass sie WebUSB nicht umsetzen wollen. Ihnen ist der direkte Zugriff einer Webseite auf USB-Geräte zu weitgehend. Darauf zu warten, dass sich das ändert, lohnt also nicht.

Auch Chrome für Android kann WebUSB.

Der Code

Den Drucker einmal auswählen

Der Geräte-Dialog geht nur nach einem Klick auf:

JavaScript
async function selectPrinter() {
    try {
        // filters: [] zeigt alle USB-Geräte, filters: [{ vendorId: 0x08a6 }] nur den Toshiba
        const device = await navigator.usb.requestDevice({ filters: [] });

        localStorage.setItem('usbVendorId', device.vendorId);
        localStorage.setItem('usbProductId', device.productId);
    } catch (err) {
        // NotFoundError = Dialog ohne Auswahl geschlossen
        if (err.name !== 'NotFoundError') {
            alert('Fehler beim Wählen des Druckers: ' + err.message);
        }
    }
}

Anders als Web Serial gibt WebUSB auch den Produktnamen heraus (device.productName). Das Gerät lässt sich damit auch ohne gespeicherte IDs wiederfinden.

Drucken

JavaScript
async function printViaUsb(content) {
    const devices = await navigator.usb.getDevices(); // nur freigegebene Geräte

    const device = devices.find(
        (d) =>
            d.vendorId === parseInt(localStorage.getItem('usbVendorId')) &&
            d.productId === parseInt(localStorage.getItem('usbProductId')),
    );

    if (!device) {
        throw new Error('Kein Drucker eingerichtet');
    }

    await device.open();

    try {
        await device.selectConfiguration(1);
        await device.claimInterface(0);
        // TextEncoder macht aus dem Text mit STX und ETX die Bytes für den Drucker
        await device.transferOut(1, new TextEncoder().encode(content));
    } finally {
        await device.close(); // sonst kommt der nächste Druck nicht heran
    }
}

Endpoint 1 und Interface 0 stehen hier fest drin. Bei einem anderen Drucker kann das anders sein. Sauberer ist es, den OUT-Endpoint nachzuschlagen:

JavaScript
const alt = device.configuration.interfaces[0].alternate;
const out = alt.endpoints.find(
    (e) => e.direction === 'out' && e.type === 'bulk',
);
await device.transferOut(out.endpointNumber, data);

Beides zusammen

Der Knopf in der Oberfläche baut das Etikett und schickt es an den Drucker:

JavaScript
await printViaUsb(qrLabel('https://www.prefabcortex.com'));

„Access denied" bei claimInterface

Das ist der häufigste Abbruch, und am Code liegt er nicht. Unter Windows hält der installierte Druckertreiber das USB-Interface exklusiv fest. device.claimInterface(0) scheitert dann mit NetworkError: Access denied oder The device is protected. Unter Linux passiert dasselbe, wenn der Kernel schon ein Treibermodul an das Gerät gehängt hat.

Helfen tut nur, das Gerät an einen generischen USB-Treiber zu binden statt an den Druckertreiber. Unter Windows ist das WinUSB, meistens mit Zadig installiert. Danach ist der Drucker für Windows kein Drucker mehr. Über den Druckdialog geht dann nichts mehr, über WebUSB alles. Das solltest du also bewusst für dieses Gerät entscheiden.

Wer den Drucker weiter aus Word heraus benutzen will, kann WebUSB deshalb nicht nehmen. Dann bleibt nur der Weg über den Treiber und einen Druckserver.

Stolperfallen

  • Falsches Gerät ausgewählt: Mit filters: [] stehen auch Maus, Tastatur und Scanner in der Liste.
  • Drucker aus oder abgezogen: open() schlägt fehl, die Freigabe bleibt aber bestehen. Nach dem Wiedereinstecken geht es ohne neuen Dialog weiter.
  • Der Aufkleber kommt nicht heraus: Der Auftrag ist angekommen, das Gerät blinkt nur, und erst die Feed-Taste schiebt das Etikett heraus. Forward Feed Wait auf OFF hat bei mir nicht gereicht. Als Nächstes würde ich den Spender-Modus probieren, bei dem das Etikett gleich vom Trägerpapier abgezogen wird.
  • Anzahl begrenzen: Ein Tippfehler im Mengenfeld soll keine ganze Rolle leer drucken.

Genannte Produkt-, Firmen- und Markennamen gehören ihren jeweiligen Inhabern und werden hier allein beschreibend verwendet. Eine Verbindung zu ihnen oder eine Empfehlung durch sie besteht nicht.