Browser & OS compatibility
SerialPort (Node.js)
Node SerialPort is a JavaScript library for connecting to serial ports that works in NodeJS and Electron.
Web Bluetooth API
A subset of the Web Bluetooth API is available in ChromeOS, Chrome for Android 6.0, Mac (Chrome 56) and Windows 10 (Chrome 70). See MDN's Browser compatibility table for more information.
For Linux and earlier versions of Windows, enable the #experimental-web-platform-features
flag in about://flags
.
Web Serial API
The Web Serial API is available on all desktop platforms (ChromeOS, Linux, macOS, and Windows) in Chrome 89. See MDN's Browser compatibility table for more information.
Web Serial API Polyfill
On Android, support for USB-based serial ports is possible using the WebUSB API and the Serial API polyfill. This polyfill is limited to hardware and platforms where the device is accessible via the WebUSB API because it has not been claimed by a built-in device driver.
Prepare PN532 hardware
PN532.js support Web Serial and Web Bluetooth. You can connect PN532 to PC via an USB2TTL module (e.g. CH340
, PL2303
) or via BLE UART module (e.g. JDY-33
, HC-08
).
See Mtools Tec's How to make PN532 work on Bluetooth and How To Test PN532 Working With Bluetooth Module? for more information.
Installing
Package manager
Using npm:
$ npm install pn532.js
$ npm install serialport
Using yarn:
$ yarn add pn532.js
$ yarn add serialport
Once the package is installed, you can import the library using import
or require
:
import { Pn532, Packet, utils: Pn532utils } from 'pn532.js'
import Crypto1 from 'pn532.js/Crypto1.js'
import LoggerRxTx from 'pn532.js/plugin/LoggerRxTx.js'
import Pn532Hf14a from 'pn532.js/plugin/Hf14a.js'
import Pn532SerialPortAdapter from 'pn532.js/plugin/SerialPortAdapter.js'
import Pn532WebbleAdapter from 'pn532.js/plugin/WebbleAdapter.js'
import Pn532WebserialAdapter from 'pn532.js/plugin/WebserialAdapter.js'
const { Pn532, Packet, utils: Pn532utils } = require('pn532.js')
const Crypto1 = require('pn532.js/Crypto1.js')
const LoggerRxTx = require('pn532.js/plugin/LoggerRxTx.js')
const Pn532Hf14a = require('pn532.js/plugin/Hf14a.js')
const Pn532SerialPortAdapter = require('pn532.js/plugin/SerialPortAdapter.js')
const Pn532WebbleAdapter = require('pn532.js/plugin/WebbleAdapter.js')
const Pn532WebserialAdapter = require('pn532.js/plugin/WebserialAdapter.js')
CDN
Using jsDelivr CDN:
<script src="https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/pn532.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/Crypto1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/plugin/Hf14a.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/plugin/LoggerRxTx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/plugin/WebbleAdapter.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pn532.js@0/dist/plugin/WebserialAdapter.min.js"></script>
After the script
tag, you can use the PN532.js
as following:
const {
_,
Pn532: { Pn532, Packet, utils: Pn532utils },
Crypto1,
Pn532Hf14a,
Pn532LoggerRxTx,
Pn532WebbleAdapter,
Pn532WebserialAdapter,
} = window
Getting Started
use Adapter
A pn532 instance must register exactly one adapter plugin:
const {
_,
Pn532: { Pn532, Packet, utils: Pn532utils },
Crypto1,
Pn532Hf14a,
Pn532WebbleAdapter,
Pn532WebserialAdapter,
} = window
const pn532usb = new Pn532()
pn532usb.use(new Pn532WebserialAdapter())
console.log(JSON.stringify(await pn532usb.getFirmwareVersion()))
const pn532ble = new Pn532()
pn532ble.use(new Pn532WebbleAdapter())
console.log(JSON.stringify(await pn532ble.getFirmwareVersion()))
import Pn532SerialPortAdapter from 'pn532.js/plugin/SerialPortAdapter.js'
const pn532node = new Pn532()
pn532node.use(new Pn532SerialPortAdapter(), { path: '/dev/tty.usbserial-120' })
console.log(JSON.stringify(await pn532node.getFirmwareVersion()))
Read UID, ATQA, SAK from Mifare Classic 1k
const {
_,
Pn532: { Pn532, Packet, utils: Pn532utils },
Pn532Hf14a,
Pn532WebserialAdapter,
} = window
const pn532 = new Pn532()
pn532.use(new Pn532WebserialAdapter())
pn532.use(new Pn532Hf14a())
console.log(JSON.stringify(await pn532.$hf14a.mfSelectCard()))
Manipulate with MIFARE Classic 1k and MIFARE Classic DirectWrite aka Gen2 aka CUID
If you are not familiar with Chinese Magic Card, see Proxmark3's Magic Cards Notes for more information.
const {
_,
Pn532: { Pn532, Packet, utils: Pn532utils },
Pn532Hf14a,
Pn532WebserialAdapter,
} = window
const pn532 = new Pn532()
pn532.use(new Pn532WebserialAdapter())
pn532.use(new Pn532Hf14a())
const key = Packet.fromHex('FFFFFFFFFFFF')
let resp1 = await pn532.$hf14a.mfReadBlock({ block: 0, isKb: 1, key })
console.log(resp1.hex)
console.log(resp1.inspect)
let resp2 = await pn532.$hf14a.mfReadBlockKeyBA({ block: 0, ka: key, kb: key })
console.log(resp2.hex)
console.log(resp2.inspect)
let resp3 = await pn532.$hf14a.mfReadSector({ sector: 0, isKb: 1, key })
console.log(JSON.stringify(resp3))
console.log(resp3.data.hex)
console.log(resp3.data.inspect)
let resp4 = await pn532.$hf14a.mfReadSectorKeyBA({ sector: 0, ka: key, kb: key })
console.log(JSON.stringify(resp4))
console.log(resp4.data.hex)
console.log(resp4.data.inspect)
let resp5 = await pn532.$hf14a.mfReadCardByKeys({ sectorMax: 16, keys: [key] })
console.log(JSON.stringify(resp5))
console.log(resp5.data.hex)
console.log(resp5.data.inspect)
const dataBlock = Packet.fromHex('000102030405060708090A0B0C0D0E0F')
await pn532.$hf14a.mfWriteBlock({ block: 1, isKb: 1, key, data: dataBlock })
await pn532.$hf14a.mfWriteBlockKeyBA({ block: 1, ka: key, kb: key, data: dataBlock })
const dataSector = new Packet(64)
dataSector.set(Packet.fromHex('FFFFFFFFFFFF08778F00FFFFFFFFFFFF'), 48)
let resp6 = await pn532.$hf14a.mfWriteSector({ sector: 1, isKb: 1, key, data: dataSector })
console.log(JSON.stringify(resp6))
let resp7 = await pn532.$hf14a.mfWriteSectorKeyBA({ sector: 1, ka: key, kb: key, data: dataSector })
console.log(JSON.stringify(resp7))
const dataCard = new Packet(1024)
dataCard.set(Packet.fromHex('0102030404080400000000000000BEAF'))
for (let i = 0; i < 16; i++) dataCard.set(Packet.fromHex('FFFFFFFFFFFF08778F00FFFFFFFFFFFF'), i * 64 + 48)
let resp8 = await pn532.$hf14a.mfWriteCardByKeys({ sectorMax: 16, keys: [key], data: dataCard })
console.log(JSON.stringify(resp8))
Manipulate with MIFARE Classic Gen1A aka UID
If you are not familiar with Chinese Magic Card, see Proxmark3's Magic Cards Notes for more information.
const {
_,
Pn532: { Pn532, Packet, utils: Pn532utils },
Pn532Hf14a,
Pn532WebserialAdapter,
} = window
const pn532 = new Pn532()
pn532.use(new Pn532WebserialAdapter())
pn532.use(new Pn532Hf14a())
let resp1 = await pn532.$hf14a.mfReadBlockGen1a({ block: 0 })
console.log(resp1.hex)
console.log(resp1.inspect)
let resp2 = await pn532.$hf14a.mfReadSectorGen1a({ sector: 0 })
console.log(JSON.stringify(resp2))
console.log(resp2.data.hex)
console.log(resp2.data.inspect)
let resp3 = await pn532.$hf14a.mfReadCardGen1a({ sectorMax: 16 })
console.log(JSON.stringify(resp3))
console.log(resp3.data.hex)
console.log(resp3.data.inspect)
const dataBlock = Packet.fromHex('000102030405060708090A0B0C0D0E0F')
await pn532.$hf14a.mfWriteBlockGen1a({ block: 1, data: dataBlock })
const dataSector = new Packet(64)
dataSector.set(Packet.fromHex('FFFFFFFFFFFF08778F00FFFFFFFFFFFF'), 48)
let resp4 = await pn532.$hf14a.mfWriteSectorGen1a({ sector: 1, data: dataSector })
console.log(JSON.stringify(resp4))
const dataCard = new Packet(1024)
dataCard.set(Packet.fromHex('0102030404080400000000000000BEAF'))
for (let i = 0; i < 16; i++) dataCard.set(Packet.fromHex('FFFFFFFFFFFF08778F00FFFFFFFFFFFF'), i * 64 + 48)
let resp5 = await pn532.$hf14a.mfWriteCardGen1a({ sectorMax: 16, data: dataCard })
console.log(JSON.stringify(resp5))
await pn532.$hf14a.mfSetUidGen1a({ uid: new Packet(_.times(4, () => _.random(0, 0xFF))) })
let resp6 = await pn532.$hf14a.mfWipeGen1a({ sectorMax: 16, uid: Packet.fromHex('01020304') })
console.log(JSON.stringify(resp6))
Related links