Communicate an NFC reader with a web application

Gautier Darchen

  • 23 🎂
  • ​Fullstack dev at Takima since 2018
    Working at Cartier in a lab
    • ​React
    • Node.js
    • Java / Spring
    • Devops
  • Easily know the remaining tasks to be performed on a piece of jewelry for a given people
     
  • Bar code / QR code
    • Innovation & a lot of data to hold
       
  • RFID
    • Short-range
       
  • NFC ✅

NFC today

  • Omnipresent in our daily life
     
  • Contactless payment, access badge, etc.
     
  • Cheap (NTAG 213 : about $ 0.08 per chip)
     
  • Different types: frequency, encryption, etc.

Target application 🎯 

Loading page

Result page

Local server NFC Reader and web-sockets

1st iteration
 

Summary

  • Buy a NFC Reader 💸
    • ACR 122U — USB NFC Reader
      ​Advanced Card System Ltd.
       
  • Start a Node.js server on the client side to have access to plugged devices 🔌
     
  • Use a PC/SC library 📕
     
  • Communicate the read payload to the web app via web-sockets 🧦 

Client - server

  • Need asynchronous communication
    • polling
    • server-sent events
    • websockets
  • Choice of a library: Socket.io

Connection workflow

NTAG 213

Memory organization

Local server

// src/index.js
// ...

io.on('connection', (socket) => {

  socket.on('require-scan', () => {
    const nfc = new NFC();
    nfc.on('reader', (reader) => {
      const pageNumber = 4;
      const bufferLength = 48;

      reader.on('card', async (card) => {
        try {
          const data = await reader.read(pageNumber, bufferLength);
          const extractedPayload = data.toString().split('/')[1];

          socket.emit('nfc-tag-scanned', extractedPayload);
        } catch (err) {
          // boilerplate code to handle the error
        }
      });
    });
  });
});
      

Client app

// src/pages/home/components/HomeContainer.jsx

// ...

const HomeContainer = () => {
  const [socket, setSocket] = useState();
  const [readTag, setReadTag] = useState();

  useEffect(() => {
    setSocket(SocketIO.connect(APILocalSocketUrl));
  }, [APILocalSocketUrl]);

  useEffect(() => {
    if (!socket) {
      return () => {};
    }

    socket.emit("require-scan");
    socket.on("nfc-tag-scanned", payload => {
      setReadTag(payload);
    });

    return () => socket.close();
  }, [socket]);

  return <Home readTag={readTag} />;
};

export default HomeContainer;

Demo 1

Cons

  • Requires Node.js on the client 🛠
  • Requires the user to start the web-socket server 👷
  • Need to update on each client if the code changes for the web-socket server 🔄
  • Works well on servers, but we are working here for desktop apps
    • Change our distribution mode

Pros

  • Users always have access to the last version of the web app ⚡️

Key learnings

  • Easy installation 🛠️
     
  • Easy distribution 🚚
     
  • Desktop app 🖥️

How to do it with web technologies?  🤔

Electron application using a NFC Reader

2nd iteration
 

Electron

Build cross platform desktop apps with JavaScript, HTML, and CSS

  • Have a desktop app developed using web frameworks/libraries (like an "embedded browser")
     
  • Easy installation on the client side
     
  • Node.js server on the client-side
    • Access to the file-system, hardware, etc.
       
  • Atom, VS Code, Slack, etc.

Summary

  • Use the USB NFC Reader bought for solution #1
    • ACR 122U — USB NFC Reader
      ​Advanced Card System Ltd.
       
  • Use Electron to have access to plugged devices 🔌
     
  • Use a PC/SC library that we already master 📕

 

Use the library

const HomeContainer = () => {
  const nfc = new NFC();
  
  const [readTag, setReadTag] = useState(undefined);

  nfc.on("reader", reader => {
    console.log(`${reader.reader.name}  device attached`);
    
    const pageNumber = 4;
    const bufferLength = 48;

    reader.on("card", card => {
      console.log(`${reader.reader.name}  card detected`, card);
      reader.read(pageNumber, bufferLength).then(data => {
        const extractedPayload = data.toString().split("/")[1];
        setReadTag(extractedPayload);
      });
      
    });
  });

  return <Home readTag={readTag} />;
};

Demo 2

Limits

  • Requires a build for each target platform (on each release)
     
  • Requires a reinstall on each device after updates
    • Manage the packaging and installation
    • Slow and heavy release cycles
      • Micro application with the release process of heavy industrial softwares
         
  • Lots of configuration to use native modules
     
  • Choose CI/CD providers that support all OS

Dream solution

  • Web app 🌐
     
  • Nothing installed client-side 🤤

Using WebUSB and an Arduino board

3rd iteration
 

WebUSB API

  • Use an API implemented in the browser 🌐
     
  • "Plug and play" devices for web applications 🔌
     
  • Security: devices declare some "web origins" they are allowed to communicate with (white list) 🛡
    • Equivalent to CORS for HTTP requests
       
  • Native code & SDKs  ➡️ cross-platform and "web-ready" libraries 📚

W3C spec draft: wicg.github.io/webusb

WebUSB API

Workflow

NFC Reader

1/2

NFC Reader

  • About $50 per device 💵
     
  • Handicraft and not yet industrialized 👨‍🔬
    • Will be subcontracted

2/2

Arduino programming

Import libraries and configure the WebUSB Serial

// NFC Reading libraries
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>

// WebUSB library
#include <WebUSB.h>

// Web origin
#define PROTOCOL_HTTP 0 /* http:// */
WebUSB WebUSBSerial(PROTOCOL_HTTP, "localhost:3000");

// NFC Shield configuration
PN532_I2C pn532_i2c(Wire);
NfcAdapter nfc = NfcAdapter(pn532_i2c);

#define BAUD_RATE 9600

Arduino programming

Reading loop

void loop() {
  if (nfc.tagPresent()){
    NfcTag tag = nfc.read();
    if (tag.hasNdefMessage()) { // If your tag has a message
      NdefMessage message = tag.getNdefMessage();

      // If you have more than 1 record then it will cycle through them
      int recordCount = message.getRecordCount();
      for (int i = 0; i < recordCount; i++) {
        NdefRecord record = message.getRecord(i);

        int payloadLength = record.getPayloadLength();
        byte payload[payloadLength];
        record.getPayload(payload);

        String payloadAsString = ""; // Processes the message as a string vs as a HEX value
        for (int c = 0; c < payloadLength; c++) {
          payloadAsString += (char)payload[c];
        }
        WebUSBSerial.write(payloadAsString.c_str());
      }
    }
  };

  WebUSBSerial.flush();
  delay(250);
}

Client live coding
👨‍💻

Working zone 🚧

  • Draft Community status (since Aug. 2019)
    • Enabled in Chrome 61 (Sept. 2017)
    • Disabled for several months (security)
    • Only available in secured context (HTTPs)




       

Production 🏭

Thank you! 🙏

Communicate a NFC Reader with a web application

By Gautier Darchen

Communicate a NFC Reader with a web application

Lorsqu'on développe un outil qui interagit avec un périphérique, nous sommes encore forcés de développer des clients lourds ou d'utiliser des conteneurs comme Electron. Pourquoi ne pas directement utiliser le navigateur? Eh oui, vos browsers, et notamment Chrome, ont de plus en plus d'APIs vous permettant d'accéder à des parties natives de vos équipements. Viens découvrir la WebUSB API à travers un exemple simple : la réalisation d'un lecteur de carte NFC en 100% web.

  • 4,918