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

modbus-connect

v5.0.4

Published

Modbus RTU/TCP over Web Serial

Downloads

2,516

Readme

banner

modbus-connect

TypeScript npm downloads PRs Welcome Contributors License MIT GitHub stars

modbus-connect is a cross-platform library for Modbus RTU/TCP communication in both Node.js and modern browsers

Features

  • Isomorphism: Works in Node.js and modern browsers
  • Single serialization point (PortQueue): Background polling, manual commands and writeToPort() all go through one per-port queue — exchanges can never collide on the channel
  • Declarative Device Schema: High-level register mapping for single registers and contiguous register groups with single-PDU batch reads and atomic group writes (client.withSchema())
  • Polling Manager: A queue of tasks with priorities, delays and exponential backoff
  • Smart reconnect: Automatic connection recovery for Serial and TCP/IP
  • Emulator: Full-fiedged TCP-slave and RTU-slave for testing without hardware
  • Auto Discovery (Scanner): Ultra-fast device discovery with adaptive mathematical timeouts and parallel TCP scanning.

Documentation

Install

Using NPM:

$ npm install modbus-connect

Using YARN:

$ yarn add modbus-connect

Usage

// Types library
import { _type_ } from 'modbus-connect/types';

// Transport Controller — it also owns and creates clients (controller.createClient)
import TransportController from 'modbus-connect/transport';

Node RTU connection Example

import TransportController from 'modbus-connect/transport';

const SLAVE_ID = 92;
const TRANSPORT_ID = 'TEST_RTU';

async function main() {
  const controller = new TransportController();
  await controller.addTransport(TRANSPORT_ID, 'node-rtu', {
    path: '/dev/tty.usbserial-01AB5F6D',
    baudRate: 9600,
    dataBits: 8,
    stopBits: 1,
    parity: 'none',
    writeTimeout: 500,
    readTimeout: 500,
  });

  await controller.connectTransport(TRANSPORT_ID);

  // The controller creates the client: framing is derived from the port RS mode (RS485 -> rtu)
  // and the slave is added to the port inventory automatically.
  const client = await controller.createClient({
    slaveId: SLAVE_ID,
    transportId: TRANSPORT_ID,
    timeout: 3000,
  });

  await new Promise(r => setTimeout(r, 250));

  const pollingTask = {
    id: 'task-read-holding-registers',
    interval: 1000,
    fn: async () => {
      return await client.readHoldingRegisters(0, 2);
    },
    onData: data => {
      console.log(data);
    },
    onError: err => {
      console.error(err.message ?? err);
    },
  };

  controller.addPollingTask(TRANSPORT_ID, pollingTask);
}

Expected result:

phk_mvn@MacBook-Air-Danila modbus-connect % node test-rtu.js
2026-09-25 14:20:01.114 INFO Transport Controller Transport "TEST_RTU" added with PollingManager
2026-09-25 14:20:01.402 INFO Node RTU Serial port /dev/tty.usbserial-01AB5F6D opened
2026-09-25 14:20:01.403 INFO Transport Controller Transport "TEST_RTU" connected
2026-09-25 14:20:01.404 INFO Transport Controller [TEST_RTU][rtu][ID:92] Client 'client-1' created
2026-09-25 14:20:01.651 INFO manager Task added -> task-read-holding-registers
2026-09-25 14:20:02.105 INFO ModbusClient [ID:92][FC:3] Response received +45ms
[ [ 1024, 2048 ] ]
2026-09-25 14:20:03.098 INFO ModbusClient [ID:92][FC:3] Response received +42ms
[ [ 1024, 2048 ] ]
...

Node TCP connection Example

import TransportController from 'modbus-connect/transport';

const SLAVE_ID = 92;
const TRANSPORT_ID = 'TEST_TCP';

async function main() {
  const controller = new TransportController();

  await controller.addTransport(TRANSPORT_ID, 'node-tcp', {
    host: '10.59.43.96',
    port: 502,
    readTimeout: 2000,
    writeTimeout: 1000,
    maxBufferSize: 4096,
    reconnectInterval: 5000,
    maxReconnectAttempts: Infinity,
  });

  await controller.connectTransport(TRANSPORT_ID);

  const client = await controller.createClient({
    slaveId: SLAVE_ID,
    transportId: TRANSPORT_ID,
    timeout: 3000,
  });

  await new Promise(r => setTimeout(r, 250));

  const pollingTask = {
    id: 'task-read-holding-registers',
    interval: 1000,
    fn: async () => {
      return await client.readHoldingRegisters(0, 4);
    },
    onData: data => {
      console.log(data);
    },
    onError: err => {
      console.error(err.message ?? err);
    },
  };

  controller.addPollingTask(TRANSPORT_ID, pollingTask);
}

Expected result:

phk_mvn@MacBook-Air-Danila modbus-connect % node test.js
2026-09-25 04:04:57.331 INFO Transport Controller Transport "TEST_TCP" added with PollingManager
2026-09-25 04:04:57.402 INFO Node TCP Connecting to 10.59.43.96:502...
2026-09-25 04:04:57.418 INFO Node TCP SUCCESS: Connected to 10.59.43.96:502
2026-09-25 04:04:57.419 INFO Transport Controller Transport "TEST_TCP" connected
2026-09-25 04:04:57.664 INFO Transport Controller [TEST_TCP][tcp][ID:92] Client 'client-1' created
2026-09-25 04:04:57.912 INFO manager Task added -> task-read-holding-records
[ [ 4114, 35714, 1986, 0 ] ]
2026-09-25 04:04:58.043 INFO ModbusClient [ID:92][FC:3] Response received +13ms
[ [ 4114, 35714, 1986, 0 ] ]
2026-09-25 04:04:59.041 INFO ModbusClient [ID:92][FC:3] Response received +11ms
[ [ 4114, 35714, 1986, 0 ] ]
2026-09-25 04:05:00.038 INFO ModbusClient [ID:92][FC:3] Response received +12ms
...

Web RTU (browser) connection Example

To use Modbus in the browser, you must first obtain a port using the Web Serial API. Note that this code must be triggered by a user gesture (e.g., a button click).

import TransportController from 'modbus-connect/transport';

const SLAVE_ID = 1;
const TRANSPORT_ID = 'WEB_SERIAL_RTU';

async function startModbus() {
  // 1. Request port from user
  const port = await navigator.serial.requestPort();

  const controller = new TransportController();

  // 2. Add transport with 'web-rtu' type
  await controller.addTransport(TRANSPORT_ID, 'web-rtu', {
    port, // Pass the native WebSerial port object
    baudRate: 9600,
    dataBits: 8,
    stopBits: 1,
    parity: 'none',
  });

  await controller.connectTransport(TRANSPORT_ID);

  const client = await controller.createClient({
    slaveId: SLAVE_ID,
    transportId: TRANSPORT_ID,
    timeout: 2000,
  });

  // 3. Setup Polling
  controller.addPollingTask(TRANSPORT_ID, {
    id: 'web-task-coils',
    interval: 2000,
    fn: async () => {
      return await client.readCoils(0, 8);
    },
    onData: data => {
      console.log('Coils status:', data[0]);
    },
    onError: err => {
      console.error('Web Serial Error:', err.message);
    },
  });
}

Expected result:

2026-09-25 14:25:10.058 INFO Transport Controller Transport "WEB_SERIAL_RTU" added with PollingManager
2026-09-25 14:25:10.412 INFO Web RTU WebSerial port opened successfully with new instance
2026-09-25 14:25:10.601 INFO Transport Controller Transport "WEB_SERIAL_RTU" connected
2026-09-25 14:25:10.702 INFO Transport Controller [WEB_SERIAL_RTU][rtu][ID:1] Client 'client-1' created
2026-09-25 14:25:10.855 INFO manager Task added -> web-task-coils
Coils status: [true, false, true, true, false, false, false, true]
2026-09-25 14:25:11.294 INFO ModbusClient [ID:1][FC:1] Response received +85ms
...

Emulator Node RTU / TCP connection

import TransportController from 'modbus-connect/transport';

const SLAVE_ID = 92;
const TRANSPORT_ID = 'TEST_TCP';

async function main() {
  const controller = new TransportController();
  await controller.addTransport(
    'emulator-1',
    'rtu-emulator', // or 'tcp-emulator'
    {
      slaveId: 1,
      responseLatencyMs: 30,
      initialRegisters: {
        holding: [
          { start: 100, value: 1234 },
          { start: 101, value: 5678 },
        ],
        coils: [{ start: 0, value: true }],
      },
    }
  );

  await controller.connectTransport('emulator-1');

  // Client configuration depending on the type of emulator
  const client = await controller.createClient({
    slaveId: 1,
    transportId: 'emulator-1',
    timeout: 3000,
    retryCount: 1,
  });

  // Polling task (will work with any emulator)
  controller.addPollingTask('emulator-1', {
    id: 'task1',
    interval: 1000,
    fn: async () => {
      return await client.readHoldingRegisters(100, 2);
    },
    onData: data => {
      console.log(data);
    },
    onError: err => {
      console.error('Polling error:', err.message);
    },
  });
}

Expected result:

phk_mvn@MacBook-Air-Danila modbus-connect % node test.js
2026-09-25 04:04:52.061 INFO ModbusSlaveCore ModbusSlaveCore initialized successfully (Slave ID: 1)
2026-09-25 04:04:52.062 INFO Transport Controller Transport "emulator-1" added with PollingManager
2026-09-25 04:04:52.341 INFO RTU Emulator RTU Emulator connected
2026-09-25 04:04:52.342 INFO Transport Controller Transport "emulator-1" connected
2026-09-25 04:04:52.347 INFO ModbusSlaveCore Registers added successfully: {"coils":1,"discrete":0,"holding":2,"input":0}
2026-09-25 04:04:52.401 INFO Transport Controller [emulator-1][rtu][ID:1] Client 'client-1' created
2026-09-25 04:04:52.638 INFO manager Task added -> task1
[ [ 1234, 5678 ] ]
2026-09-25 04:04:52.671 INFO ModbusClient [ID:1][FC:3] Response received +32ms
[ [ 1234, 5678 ] ]
2026-09-25 04:04:53.669 INFO ModbusClient [ID:1][FC:3] Response received +32ms
[ [ 1234, 5678 ] ]
2026-09-25 04:04:54.671 INFO ModbusClient [ID:1][FC:3] Response received +32ms
[ [ 1234, 5678 ] ]