Celebrating

10

years

of expertise

in software

development

Latest news

We wish you a pleasant reading

Webassembly, Qt and USB

Introduction

C++ and Qt library are great. They really are. However, I hate the fact that C++ doesn't have a standardized package manager. Deploying applications on different platforms never goes seamless. Managing dependencies can be problematic and it takes time. A neat idea is to use Webassembly, compile our application, and happily use it on different devices, inside the browser which supports it. Simply every device with Chrome browser.

From a developer perspective, WebAssembly is a bridge which connects a web browser with a native part written in languages such as C++ or Rust. An application written in C++ is compiled to Webassembly format (using provided emscripten tools) and can be loaded within the browser. There are similar solutions, but they always needed additional plugins to be installed (e.g. Unity3d Web Player or even Java applets), which was always discouraging for me. I guess I'm not the only one.

Limitations

What are the limits of this Qt-Web relationship? In general - only limits imposed by the browser. Yey, that means that we can also use webusbapi without any problems, right? No. As always, in real-life scenarios, there are always some obstacles. Some of them are impossible to solve, others need some workarounds. In our case, webusbapi currently works only on the Chrome browser. Fortunately, it doesn't require an additional flag set (chrome://flags/), which is needed to use a bluetooth API. Also, as a safety feature, it needs a user gesture like a mouse click to start communication with a USB device. There is no support in Webassembly itself to achieve it, so we need to improvise with Java Script to get what we want.

There are other constraints within USB API. First of all, the device should support this API to be visible. Most common gadgets, like external hard drives mouses or keyboards, will not be. I tried several, and only my mobile phone worked (Motorola).

Although there is a method to list all available USB devices connected to the device (navigator.usb.getDevices()), I couldn't use it on my setup. No matter how much I tried, the empty list of devices was returned. This feature is still experimental and hopefully, it will be fixed and fully operational in the future. Another way is to get a device through a specified Vendor ID that we should know upfront. I guess it can be considered as another limit. Thankfully, it is very easy to get that identifier. On Linux there is one simple command:

lsusb -v

On Windows, this id can be found in Device Manager. Just find your device in it, under properties select "Hardware Ids". VendorID can be found under VID

Setting up environment

You can get my working example from here: SourceCode .

I used Emscripten 1.38.27 and Qt 5.14. More information here:

https://wiki.qt.io/Qt_for_WebAssembly

Compiling & running

Emscripten environment variables should already be set up at this point (by running emsdk_env script). Compiling projects in Webassembly does not differ from any other platform. Simply run qmake in a folder with pro file and then run make (or nmake on Windows).

cd to-the-project-directory
path-to-wasm-qt/bin/qmake
make # or nmake on Windows

To run it, we can use Emscripten tool emrun:

emrun webusbtest.html

The application should start, just remember to set Chrome as your default browser.

Emrun is convenient, but at this point, the application will run on an HTTP server, even a simple one provided by python. To try it, simply run it in the project directory:

python -m SimpleHTTPServer 8000

You should see this in your browser:

Provide your device Vendor id, click the button to connect. Choose your device in popup window:

 If everything works, you should see some device information.

Troubleshooting

Sometimes the device refuses to connect. Of course, it depends on how it is implemented so I will present some obstacles which emerged when I was connecting my phone. It wasn’t visible to the browser unless I turned on USB debugging and switched my connection state to ‘transfer files’. When a different process was using my device, the connection was failing with the ‘Access denied’ message.

How does it work

1. JS <-> C++ communication

Let's start with some basic building blocks. We need to have a way to exchange data between C++ backend and Javascript. Webassembly comes to the rescue. With just a tiny wrapper, we can have fully functional bi-directional data transfer. Take a look at these functions definitions:

#ifdef __cplusplus
extern "C" {
#endif

void EMSCRIPTEN_KEEPALIVE stringCallback(const char* txt) {
    model->addItem(txt);
}

const char* EMSCRIPTEN_KEEPALIVE usbvendor() {
    QByteArray ba = lineEdit->text().toLocal8Bit();
    return ba.data();
}

#ifdef __cplusplus
}
#endif

Some explanations:

  • EMSCRIPTEN_KEEPALIVE - this macro exports function and prevents inlining so we can be sure our function will appear as a unique function in JavaScript
  • stringCallback - this function will be called from Javascript, takes c-style literal as input. This type is not directly available in js, but we can easily obtain it using emscripten helpers.
  • usbVendor - another way around, this function provides data to the js, it also should be converted to Javascript literal type.

Mentioned functions can be invoked directly from javascript, thanks to emscripten helpers. They are available inside the Module object with underscore prefix - Module._stringCallback & Module._usbVendor.

Time to look at the JS part. Here is a convenient wrapper, which converts a string to a pointer and passes it into C++ backend.

function passString(txt) {
  var ptr  = allocate(intArrayFromString(txt), 'i8', ALLOC_NORMAL);
  Module._stringCallback(ptr);
  _free(ptr);
}   

passString("Hello world");

Notice that memory is allocated and after passing a block to a function, it should be released.

Converting char* type into js string is much simpler, UTF8ToString function does the trick.

var txt = UTF8ToString(Module._usbvendor());
console.log(txt);

2. Connecting to the device

Connecting to a device, namely navigator.usb.requestDevice method requires human gesture and web assembly doesn't support it. The solution to this predicament is to insert javascript button in the DOM structure. In the following, the snippet button is created precisely before the first body element.

var button = document.createElement('button');
button.innerHTML = 'Connect webusb device';
var body = document.getElementsByTagName('body')[0];
body.insertBefore(button, body.firstChild);
button.addEventListener ('click', function() {
    if (navigator.usb) {
        talkToDevice(UTF8ToString(Module._usbvendor()));
    } else {
        alert('WebUSB not supported.');
    }
});

We are getting close to the bottom. Following function talkToDevice function looks like this:

async function talkToDevice(Vendor) {
try {
    let device = await navigator.usb.requestDevice({ filters: [{ vendorId: Vendor }] });
    await device.open();
    await device.selectConfiguration(1);
    await device.claimInterface(1);
    let status = await device.controlTransferOut({
      requestType: 'vendor',
      recipient: 'interface',
      request: 0x01,
      value: 0x0013,
      index: 0x0001 
    });
    passString('received transfer status: ' + status.status);
} catch (error) {  
    console.log(error);
}   

As you can see, there are several steps to transfer some data between app and devices. The device is created, opened and then the interface is claimed. Unfortunately, most of the provided data is vendor-specific. Different values should be provided to have a successful transfer. The above values work for my Motorola phone. I'm getting Status: OK as a response.

Take a look at this working example here:

https://www.milosolutions.com/webassembly/

Some useful links

https://www.bennettnotes.com/javascript-read-usb-2/

https://wiki.qt.io/Qt_for_WebAssemblyhttps://doc.qt.io/qt-5/wasm.html

https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb

https://developers.google.com/web/updates/2016/03/access-usb-devices-on-the-web

https://www.visuality.pl/posts/webusb-bridge-between-usb-devices-and-web-browsers 

https://whatwebcando.today/usb.html

https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb

Comments