@danidoble/webserial-board2droid
v1.0.0
Published
A strongly-typed, event-driven USB board2droid driver for the Web Serial API, built on top of webserial-core.
Readme
@danidoble/webserial-board2droid
TypeScript driver for Board2Droid vending-machine controllers, built on top of webserial-core v2.
Installation
npm install @danidoble/webserial-board2droid webserial-core
# or
pnpm add @danidoble/webserial-board2droid webserial-corewebserial-core is a peer dependency — you must install it alongside this package.
Quick start
import { Board2Droid } from '@danidoble/webserial-board2droid';
const board = new Board2Droid({ filters: [{ usbVendorId: 0x1a86 }] });
board.on('b2d:pong', ({ firmware, deviceNumber }) =>
console.log('Board connected — firmware:', firmware, '— device:', deviceNumber),
);
await board.connect(); // opens the serial port and runs handshake
await board.sendPing(); // optional extra pingProtocol
Binary, fixed 16-byte frames.
[SOF=0xF1][CMD][D0..D11][EOF=0xF2][CHK]CHK = (CMD + D0 + … + D11 + EOF) & 0xFFD11= target device number (0 = broadcast)- Multi-board addressing over RS-232 bus
Constructor options (Board2DroidOptions)
| Option | Type | Description |
|-------------------|-------------------------|--------------------------------------------------|
| filters | SerialPortFilter[] | USB vendor/product filters for requestPort() |
| provider | SerialProvider | Custom transport (WebUSB, BT, WebSocket, …) |
| polyfillOptions | SerialPolyfillOptions | Extra options forwarded to the core polyfill |
Defaults: baudRate=115200, 8N1, bufferSize=512, commandTimeout=3000 ms, autoReconnect=true.
Events (incoming)
Listen with board.on(event, handler).
| Event | Payload type | Fires when… |
|----------------------------|-----------------------|-------------------------------------------------------|
| b2d:pong | PongEvent | Board replies to PING — carries firmware + deviceNumber |
| b2d:coin-in | CoinInEvent | A coin was inserted (raw, type, centavos) |
| b2d:bill-in | BillInEvent | Bill activity — discriminated by kind ('bill' or 'status') |
| b2d:coin-config | — | Coin changer config accepted |
| b2d:bill-config | BillConfigEvent | Bill acceptor denomination config (config: number[]) |
| b2d:tubes | TubesEvent | Coin tube fill levels (counts: number[], fullMask) |
| b2d:bill-channels | BillChannelsEvent | Bill channel config read from EEPROM |
| b2d:coins-out | — | Coin dispense cycle completed |
| b2d:product | ProductEvent | Motor dispense finished (success: boolean) |
| b2d:door | DoorEvent | Cabinet door status changed (open: boolean) |
| b2d:temperature | TemperatureEvent | ADC temperature reading (adcRaw: number) |
| b2d:relay | RelayEvent | Relay acknowledgement (relay, state) |
| b2d:save-memory | — | EEPROM write completed |
| b2d:read-memory | ReadMemoryEvent | EEPROM read result (addr, value) |
| b2d:cashless | CashlessEvent | MDB cashless sub-event (subEvent, data) |
| b2d:motor-status | MotorStatusEvent | Motor/slot connectivity (selector, connected) |
| b2d:device-number | DeviceNumberEvent | Device number confirmed after assignment |
| b2d:hopper | HopperEvent | Hopper result code + optional data |
| b2d:temperature-report | TemperatureReportEvent | Periodic auto-temp report (adcRaw, relayState, autoActive) |
| b2d:unknown | UnknownFrameEvent | Frame arrived but EVT code not recognised (frame) |
BillInEvent discriminated union
board.on('b2d:bill-in', (evt) => {
if (evt.kind === 'bill') {
console.log(evt.routing, evt.billType, evt.centavos);
} else {
console.log('Bill status:', evt.statusCode);
}
});Commands (outgoing)
All commands accept an optional device parameter (default 0 = broadcast).
General
| Method | Parameters | Description |
|--------|-----------|-------------|
| sendPing(device?) | — | Ping the board — fires b2d:pong |
| sendSetupBill(device?) | — | Re-initialise bill acceptor/recycler |
| sendSetupCoin(device?) | — | Re-initialise coin changer |
| sendReadTubes(device?) | — | Request tube fill levels → b2d:tubes |
| sendReadBillChannels(device?) | — | Read bill denom config from EEPROM → b2d:bill-channels |
| sendEscrow(accept, device?) | accept: boolean | Stack (true) or return (false) escrowed bill |
| sendDispenseBills(count0, count1, device?) | counts | Dispense bills from recycler |
| sendDispenseCoins(counts, device?) | counts: number[] (up to 6) | Dispense coins by denomination |
| sendProductOut(sel1, sel2?, mode?, device?) | selector | Activate product motor |
| sendSaveMemory(addr, value, device?) | 16-bit addr, byte | Write byte to EEPROM → b2d:save-memory |
| sendReadMemory(addr, device?) | 16-bit addr | Read byte from EEPROM → b2d:read-memory |
| sendLed(state, device?) | 0=off, 1=on, 2=pulse | Control product LED |
| sendReadTemperature(device?) | — | Request ADC temperature → b2d:temperature |
| sendRelay(relay, state, device?) | relay number, 0/1 | Control relay output → b2d:relay |
| sendMotorStatus(selector, device?) | motor selector | Check slot connectivity → b2d:motor-status |
| sendSetDeviceNumber(newNum, currentDevice?) | new number | Assign device number → b2d:device-number |
| sendTemperatureConfig(options, device?) | TemperatureConfigOptions | Configure auto-temperature control |
TemperatureConfigOptions
interface TemperatureConfigOptions {
enable: boolean; // enable auto-relay control
adcHigh: number; // ADC threshold to activate relay
adcLow: number; // ADC threshold to deactivate relay
relay: number; // relay number to control
interval: number; // report interval in seconds
}Cashless (MDB)
| Method | Parameters | Description |
|--------|-----------|-------------|
| sendCashlessEnable(device?) | — | Enable cashless reader |
| sendCashlessDisable(device?) | — | Disable cashless reader |
| sendCashlessSetPrice(price, sel, device?) | centavos, selection | Pre-configure price |
| sendCashlessVendRequest(price, sel, device?) | centavos, selection | MDB VEND REQUEST |
| sendCashlessVendSuccess(sel, device?) | selection | MDB VEND SUCCESS |
| sendCashlessVendFailure(device?) | — | MDB VEND FAILURE |
| sendCashlessVendCancel(device?) | — | Cancel pending vend |
| sendCashlessSessionComplete(device?) | — | MDB SESSION COMPLETE |
| sendCashlessCashSale(price, sel, device?) | centavos, selection | Log equivalent cash sale |
| sendCashlessRevalueRequest(amount, device?) | centavos | Request card revalue (L2+) |
| sendCashlessRevalueLimit(device?) | — | Query max revalue amount |
| sendCashlessTimeDateResponse(date?, device?) | Date | Reply to time/date request (L2+) |
| sendCashlessDataEntryResponse(data, device?) | number[] | Reply to data-entry/PIN request (L2+) |
Hopper
| Method | Parameters | Description |
|--------|-----------|-------------|
| sendHopperInit(device?) | — | Initialise hopper → b2d:hopper |
| sendHopperStatus(device?) | — | Query tube fill levels |
| sendHopperDispense(count, tubeType, device?) | count, type | Dispense N coins of one denomination |
| sendHopperPayout(amounts, device?) | amounts: number[] (up to 8) | Multi-denomination payout |
| sendHopperStop(device?) | — | Emergency stop |
Protocol constants
All constant maps are exported for direct use:
import { CMD, EVT, CL_CMD, CL_EVT, HOPPER_ACT, HOPPER_EVT, BILL_ROUTING, BILL_STATUS, LED_STATE, MOTOR_MODE } from '@danidoble/webserial-board2droid';Multi-board addressing
Set the device parameter on any command to target a specific board over the RS-232 bus. 0 broadcasts to all boards.
await board.sendPing(2); // ping board #2 only
await board.sendRelay(1, 1, 3); // turn relay 1 on — board #3 onlyTypeScript
All events and method signatures are fully typed. The package ships with .d.mts / .d.cts declaration files. Commonly used types and all built-in providers are re-exported:
import {
Board2Droid,
WebUsbProvider,
createBluetoothProvider,
createWebSocketProvider,
} from '@danidoble/webserial-board2droid';
import type {
Board2DroidOptions,
PongEvent,
CoinInEvent,
BillInEvent,
BillConfigEvent,
BillChannelsEvent,
ReadMemoryEvent,
DeviceNumberEvent,
TemperatureReportEvent,
TemperatureConfigOptions,
SerialPortFilter,
SerialProvider,
} from '@danidoble/webserial-board2droid';License
GPL-3.0-only © Danidoble
