npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@danidoble/webserial-boardroid

v1.0.0

Published

A strongly-typed, event-driven USB boardroid driver for the Web Serial API, built on top of webserial-core.

Readme

@danidoble/webserial-boardroid

A strongly-typed, event-driven vending machine controller driver built on top of webserial-core.

Manages the serial connection, binary handshake, auto-reconnect, and message routing for a full vending machine main board: coin purse (MDB), banknote purse (recycler / ICT), card reader, cooling relay, temperature sensor, door monitor, and product dispensing.

Not tied to a single transport: swap in the WebUSB, Web Bluetooth, or WebSocket provider from webserial-core, or implement your own SerialProvider for any platform.

npm version license


Requirements

  • webserial-core ^2.1.0 (peer dependency)
  • A compatible transport (see Providers):
    • Web Serial API — Chrome / Edge 89+ (default, no extra setup)
    • WebUSB — Chrome / Edge (via WebUsbProvider)
    • Web Bluetooth — Chrome / Edge (via createBluetoothProvider, Nordic UART Service)
    • WebSocket — any environment (via createWebSocketProvider + a bridge server)
    • Custom — any platform via your own SerialProvider implementation

Installation

# npm
npm install @danidoble/webserial-boardroid webserial-core

# pnpm
pnpm add @danidoble/webserial-boardroid webserial-core

# yarn
yarn add @danidoble/webserial-boardroid webserial-core

# bun
bun add @danidoble/webserial-boardroid webserial-core

webserial-core is a peer dependency — it must be installed alongside this package.


Quick start

import { Boardroid } from '@danidoble/webserial-boardroid';

const boardroid = new Boardroid({ channel: 1, filters: [{ usbVendorId: 0x2341 }] });

// Serial lifecycle
boardroid.on('serial:connecting',   () => console.log('Opening port…'));
boardroid.on('serial:connected',    () => console.log('Port open'));
boardroid.on('serial:disconnected', () => console.log('Disconnected'));

// Boardroid ready
boardroid.on('boardroid:connected', ({ channel }) => console.log(`Boardroid on channel ${channel} ready`));

// Every device message
boardroid.on('boardroid:message', (msg) => console.log(`[${msg.no_code}] ${msg.name}`));

// Payment events
boardroid.on('money:inserted',    ({ type, money }) => console.log(`${type}: ${money.name}`));
boardroid.on('session:money-request', () => console.log('Money session updated'));

// Product dispense result
boardroid.on('dispensed', () => console.log('Dispense cycle finished'));

// Door / temperature / relay monitoring
boardroid.on('event:door',        ({ open }) => console.log('Door', open ? 'open' : 'closed'));
boardroid.on('status:temperature',({ temperature }) => console.log('Temp:', temperature));
boardroid.on('status:relay',      ({ enabled }) => console.log('Relay', enabled ? 'on' : 'off'));

// Opens a port picker dialog (requires a user gesture)
await boardroid.connect();

// Enable payment purses (coin + banknote)
await boardroid.sendPaymentPursesEnable({ coin: true, banknote: true });

// Dispense product on selection 5
await boardroid.sendDispense({ selection: 5 });

// Disable payment purses at end of sale
await boardroid.sendPaymentPursesDisable();

// Dispense change
await boardroid.sendCoinPurseDispense({ $5: 1, $1: 2 });

// Read temperature
await boardroid.sendReadTemperature();

// Test all 80 motors sequentially
const results = await boardroid.sendTestEngines({ limit: 80 });
console.log(results.filter(r => r.dispensed).length, 'motors OK');

Serial settings

The constructor pre-configures the following defaults — no extra setup needed:

| Setting | Value | | ------------------ | ------------------------ | | Baud rate | 115 200 | | Data bits | 8 | | Stop bits | 1 | | Parity | none | | Flow control | none | | Buffer size | 255 B | | Parser | fixedLength (14 bytes) | | Command timeout | 5 000 ms | | Auto-reconnect | ✓ | | Reconnect interval | 1 500 ms | | Handshake timeout | 3 000 ms |


Providers

By default the library uses the browser's native Web Serial API (navigator.serial). You can replace this with any of the built-in providers from webserial-core, or write your own.

Web Serial API (default)

No setup required — works out of the box in Chrome / Edge 89+.

import { Boardroid } from '@danidoble/webserial-boardroid';

const boardroid = new Boardroid({ filters: [{ usbVendorId: 0x2341 }] });
await boardroid.connect();

WebUSB (WebUsbProvider)

import { Boardroid, WebUsbProvider } from '@danidoble/webserial-boardroid';

const boardroid = new Boardroid({
  filters: [{ usbVendorId: 0x2341 }],
  provider: new WebUsbProvider()
});

await boardroid.connect();

Web Bluetooth (createBluetoothProvider)

import { Boardroid, createBluetoothProvider } from '@danidoble/webserial-boardroid';

const boardroid = new Boardroid({ provider: createBluetoothProvider() });
await boardroid.connect();

WebSocket (createWebSocketProvider)

import { Boardroid, createWebSocketProvider } from '@danidoble/webserial-boardroid';

const boardroid = new Boardroid({
  filters: [{ usbVendorId: 0x2341 }],
  provider: createWebSocketProvider('ws://localhost:8080')
});

await boardroid.connect();

Global provider (AbstractSerialDevice.setProvider)

import { AbstractSerialDevice, WebUsbProvider } from 'webserial-core';
import { Boardroid } from '@danidoble/webserial-boardroid';

AbstractSerialDevice.setProvider(new WebUsbProvider());
const boardroid = new Boardroid({ filters: [{ usbVendorId: 0x2341 }] });
await boardroid.connect();

Custom provider

import type { SerialProvider, SerialPortFilter } from '@danidoble/webserial-boardroid';
import { Boardroid } from '@danidoble/webserial-boardroid';

const myProvider: SerialProvider = {
  async requestPort(options?: { filters?: SerialPortFilter[] }): Promise<SerialPort> {
    // return a SerialPort-compatible object
  },
  async getPorts(): Promise<SerialPort[]> {
    // return previously authorised ports
  }
};

const boardroid = new Boardroid({ provider: myProvider });

API

new Boardroid(options?)

| Option | Type | Default | Description | | ----------------- | ----------------------------- | ------- | ------------------------------------------------------------ | | channel | number | 1 | Channel used in the binary handshake. | | filters | SerialPortFilter[] | [] | USB vendor/product filters for port matching. | | provider | SerialProvider | — | Per-instance transport provider. | | polyfillOptions | SerialDeviceOptions<Uint8Array> | — | Extra options forwarded to the underlying device. |


Connection

boardroid.connect()

Opens the serial port and performs the binary handshake. Shows a browser port-picker on first connection; subsequent calls reuse the last authorised port.

await boardroid.connect();

boardroid.disconnect()

Gracefully closes the port and stops auto-reconnect.

await boardroid.disconnect();

boardroid.isConnected()

Returns true when the port is open and the handshake has completed.

boardroid.softReload()

Resets the in-memory sale session (price, change, inserted/retired counters) without touching the physical device.

boardroid.softReload();

Properties

| Property | Type | R/W | Description | | ------------------------ | --------- | --- | --------------------------------------------------------------------------- | | totalInTubes | number | R | Total monetary value (in currency units) currently in the coin tubes. | | totalInRecycler | number | R | Total monetary value currently in the banknote recycler. | | hasRecycler | boolean | R/W | Whether the banknote purse has a recycler unit. | | hasICT | boolean | R/W | Whether the recycler uses ICT (Innovative Technology) hardware. | | banknoteICT | number | R/W | Active ICT banknote denomination (20 \| 50 \| 100 \| 200 \| 500). | | hasCoinPurse | boolean | R/W | Whether a coin purse is physically connected. | | price | number | R/W | Current sale price. Negative or NaN values are normalised to 0. | | change | number | R | Calculated change due (inserted − price). Returns 0 if no debt. | | coins | CoinsInfo | R | Live snapshot of all coin counters (tubes, box, totals). | | banknotes | BanknotesInfo | R | Live snapshot of all banknote counters (stacker, recycler, out, totals). | | cardReaderAvailable | boolean | R/W | Whether a card reader is physically connected. | | cardReaderMaxPreCredit | number | R/W | Maximum pre-credit amount allowed for the card reader. |


Coin purse

boardroid.sendCoinPurseConfigure(options?)

Sends a full configuration frame to the coin purse.

| Option | Type | Default | Description | | -------- | --------- | -------- | ------------------------------ | | enable | boolean | false | Enable (true) or disable. | | high | number | 0xff | High-byte tube limit. | | low | number | 0xff | Low-byte tube limit. |

boardroid.sendCoinPurseEnable()

Shorthand for sendCoinPurseConfigure({ enable: true }).

boardroid.sendCoinPurseDisable()

Shorthand for sendCoinPurseConfigure({ enable: false }).

boardroid.sendCoinPurseReadTubes()

Requests the current coin count in each tube. Triggers a coin-purse:tubes event.

boardroid.sendCoinPurseDispense(options?)

Dispenses coins from the coin purse.

| Option | Type | Default | Description | | ------- | -------- | ------- | ------------------------------ | | $50c | number | 0 | Number of 50-centavo coins. | | $1 | number | 0 | Number of $1 coins. | | $2 | number | 0 | Number of $2 coins. | | $5 | number | 0 | Number of $5 coins. | | $10 | number | 0 | Number of $10 coins. |

await boardroid.sendCoinPurseDispense({ $5: 1, $1: 2 }); // dispense $7 in coins

Banknote purse

boardroid.sendBanknotePurseConfigure(options?)

Sends a configuration frame to the banknote purse. Automatically selects ICT or standard protocol based on hasICT.

| Option | Type | Default | Description | | -------- | --------- | ------- | ----------------------------------------- | | enable | boolean | false | Enable (true) or disable. | | scrow | boolean | false | Enable scrow mode (manual accept/reject). |

boardroid.sendBanknotePurseEnable(options?)

Shorthand for sendBanknotePurseConfigure({ enable: true, ... }).

| Option | Type | Default | Description | | ------- | --------- | ------- | -------------------- | | scrow | boolean | false | Enable scrow mode. |

boardroid.sendBanknotePurseDisable()

Shorthand for sendBanknotePurseConfigure({ enable: false }).

boardroid.sendBanknotePurseDispense(options?)

Dispenses banknotes from the recycler. Requires hasRecycler = true.

| Option | Type | Default | Description | | -------- | -------- | ------- | ---------------------------- | | $20 | number | 0 | Number of $20 banknotes. | | $50 | number | 0 | Number of $50 banknotes. | | $100 | number | 0 | Number of $100 banknotes. | | $200 | number | 0 | Number of $200 banknotes. | | $500 | number | 0 | Number of $500 banknotes. | | $1000 | number | 0 | Number of $1000 banknotes. |

boardroid.sendBanknotePurseAcceptInScrow()

Accepts (stacks/recycles) a banknote currently held in scrow.

boardroid.sendBanknotePurseRejectInScrow()

Rejects and ejects a banknote currently held in scrow.

boardroid.sendBanknotePurseReadRecycler()

Requests the current banknote count in the recycler. Triggers a banknote-purse:recycler event.

boardroid.sendBanknotePurseSaveMemory(options)

Saves denomination counts to the banknote purse NVRAM. All fields are required.

| Option | Type | Description | | --------- | -------- | ----------------------------- | | channel | number | Channel number. | | $20 | number | $20 count to save. | | $50 | number | $50 count to save. | | $100 | number | $100 count to save. | | $200 | number | $200 count to save. | | $500 | number | $500 count to save. | | $1000 | number | $1000 count to save. |


Payment purses (combined)

boardroid.sendPaymentPursesEnable(options?)

Enables the specified payment peripherals simultaneously.

| Option | Type | Default | Description | | --------------- | --------- | ------- | ---------------------------------------- | | coin | boolean | true | Enable coin purse. | | banknote | boolean | true | Enable banknote purse. | | scrowBanknote | boolean | false | Enable scrow mode on the banknote purse. |

boardroid.sendPaymentPursesDisable(options?)

Disables the specified payment peripherals simultaneously.

| Option | Type | Default | Description | | ------------ | --------- | ------- | ----------------------------- | | coin | boolean | true | Disable coin purse. | | banknote | boolean | true | Disable banknote purse. | | cardReader | boolean | false | Disable card reader. |


Card reader

boardroid.sendCardReaderDispense(options?)

Initiates a card reader transaction and dispenses the selected product.

| Option | Type | Default | Description | | ------------------ | ------------------ | ------- | ---------------------------------------------------- | | selection | number | 1 | Primary product selection (1-based). | | second_selection | number \| null | null | Secondary selection for combo dispensing. | | sensor | boolean | true | Use optical sensor to detect delivery. | | seconds | number \| null | null | Motor run time in seconds (0.1–40.0) if no sensor. | | price | number | 0 | Transaction amount. Must be > 0. |

boardroid.sendCardReaderDisable()

Disables the card reader.


Dispense

boardroid.sendDispense(options?)

Triggers the vending motor for the given product selection.

| Option | Type | Default | Description | | ------------------ | ---------------- | ------- | ---------------------------------------------------- | | selection | number | 1 | Product selection (1–80). | | second_selection | number \| null | null | Secondary selection (1–80, must differ from first). | | sensor | boolean | true | Use optical sensor to confirm delivery. | | seconds | number \| null | null | Motor run time in seconds (0.1–40.0) if no sensor. |

await boardroid.sendDispense({ selection: 5 });
// without sensor — run motor for 2 s
await boardroid.sendDispense({ selection: 5, sensor: false, seconds: 2 });

boardroid.sendTestEngines(options?)

Sequentially fires every motor from 1 to limit, collects the response for each, and returns an array of DispenserDispenseResponse. Emits percentage:test after each motor.

| Option | Type | Default | Description | | ------- | -------- | ------- | ------------------------------------- | | limit | number | 80 | Last motor to test (1-based). |

boardroid.on('percentage:test', ({ percentage }) => console.log(`${percentage}%`));
const results = await boardroid.sendTestEngines({ limit: 80 });

Cooling relay

boardroid.sendCoolingRelayConfigure(options?)

| Option | Type | Default | Description | | -------- | --------- | ------- | -------------------- | | enable | boolean | false | Turn relay on/off. |

boardroid.sendCoolingRelayEnable()

Shorthand for sendCoolingRelayConfigure({ enable: true }).

boardroid.sendCoolingRelayDisable()

Shorthand for sendCoolingRelayConfigure({ enable: false }).


Temperature

boardroid.sendReadTemperature()

Requests the current cabinet temperature. Triggers a status:temperature event.


Custom frame

boardroid.sendCustomCode(options)

Sends a raw byte array directly to the device.

| Option | Type | Description | | ------ | ---------- | ------------------------------ | | code | number[] | Raw bytes to send (12 bytes). |

await boardroid.sendCustomCode({ code: [0xf1, 0xcb, 0, 0, 0, 0, 0, 0, 0, 0, 0xf2, 0x00] });

Events

Core events (from webserial-core)

| Event | Payload | Description | | ------------------------ | --------------------------------- | ------------------------------------------------- | | serial:connecting | instance | Port is being opened. | | serial:connected | instance | Port opened successfully. | | serial:disconnected | instance | Port closed or device unplugged. | | serial:reconnecting | instance | Auto-reconnect attempt in progress. | | serial:data | data: Uint8Array, instance | Raw 14-byte frame received from the device. | | serial:sent | data: Uint8Array, instance | Raw bytes written to the port. | | serial:error | error: Error, instance | An error occurred during communication. | | serial:need-permission | instance | No authorised port found; user must grant access. | | serial:timeout | command: Uint8Array, instance | A queued command timed out. |

Boardroid events

| Event | Payload | Description | | ---------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | | boardroid:connected | { channel: number } | Handshake succeeded; device is ready. | | boardroid:message | BoardroidMessage | Fired for every recognised device response. | | boardroid:unknown | BoardroidMessage | Fired for unrecognised response bytes. | | run:default-load | {} | Fires right after boardroid:connected. Load defaults here. | | money:inserted | { type: 'coin' \| 'banknote'; money: MoneyInfo; where: string } | A coin or banknote was inserted. | | session:money-request | {} | Money session totals changed. | | session:money-dispensed | { type_money: string \| null; retired: number \| null; finish: boolean; type: string; data?: BoardroidMessage } | Change or banknote was dispensed. | | dispensed | {} | Product dispense cycle completed. | | coin-purse:config | { enabled: boolean } | Coin purse enable/disable confirmed. | | coin-purse:tubes | CoinTubes | Current coin count per tube denomination. | | coin-purse:coin-event | CoinsInfo | Full coin counter snapshot. | | coin-purse:reject-lever | {} | Reject lever was pressed. | | coin-purse:reset | {} | Coin purse was reset. | | banknote-purse:config | { enabled: boolean; scrow: boolean } | Banknote purse enable/scrow confirmed. | | banknote-purse:event-banknote | BanknotesInfo | Full banknote counter snapshot. | | banknote-purse:recycler | BanknoteStacker | Current count per recycler denomination. | | banknote-purse:banknote-scrow-status | { status: boolean } | Scrow accept/reject result. | | banknote-purse:save-memory | { message: BoardroidMessage } | NVRAM save operation result. | | banknote-purse:read-memory | { message: BoardroidMessage } | NVRAM read operation result. | | card-reader:event | BoardroidMessage | Card reader state change (disable, pre-auth, sell, etc.). | | event:door | { open: boolean } | Cabinet door opened or closed. | | door:event | { open: boolean } | Alias for event:door. | | status:temperature | { high: number; low: number; temperature: number } | Current cabinet temperature (°C). | | status:relay | { enabled: boolean } | Cooling relay state. | | percentage:test | { percentage: number; dispensed: DispenserDispenseResponse[] \| null } | Progress during sendTestEngines(). |

BoardroidMessage — selected no_code values

| no_code | Meaning | | ------------ | ----------------------------------------------------- | | 1 | Connection handshake completed. | | 3 | Coin purse enabled. | | 4 | Coin purse disabled. | | 5 | Banknote purse configured. | | 6 | Coin tubes read. | | 7 | Banknote recycler read. | | 8 | Banknote scrow status. | | 9 | Banknotes dispensed. | | 10 | Coins dispensed. | | 11 | Product not delivered. | | 12 | Product delivered. | | 13 | Door closed. | | 14 | Door open. | | 15 | Temperature status. | | 16 | Relay on. | | 17 | Relay off. | | 18 | Banknote NVRAM saved. | | 19 | Banknote NVRAM read. | | 20–31 | Card reader events (code 20 + event_byte). | | 100 | Coin reject lever pressed. | | 101 | Coin purse reset. | | 200 | Banknote dispensed by ICT recycler. | | 400 | Response received but not recognised. |


TypeScript

All events and method signatures are fully typed. The package ships with .d.mts / .d.cts declaration files — no extra @types package required.

Commonly used types and all built-in providers are re-exported so you do not need to import directly from webserial-core:

import {
  Boardroid,
  WebUsbProvider,
  createBluetoothProvider,
  createWebSocketProvider
} from '@danidoble/webserial-boardroid';

import type {
  BoardroidOptions,
  BoardroidMessage,
  DispenserDispenseResponse,
  CommandOptions,
  CoinPurseConfigureOptions,
  CoinPurseDispenseOptions,
  BanknotePurseConfigureOptions,
  BanknotePurseEnableOptions,
  BanknotePurseDispenseOptions,
  BanknotePurseICTConfigureOptions,
  BanknotePurseICTDispenseOptions,
  BanknotePurseOtherConfigureOptions,
  BanknotePurseOtherDispenseOptions,
  BanknotePurseSaveMemoryOptions,
  CardReaderDispenseOptions,
  BoardroidDispenseOptions,
  CoolingRelayConfigureOptions,
  PaymentPursesOptions,
  PaymentPursesEnableOptions,
  SendCustomCodeOptions,
  MoneyInfo,
  CoinTubes,
  CoinsInfo,
  BanknoteStacker,
  BanknotesInfo,
  DenominationBanknote,
  SerialPortFilter,
  SerialDeviceOptions,
  SerialEventMap,
  SerialParser,
  SerialProvider,
  SerialPolyfillOptions
} from '@danidoble/webserial-boardroid';

License

GPL-3.0-only © Danidoble