npm.io
1.2.0 • Published yesterday

nxtqs-thermal-printer-cordova-plugin

Licence
Apache-2.0
Version
1.2.0
Deps
0
Size
177 kB
Vulns
0
Weekly
0

Cordova Plugin for Thermal Printer's

npm version npm downloads


This plugin is a wrapper for the Android library for ESC/POS Thermal Printer.

Install

Cordova
$ cordova plugin add nxtqs-thermal-printer-cordova-plugin
Ionic
$ ionic cordova plugin add nxtqs-thermal-printer-cordova-plugin
Capacitor
$ npm install nxtqs-thermal-printer-cordova-plugin
$ npx cap sync

Don't forget to add BLUETOOTH and INTERNET (for TCP) permissions and for USB printers the android.hardware.usb.host feature to the AndroidManifest.xml.

<uses-feature android:name="android.hardware.usb.host" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.INTERNET" />
Examples
Notice for TypeScript-Developers

You can easily import and use the ThermalPrinter plugin in your TypeScript-Projects.

import { ThermalPrinterPlugin } from "nxtqs-thermal-printer-cordova-plugin/src";

declare let ThermalPrinter: ThermalPrinterPlugin;

And then use the following examples in your code.

Print via Internal Urovo/Gertec Printer (GPOS820)

Available since v1.1.0 — This printer type uses structured operations (text, QR codes, images, gaps) instead of formatted text strings. It communicates directly with the device's internal PrinterManager API.

Prerequisites:

  • Device must have android.device.PrinterManager available (typically Urovo/Gertec devices with GPOS820 or similar printers)
  • Permissions in AndroidManifest.xml:
    <uses-permission android:name="smartpos.deviceservice.permission.Printer" />
    <uses-permission android:name="android.permission.CLOUDPOS_PRINTER" />

Basic Usage:

ThermalPrinter.listPrinters(
  { type: "internal-urovo" },
  function (printers) {
    if (printers.length > 0) {
      var printer = printers[0];
      ThermalPrinter.printInternalUrovoPage(
        {
          type: "internal-urovo",
          id: printer.id,
          operations: [
            {
              type: "text",
              value: "Hello World!",
              size: "normal",
              align: "center",
            },
            {
              type: "qr",
              value: "https://example.com",
              size: 200,
            },
            {
              type: "gap",
              value: 20, // millimeters
            },
          ],
        },
        function () {
          console.log("Successfully printed!");
        },
        function (error) {
          console.error("Printing error", error);
        },
      );
    }
  },
  function (error) {
    console.error("No printers found", error);
  },
);

Operation Types:

  • text: Print text with specified size and alignment

    • value: string (text to print)
    • size: 'small' | 'normal' | 'title' | 'ticket'
    • align: 'left' | 'center' | 'right'
    • x: number (optional, horizontal offset in pixels)
    • topGap: number (optional, gap before text in pixels)
  • qr: Print QR code

    • value: string (URL or data to encode)
    • size: number (QR code size in pixels)
    • x: number (optional, horizontal offset)
    • topGap: number (optional, gap before QR in pixels)
  • image: Print image

    • value: string (base64 encoded image)
    • x: number (optional, horizontal offset)
    • topGap: number (optional, gap before image in pixels)
  • gap: Add vertical spacing

    • value: number (gap size in pixels)
Print via Bluetooth

Printing via Bluetooth is as easy as possible.

ThermalPrinter.printFormattedText({
    type: 'bluetooth',
    id: 'first', // You can also use the identifier directly i. e. 00:11:22:33:44:55 (address) or name
    text: '[C]<u><font size='big'>Hello World</font></u>' // new lines with "\n"
}, function() {
    console.log('Successfully printed!');
}, function(error) {
    console.error('Printing error', error);
});

Notice: If not working please ensure that you have the printer connected. (Settings -> Bluetooth -> Pairing) If you have other issues maybe you have not granted the android.permission.BLUETOOTH permission.

Print via TCP

Printing via TCP is as easy as possible.

ThermalPrinter.printFormattedText({
    type: 'tcp',
    address: '192.168.1.123',
    port: 9100,
    id: 'tcp-printer-001', // Use an unique identifier for each printer i. e. address:port or name
    text: '[C]<u><font size='big'>Hello World</font></u>' // new lines with "\n"
}, function() {
    console.log('Successfully printed!');
}, function(error) {
    console.error('Printing error', error);
});

Notice: If not working please ensure that your device can ping the printer. And the printer must be a POSPrinter! Also ensure that you're using the correct port. 9100 is default for the thermal printers.

Print via USB (incl. listPrinters and requestPermissions)
  1. First we get our printer because we don't know the printer's ID.
  2. Then we request permissions for printing. This is needed because Android will not allow us to access all devices.
  3. And finally we can print with our device.
ThermalPrinter.listPrinters({type: 'usb'}, function(printers) {
    if (printers.length > 0) {
        var printer = printers[0];
        ThermalPrinter.requestPermissions(printer, function() {
            // Permission granted - We can print!
            ThermalPrinter.printFormattedText({
                type: 'usb',
                id: printer.id,
                text: '[C]<u><font size='big'>Hello World</font></u>' // new lines with "\n"
            }, function() {
                console.log('Successfully printed!');
            }, function(error) {
                console.error('Printing error', error);
            });
        }, function(error) {
            console.error('Permission denied - We can\'t print!');
        });
    } else {
        console.error('No printers found!');
    }
}, function(error) {
    console.error('Ups, we cant list the printers!', error);
});

Reconnection handling (since v1.2.0): USB connections are cached, and the cache is keyed by vendorId/productId/serialNumber, which do not change when a device re-enumerates. The plugin therefore also checks the deviceId of the cached device against the bus before reusing a connection, because deviceId does change on re-enumeration and the open file descriptor dies with the old one. Reusing a stale connection would fail with Error during claim USB interface. The cache is additionally dropped when the device is detached, when a device comes back re-enumerated, and after a failed USB print. This matters on OTG adapters that let the printer leave the bus — see onUsbEvent to react to it from the app.

listPrinters(data, successCallback, errorCallback)

List available printers

Param Type Description
data Object Data object
data.type "bluetooth" | "usb" Type of list: bluetooth or usb
successCallback function Result on success
errorCallback function Result on failure

printFormattedText(data, successCallback, errorCallback)

Print a formatted text and feed paper

See: https://github.com/DantSu/ESCPOS-ThermalPrinter-Android#formatted-text--syntax-guide

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
[data.mmFeedPaper] number Millimeter distance feed paper at the end
[data.dotsFeedPaper] number Distance feed paper at the end
data.text string Formatted text to be printed
successCallback function Result on success
errorCallback function Result on failure

printFormattedTextAndCut(data, successCallback, errorCallback)

Print a formatted text, feed paper and cut the paper

See: https://github.com/DantSu/ESCPOS-ThermalPrinter-Android#formatted-text--syntax-guide

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
[data.mmFeedPaper] number Millimeter distance feed paper at the end
[data.dotsFeedPaper] number Distance feed paper at the end
data.text string Formatted text to be printed
successCallback function Result on success
errorCallback function Result on failure

getEncoding(data, successCallback, errorCallback)

Get the printer encoding when available

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
successCallback function Result on success
errorCallback function Result on failure

disconnectPrinter(data, successCallback, errorCallback)

Close the connection with the printer

For USB, this closes existing cached connections only; it never opens a new connection. It remains available while a print is blocked, and succeeds if there is nothing left to close. A type/id selector also finds a connection cached under stable USB identifiers. Other printers are preserved.

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
successCallback function Result on success
errorCallback function Result on failure

requestPermissions(data, successCallback, errorCallback)

Request permissions for USB printers

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
successCallback function Result on success
errorCallback function Result on failure

getPrinterStatus(data, successCallback, errorCallback)

Experimental, since v1.2.0 — USB only. Queries the USB printer class GET_PORT_STATUS (paper), then ESC/POS DLE EOT 4 (paper, near end) and DLE EOT 2 (cover). Pass the same USB selector as printing (type, id, optional vendorId, productId, serialNumber); request USB permission beforehand.

ThermalPrinter.getPrinterStatus(
  { type: 'usb', id: printer.deviceId, vendorId: printer.vendorId, productId: printer.productId },
  function (status) {
    console.log('USB printer status:', JSON.stringify(status));
    if (status.paperPresent === false) console.log('Paper is absent');
    if (status.coverOpen === true) console.log('Cover is open');
    // null means unknown. Never block ticket issuance because a query did not answer.
  },
  function (error) { console.error('Status request failed:', error); }
);
Result Meaning
paperPresent true / false / null (unknown)
paperNearEnd true / false / null; requires the corresponding sensor
coverOpen true / false / null; requires the model to implement the standard cover bit
printerStopped true / false / null; class error bit. true means the printer stopped without naming a cause, so treat it as blocking and do not report a specific cause to the operator
supported true: at least one valid reply; false: unsupported transport or missing bulk endpoints; null: undetermined
raw { paper: number[], offline: number[], port: number[] }, unsigned response bytes, including invalid replies; [] when none read
reason null for a complete standard reply; otherwise one of the reasons below

Reasons: busy, device_not_found, permission_required, unsupported_transport, no_status_endpoint, interface_unavailable, input_not_quiet, timeout, write_timeout, invalid_response, io_error, partial. These are successful API responses, not print errors. partial means the class request answered but DLE EOT did not, so paper is known while cover and near end are null. write_timeout means the printer stopped draining its bulk OUT pipe: its receive buffer is full, which is itself a sign that it is offline. Malformed arguments use the error callback. Partial results are possible: paper may be known while cover is null. A timeout cannot establish whether the printer supports the command.

Printing, getEncoding and bitmapToHexadecimalString are serialized against status queries because they open USB writers. They wait at most 5000 ms for the lock, then use the error callback with { error: "USB printer is busy", type: "PRINT_ERROR" }. Discovery, permissions and diagnostics do not open writers. USB disconnect only closes cached connections and stays outside the lock so recovery can close a blocked native write. Permission requests do not create cache entries. A query returns busy immediately if a writer action holds the lock or is waiting. Otherwise it releases cached printing connections for the selected device, opens a temporary connection, claims the same printer interface with force=false, retrying and forcing only on the last attempt, and always closes it before allowing another operation. The next print reconnects normally. This keeps library version 3.6.0 and avoids access to its private fields. A print arriving during the query can wait for its bounded USB transfers: 400 ms per query, with a 1500 ms total budget (Android open/claim/close and scheduling overhead are additional). A reply is polled every 5 ms, because an IN transfer with nothing pending returns 0 at once rather than waiting out its timeout. A model that accepts DLE EOT and never answers is remembered by vendor/product and skipped on later queries, so it costs the read budget only once. Avoid tight polling. The existing detach, re-enumeration and print-failure recovery remains in place.

Old input is drained with a bounded loop. Replies must contain exactly one byte matching 0xx1xx10b; bit 4 is 1, as specified in the Epson DLE EOT reference. Paper uses masks 0x60 and 0x0C; mixed sensor bit pairs remain unknown. Cover uses 0x04 from the offline response. After a missing/invalid paper response, the cover command is skipped to avoid interpreting a late paper reply as cover status. Responses have no command identifiers; model-specific delayed/unsolicited data still requires bench validation.

Validated models. EPSON TM-T20X (vendor 1208, product 3623) on Samsung SM-X236B, 2026-09-22.

GET_PORT_STATUS answers in under 2 ms in every state. Measured bytes: 0x18 ready, 0x10 cover open with paper loaded, 0x30 out of paper with the cover closed. Bit 3 (Not Error) separates ready from stopped in all three, which is what printerStopped reports; bit 5 separates out-of-paper from the other causes.

DLE EOT works on this model only while it is ready, where it replies 0x12 to both commands in about 6 ms and fills coverOpen and paperNearEnd with reason: null. The moment the printer stops it goes silent, contrary to the ESC/POS promise that real-time commands are answered offline. So the fields that would name the cause disappear exactly when a cause exists: in practice cover open surfaces as printerStopped, not as coverOpen, and the class byte is the only source that survives a fault. Once the receive buffer fills, the DLE EOT write itself fails (write_timeout) while the class request keeps answering.

Because of this, silence is only taken as proof that a model never answers DLE EOT when it comes from a printer reporting ready; silence from a stopped printer says nothing about the model and must not disable the command. A valid reply does not prove that every sensor exists.

paperNearEnd is unusable on this model. With a roll holding exactly one more ticket, the paper byte still read 0x12 — near-end bits clear, "paper adequate" — and one second after that ticket printed the class byte went straight to 0x30, out of paper. The model reports no intermediate state, so the field never warns in time to change the roll, which is its only purpose. Treat it as absent here and do not read paperNearEnd: false as evidence that a near-end sensor exists.

Note for bench work: the red stripe printed near the core of a thermal roll is a visual cue for the operator. Nothing in the paper path can read it, and it has no relation to paperNearEnd, which measures the remaining roll diameter through a separate sensor when the model has one. Do not assume paperNearEnd: false proves a near-end sensor is installed. A snapshot is not confirmation that a ticket printed, and some models retain the prior paper state while the cover is open. Bluetooth, TCP and internal Urovo/Gertec status are outside this API's scope.

USB status bench validation

Install this sandbox checkout in the test application, sync/rebuild Android, and call the API after USB permission is granted. For every model/firmware, record raw, the decoded fields, supported, reason, elapsed time and the observed physical condition for every row:

Scenario Expected observation
Paper loaded, cover closed paperPresent: true, coverOpen: false
Paper removed, cover closed paperPresent: false
Paper near end, if sensor exists paperNearEnd: true; otherwise mark sensor unavailable
Cover open, with and without paper coverOpen: true, or printerStopped: true on models that go silent while stopped; record whether paper state is retained
Printer powered off / cable removed Unknown result, no indefinite wait or blocked ticket issuance
Query during a long print busy, no interruption, truncation or duplicated ticket
Print immediately after starting a query Print resumes after the bounded query, including timeout
Silent/unsupported printer or no input endpoint Unknown/unsupported, no false paper/cover alarm
Repeated queries, including after a timeout No old paper byte interpreted as cover; print still works
Same printer addressed by legacy id and stable ids Cached aliases released, no claim conflict
Unstable cable, detach/replug and re-enumeration Normal printing and the app's existing automatic reprint recover as before
Two USB printers / unrelated hub attach Query leaves the other device's cached connection intact

Record approved models and their reliable bits here only after this procedure is completed. Every query logs its outcome unconditionally under the ThermalPrinter tag: [status] port read=…, [status] n=… read=… after … polls, and the full snapshot. Filter logcat with ThermalPrinter:I ThermalPrinterUsbDiag:I Capacitor/Console:V '*:S'. The wider ThermalPrinterUsbDiag event trace is still gated by USB_DIAG_VERBOSE.

Before each scenario, power-cycle the printer. A printer left offline keeps a full receive buffer and will fail the DLE EOT write of the next run, which invalidates the reading.

getUsbDiagnostics(successCallback, errorCallback)

Available since v1.2.0 — Returns a snapshot of the USB subsystem: devices reported by UsbManager (with interfaces, endpoints and permission state), the plugin's connection cache, the sticky USB_STATE extras, the current power/battery state, the most recent USB, power and screen events, and whether the plugin's USB/power receiver is registered (receiverRegistered).

Useful when diagnosing field issues with OTG adapters, where the printer may leave the bus entirely. TypeScript consumers can import the returned shape as UsbDiagnostics.

Param Type Description
successCallback function Result on success
errorCallback function Result on failure

Returned object:

Field Type Description
timestamp, uptimeMs number Wall clock and SystemClock.elapsedRealtime() of the snapshot
device string Manufacturer, model, SDK level and build
receiverRegistered boolean false means the cache is only cleared on detach and on print failure
eventListenerAttached boolean Whether onUsbEvent currently has a subscriber
verboseLogging boolean Whether the per-print debug logging is compiled in
power object plugged, pluggedLabel, status, levelPercent, voltageMv
usbState object Sticky USB_STATE extras, stringified
usbDevices Array Devices on the bus, each with deviceId, ids, interfaces and endpoints
connectionCache Array The plugin's cached connections, with the deviceId each one was opened against
recentEvents Array Up to the last 200 USB, power and screen events

onUsbEvent(eventCallback, errorCallback)

Available since v1.2.0 — Subscribes to USB attach/detach, USB_STATE, power and screen events. The callback is kept and invoked for every event, each one carrying the action, the battery/power state and the number of USB devices currently on the bus. Attach and detach events also carry the device descriptor. TypeScript consumers can import the event shape as UsbEvent.

Typical use: react to android.hardware.usb.action.USB_DEVICE_ATTACHED to retry a print as soon as a printer that dropped off the bus comes back. By the time the event reaches the callback, the plugin has already dropped any cached connection whose device came back re-enumerated, so a print started from the callback opens a fresh connection.

Subscribe once: calling it again replaces the previous subscription and releases the earlier callback.

Param Type Description
eventCallback function Called for every event
errorCallback function Result on failure

bitmapToHexadecimalString(data, successCallback, errorCallback)

Convert Drawable instance to a hexadecimal string of the image data

Param Type Description
data Array.<Object> Data object
data.type "bluetooth" | "tcp" | "usb" List all bluetooth or usb printers
[data.id] string | number ID of printer to find (Bluetooth: address, TCP: Use address + port instead, USB: deviceId)
[data.address] string If type is "tcp" then the IP Address of the printer
[data.port] number If type is "tcp" then the Port of the printer
data.base64 string Base64 encoded picture string to convert
successCallback function Result on success
errorCallback function Result on failure

Keywords