A Node.js driver for Innovative Technology banknote validators / payout units (SMART Payout, NV200, NV11) using the encrypted eSSP (Smiley Secure Protocol) over a serial connection.
It handles the low-level protocol — packet framing, CRC16, sequence flags, the eSSP key negotiation (Diffie-Hellman) and AES encryption of the link — and exposes a high-level, event-driven API for accepting and dispensing notes.
⚠️ This library talks to real cash-handling hardware over a serial port. Test against a device you control, in a safe environment.
🧪 Status: the protocol logic is unchanged from production use, but the migration to
serialportv13 has not yet been verified on real hardware. If you have a device, testing and feedback are very welcome. Pin an earlier version if you need the previously hardware-testedserialportv7 code path.
- Full eSSP encrypted-link setup (generator/modulus/key exchange, AES-ECB).
- Accept notes (
takeNote) and dispense notes (dispenseNote). - Query device state: serial number, setup/denominations, stored levels.
- Note routing (payout store vs. cashbox).
- Rich event stream for every device status (credit, stacking, jams, fraud, cashbox removed, dispensing, payout errors, …).
- Node.js >= 20 (required by
serialportv13). - A serial connection to the device (e.g.
/dev/ttyACM0,/dev/ttyUSB0). - Built on
serialportv13.
From npm:
npm install essp-smartpayoutconst SMARTPAYOUT = require('essp-smartpayout');Or from source (uses Babel, since the source is written as ES modules):
git clone https://github.com/UrgenProchnoff/essp-smartpayout.git
cd essp-smartpayout
npm install
npm run build # compiles to dist/import SMARTPAYOUT from './module_smartpayout';
const payout = new SMARTPAYOUT();
payout.initialize({
device: '/dev/ttyACM0', // serial port of the device
type: 'SMARTPayout',
});
async function main() {
// Establish the encrypted link and switch to protocol v6.
await payout.Init();
// Read device configuration (currency, channels, denominations).
const setup = await payout.getSetup();
console.log('setup', setup);
// Read serial number and stored note levels.
console.log('serial', await payout.getSerialNumber());
console.log('levels', await payout.getAllLevels());
// Accept notes. Pass an array of nominals (e.g. [100, 500]) to accept
// only those denominations, or call without arguments to accept all.
await payout.takeNote();
// Run the poll loop while a transaction is active.
await payout.pool();
}
payout.on('take_note', ({ note_nominal, note_number }) => {
console.log('Accepted note', note_nominal);
});
payout.on('dispense_note', ({ note_nominal }) => {
console.log('Dispensed note', note_nominal);
});
payout.on('error', err => {
console.error('Device error', err);
});
main();Because the source uses ES modules, run it through Babel, e.g.:
npx babel-node example.jsRunnable examples live in examples/. They require a real device
on a serial port (set via the ESSP_DEVICE environment variable):
# Read-only: serial number, setup, stored levels (safest to run first)
ESSP_DEVICE=/dev/ttyACM0 npx babel-node examples/device-info.js
# Accept an inserted note
ESSP_DEVICE=/dev/ttyACM0 npx babel-node examples/accept-note.js
# Dispense notes (this physically pays out cash)
ESSP_DEVICE=/dev/ttyACM0 NOMINAL=100 COUNT=1 npx babel-node examples/dispense-note.jsnpm run example runs the read-only device-info example.
| Method | Description |
|---|---|
initialize(opts) |
Open the serial port and wire up device events. opts = { device, type }. |
Init() |
Sync, exchange keys and switch to protocol v6 (encrypted link). |
getSetup() |
Request device setup (currency, channels, denominations, multiplier). |
getSerialNumber() |
Return the device serial number. |
getAllLevels() |
Return stored note levels per denomination. |
setDenominationRoute(arr) |
Route each denomination to payout store or cashbox. |
takeNote(nominals?) |
Enable note acceptance. Optional array of note values restricts accepted denominations; omit to accept all. |
dispenseNote([nominal, count]) |
Pay out count notes of nominal. |
pool() |
Poll the device while a transaction is in progress. |
display_on() / display_off() |
Toggle the bezel light. |
all_disable() |
Disable the validator. |
take_note, dispense_note, pool_event, and error are emitted by the
high-level class. The underlying eSSP instance (lib/essp_smartpayout.js)
emits the full protocol event set, including: ready, read_note,
credit_note, note_rejecting, note_stacking, note_stacked,
note_rejected, safe_note_jam, unsafe_note_jam, fraud_attempt,
stacker_full, cashbox_removed, cashbox_replaced, dispensing,
dispensed, jammed, incomplete_payout, incomplete_float, emptying, emptied,
note_stored_in_payout, payout_out_of_service, jam_recovery,
error_during_payout, and more.
.
├── module_smartpayout.js # High-level SMARTPAYOUT class (accept / dispense)
└── lib/
└── essp_smartpayout.js # Low-level eSSP protocol driver
This is an independent, community project. It is not affiliated with or endorsed by Innovative Technology Ltd. "SMART Payout", "NV200", "NV11" and related names are trademarks of their respective owners. Use at your own risk.
MIT © Evgenii Polomannyi