Connect to USB Card Readers with NodeJS

A really handy thing to be able to do is to control USB devices using JavaScript. This is a powerful capability, made easily possible by NodeJS (better known, simply, as Node). To get Node, go here.   b2ap3_thumbnail_nodejs-logo.png

Node is an increasingly vital piece of infrastructure for companies that do business via the Web. You’ve heard of many of the companies I’m talking about. (Check out their logos below.)

Most companies that use Node run it on the server side. But it’s easily possible (and definitely worthwhile) to run it on the client side, too.

Unlike scripts written for web pages, scripts that run on Node have no security restrictions that would keep them from performing file I/O, opening network connections across domains, shelling out to the OS, or calling native code modules, among other things. Since they’re unhampered by “sandbox security” restrictions, scripts that run on Node can do amazing, powerful things that browser scripts could never do.

b2ap3_thumbnail_WhoUsesNode.png
Companies that use Node for critical infrastructure.

 

When I wanted to connect to ID TECH’s USB-based credit card readers using JavaScript, I looked to see what kind of USB connectivity modules might already exist for Node. Turns out there are several. I chose one called node-hid, which is based on the cross-platform HIDAPI library.

I chose node-hid mostly because of its brain-dead-simple API, but also based on the fact that it installs super-easily. Just fire up a command line console and run npm install node-hid. Node’s npm package manager will go to work downloading all the bits you need from the Web, and it will resolve dependencies and build binaries automatically. (Allow a minute or two for this.) When you’re done, you’ll have the ability to put require 'node-hid' in your Node scripts and connect to USB devices!

Below is an example of a script you can run with Node (and node-hid) that will let you connect to USB devices having the ID TECH vendor ID (hex 0xACD):

// ==========  usb-provider ===========
'use strict';

var HID = require("node-hid");
const EventEmitter = require('events');

class USBProvider extends EventEmitter {

    constructor() {
        super();
        var self = this;
        this.onerror = function(e) {
            console.log('error: ' + e);
        };
        this.getDeviceHandle = function() {
            return deviceHandle;
        }

        var SCAN_INTERVAL = 2000; // scan every 2 secs
        var VENDOR_ID = 0xACD; // default ID TECH vid
        var deviceHandle = null; // stores our handle
        var deviceRecord = null; // stores device record
        var stopKey = null; // to stop polling (if needed)

        // This will be called repeatedly by poll(), below
        function cycle() {

            var deviceFound = false;
            HID.devices().forEach(function(device, index, records) {

                deviceFound = (device.vendorId == VENDOR_ID);

                if (device.vendorId == VENDOR_ID && deviceRecord == null) {
                    deviceRecord = device;
                    try {
                        // Try to connect.
                        deviceHandle =
                            new HID.HID(device.vendorId, device.productId);

                        deviceHandle.on('error', self.onerror);

                        self.emit('usbconnect', deviceHandle);

                        console.log("usbprovider: connect");
                    } catch (e) {
                        self.onerror("Exception caught:\n" + e);
                        self.emit('usbexception', device);
                    }
                } // if

                if (index == records.length - 1 && !deviceFound) {

                    // HANDLE DISCONNECT EVENT
                    if (deviceRecord != null) {
                        deviceRecord = deviceHandle = null; // nullify record	
                        // self.ondisconnect();
                        self.emit('usbdisconnect');

                        console.log("usbprovider: disconnect");
                    } // if 

                } // if 
            }); // forEach
        } // cycle

        this.poll = function() {
            this.stopKey = setInterval(cycle, SCAN_INTERVAL);
        }
    }
}
// Allow other modules to use this one:
module.exports = USBProvider;

We want to inherit the methods of Node’s EventEmitter class (so that we can emit custom events). Therefore we start right out by declaring a class that extends EventEmitter. (Note that Node allows the use of EMCA 6 class-declaration syntax.)

We have a poll() method (near the bottom) that uses setInterval() to start an endless polling cycle that has us checking the device list once every two seconds. The cycle() method carries out our poll. It checks every connected device using two ‘if’ statements. The first ‘if’ says: If any device with the ID TECH VendorID (0x0ACD) is found, and we’re not already connected to a device of interest, we go ahead and try connecting to the device. The second ‘if’ says: If the poll finds no ID TECH device and yet we already have a non-null deviceRecord, it means the record is stale because the device is unplugged. (Hence we pop a ‘usbdisconnect’ event.)

The USBProvider emits ‘usbconnect’ and ‘usbdisconnect’ events, plus ‘data’ events. When you get a ‘usbconnect’ event, it’s important to realize you’re being passed the deviceHandle for the connected device. You need to store a reference to that handle right away, so you can later use it to write to the device. But there’s also another thing you need to do right away, which is register your data handler. This lets you read from the device. (Reading happens via ‘data’ events.) Register your ‘data’ handler at connect time.

Code that uses the USBProvider class might look similar to this:

// First, instantiate the provider
var usb = new USBProvider();

var deviceHandle = null; // We will store the device handle here

// Set up a connection handler. Inside it, set the data handler.
usb.on('usbconnect', function(h) {

    deviceHandle = h; // cache the handle

    // set up a data handler (for reading data)
    deviceHandle.on('data', (data) => {

        var hex = data.toString('hex');
        // do something with data...
    });

});

Notice that we can easily get a hex string version of our data using toString('hex'). That’s because the data is in a Buffer object. The Buffer object is not a JavaScript array.

Elsewhere in your code, you can write to the device very easily, as follows:

    deviceHandle.write( cmdArray );

The cmdArray isn’t a Buffer object. It is simply a JavaScript array containing integer values.

But don’t forget, you can’t connect to anything at all unless you start up the poll() method! So after setting up your event handlers and initializing your code, remember to begin polling:

    usb.poll();  // Be sure to do this! This starts auto-detect & connect

With the various bits of code shown above, you’ll be able to read and write USB-based ID TECH devices in no time. And if you want, you can use Node’s wonderful Websockets capability to send USB data straight to the browser, so that your HTML page can control your payment device. I’ll have a lot more to say on that very subject in future posts. So check back here soon!

Comments (2)

Hi Kas. It is very interesting post. I found it useful to manage my contactless card reader in a pc client using node. But I need some guidance how to do it or implementing in a contactless card reader attached to android phone, considering my programming skills are closer to node.js than native android languages.
Thanks

For Android, I don’t know of an equivalent way to do it (in JavaScript). Instead, you will want to look into using our (free) Universal SDK. We have an Android version of the SDK (using Java). Consult our Downloads Page at https://atlassian.idtechproducts.com/confluence/display/KB/Downloads+-+Home (no registration required).

Leave a comment

You must be logged in to post a comment.

There was an error

Success