paint-brush
Exploring the WebUSB API: Connecting to USB Devices and Printing With TSPL/TSPL2by@altynberg
10,688 reads
10,688 reads

Exploring the WebUSB API: Connecting to USB Devices and Printing With TSPL/TSPL2

by Altynbek UsenbekovApril 4th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The WebUSB API provides classes and methods that allow us to connect devices and send or receive data via USB on web browsers. The supported browsers are Google Chrome, Microsoft Edge, Opera, Samsung Internet, and Baidu Browser. The API can only be used in secure contexts.
featured image - Exploring the WebUSB API: Connecting to USB Devices and Printing With TSPL/TSPL2
Altynbek Usenbekov HackerNoon profile picture

What Is the WebUSB API?

The WebUSB API provides classes and methods that allow us to connect devices and send or receive data via USB on web browsers.

Quick Overview of How to Use WebUSB

We use the USB.requestDevice() method to display the pairing dialog to the user. After the user selects a USB device from the dialog, it returns the selected device as an USBDevice instance.


We can later send or receive data from this device. To get a list of the already paired devices, we can use the USB.getDevices() method.

WebUSB Supported Browsers

Not all browsers might support the WebUSB API. At the moment, the supported browsers are Google Chrome, Microsoft Edge, Opera, Samsung Internet, and Baidu Browser. You can check which browsers support WebUSB.


One way to check if the browser supports WebUSB with JavaScript is:

if (!('usb' in navigator)) {
	console.log('WebUSB API is not supported!');
}


WebUSB API Is Only Available in Secure Contexts (HTTPS)

The WebUSB API can only be used in secure contexts, in other words, it must be served over https:// or wss:// URLs.


However, you can still test it on localhost because, according to MDN Web Docs, resources that are local are considered to have been delivered securely:


Locally-delivered resources such as those with http://127.0.0.1 URLs, http://localhost and http://*.localhost URLs (e.g. http://dev.whatever.localhost/), and file:// URLs are also considered to have been delivered securely.


Enable or Disable WebUSB API

If you want to disable the WebUSB API, you can add an HTTP header Permissions-Policy: usb=(), and use the allow attribute for iframes. Here is an example for Node.js:


res.setHeader('Permissions-Policy', 'usb=()');


If you try to use the WebUSB API after disabling it, you will get an error: Permissions policy violation: usb is not allowed in this document. Check out MDN Web Docs - Permissions Policy for more details.

WebUSB API With an Example

Let's explore how we can use the WebUSB API with step-by-step examples.

Pairing and Connecting to a USB Device Using WebUSB

We can use the USB.requestDevice() method to pair and gain access to the USB device. This method opens a dialog in the browser where we can select the USB device we want to connect to. Upon success, it returns an instance of USBDevice.


If no device is selected, it throws an exception: Failed to execute 'requestDevice' on 'USB': No device selected.


This method can only be called with user gestures like clicks. Otherwise, it throws an exception: Failed to execute 'requestDevice' on 'USB': Must be handling a user gesture to show a permission request.


To reset permission to the USB device, we can use device.forget().


Let's write some code. Add a simple button that calls the USB.requestDevice() method on click:

<button onclick="requestDevice()">Request device</button>
…
async function requestDevice() {
  try {
    const device = await navigator.usb.requestDevice({ filters: [] });
    console.log(device);
  } catch (e) {
    console.error(e);
  }
}


The filters parameter is used to specify a list of objects for possible devices we would like to pair. If left empty, it returns all USB devices. Filter objects can have properties like vendorId, productId, classCode, subclassCode, protocolCode, and serialNumber.


For example, if we only want to get a list of Apple devices, the code should look like this:

navigator.usb.requestDevice({ filters: [{ vendorId: 0x05ac }] });


Here is the list of USB ID’s.

Getting the List of Already Paired Devices

Once we pair and get permission, we can get all paired devices using the USB.getDevices() method. We only need to use the USB.requestDevice() method once to get permission, and thereafter we can get an array of paired devices with USB.getDevices().

async function getDevices() {
  const devices = await navigator.usb.getDevices();
  devices.forEach((device) => {
    console.log(`Name: ${device.productName}, Serial: ${device.serialNumber}`);
  });
  return devices;
}


Sending Data to a USB Device Using WebUSB API

Now that we have permission to use the USB device and can get the paired device list, let's see how we can send data to it.


Here's a simple code that sends data to a connected device:

async function transferOutTest(device) {
  await device.open();
  await device.selectConfiguration(1);
  await device.claimInterface(0);
  await device.transferOut(
    2,
    new Uint8Array(
      new TextEncoder().encode('Test value\n')
    ),
  );
  await device.close();
}


  • open() starts the device session.


  • selectConfiguration() selects device-specific configuration.


  • claimInterface() claims the device-specific interface for exclusive access.


  • transferOut() sends data to the USB device.


  • close() releases interfaces and ends the device session.


Example of Printing Labels Using WebUSB API

I created a simple example of how to print labels with TSPL and WebUSB API. You can get it from GitHub, but here is a shorter version:

<button onclick="requestDevice()">Request device</button><br><br>
<button id="device"></button>

<script>
async function requestDevice() {
  try {
    const device = await navigator.usb.requestDevice({ filters: [] });
    const elem = document.querySelector('#device');
    elem.textContent = `Print with '${device.productName}'`;
    elem.onclick = () => testPrint(device);
  } catch (e) {
    console.error(e);
  }
}

async function testPrint(device) {
  const cmds = [
    'SIZE 48 mm,25 mm',
    'CLS',
    'TEXT 30,10,"4",0,1,1,"HackerNoon"',
    'TEXT 30,50,"2",0,1,1,"WebUSB API"',
    'BARCODE 30,80,"128",70,1,0,2,2,"altospos.com"',
    'PRINT 1',
    'END',
  ];
  
  await device.open();
  await device.selectConfiguration(1);
  await device.claimInterface(0);
  await device.transferOut(
    device.configuration.interfaces[0].alternate.endpoints.find(obj => obj.direction === 'out').endpointNumber,
    new Uint8Array(
      new TextEncoder().encode(cmds.join('\r\n'))
    ),
  );
  await device.close();
}
</script>


And the result looks like this:

Label printed using WebUSB API


If you're interested in learning more about TSPL/TSPL2, I have written a few articles that you might find helpful. They cover topics like how to print labels and images, as well as how to print receipts using TSPL and JavaScript.


I wrote these articles after adding printing support to Alto's POS & Inventory, hoping that these would be useful to someone in a similar situation.



Some useful tools:

  • chrome://device-log/ - Use this to get device details and create a test device.
  • chrome://device-log/ - This shows logs related to devices.
  • Zadig - Generic USB drivers for Windows - You may need to install a driver on Windows.


No War! ✋🏽