modbus-serial

repository·main·Indexed 20 days ago

https://github.com/yaacov/node-modbus-serial

A pure JavaScript implementation of the MODBUS-RTU protocol for NodeJS, supporting both Serial and TCP. It provides a client for communicating with industrial electronic devices, robots, or Arduino-based Modbus slaves, as well as a Modbus-TCP server implementation. The library supports various function codes (FC1-FC43), typecasting via a worker class, and efficient polling of multiple data points. The serialport package is an optional dependency for serial-specific APIs.

Tokens
6.2K
Snippets
25
Records
29
Agent score
73%

What's inside modbus-serial

  1. Install modbus-serial via npm

    main

    Install the package using npm. If you encounter installation issues, you can try building from source with specific flags.

    npm install modbus-serial

    If you have problems installing, try:

    npm install modbus-serial --unsafe-perm --build-from-source
  2. Configure optional serialport dependency

    main

    The serialport package is an optional dependency. If it is not installed, you can still use TCP/UDP functionality, but serial-specific APIs will fail at runtime.

    Skip optional dependencies

    To install without serialport (smaller dependency tree, no native serial build):

    npm install modbus-serial --no-optional

    Or set optional=false in your .npmrc.

    Install serialport explicitly

    If you skipped the optional dependency but later need serial RTU/ASCII, install it manually:

    npm install serialport

    Compatibility Note

    The package targets serialport 13.x, which requires Node.js 20 or newer. For older Node.js releases, you must explicitly install a compatible major version (e.g., 12.x).

  3. Use ModbusRTU to communicate via Serial Port

    main

    The ModbusRTU class is used to perform Modbus RTU operations over a serial port. You must provide an instance of a serial port (typically from the serialport package) to the constructor.

    To use it:

    1. Instantiate ModbusRTU with your serial port.
    2. Call .open(callback) to initialize the connection.
    3. Use the writeFC* methods to perform Modbus function calls.
    4. Handle the response in the provided callback function.
    const ModbusRTU = require('modbus-serial').ModbusRTU;
    // Assuming 'port' is a valid SerialPort instance
    const modbus = new ModbusRTU(port);
    
    modbus.open((err) => {
      if (err) return console.error(err);
      
      // Example: Read Holding Registers (FC3)
      modbus.writeFC3(1, 0, 10, (err, res) => {
        if (err) return console.error(err);
        console.log(res.data);
      });
    });
  4. Example: Read and Write via Serial RTU

    main

    This example demonstrates connecting to a serial port, setting the slave ID, writing values to registers, and then reading them back.

    // create an empty modbus client
    const ModbusRTU = require("modbus-serial");
    const client = new ModbusRTU();
    
    // open connection to a serial port
    client.connectRTUBuffered("/dev/ttyUSB0", { baudRate: 9600 }, write);
    
    function write() {
        client.setID(1);
    
        // write the values 0, 0xffff to registers starting at address 5
        // on device number 1.
        client.writeRegisters(5, [0 , 0xffff])
            .then(read);
    }
    
    function read() {
        // read the 2 registers starting at address 5
        // on device number 1.
        client.readHoldingRegisters(5, 2)
            .then(console.log);
    }
  5. Example: ModbusTCP Server Implementation

    main

    This example shows how to implement a ServerTCP by providing a vector object that handles various Modbus requests (Get/Set registers and coils). The vector can handle synchronous returns, callbacks, or Promises.

    const ModbusRTU = require("modbus-serial");
    const vector = {
        getInputRegister: function(addr, unitID) {
            // Synchronous handling
            return addr;
        },
        getHoldingRegister: function(addr, unitID, callback) {
            // Asynchronous handling (with callback)
            setTimeout(function() {
                // callback = function(err, value)
                callback(null, addr + 8000);
            }, 10);
        },
        getCoil: function(addr, unitID) {
            // Asynchronous handling (with Promises, async/await supported)
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve((addr % 2) === 0);
                }, 10);
            });
        },
        setRegister: function(addr, value, unitID) {
            // Asynchronous handling supported also here
            console.log("set register", addr, value, unitID);
            return;
        },
        setRegisterMask: function(addr, andMask, orMask, unitID) {
            // Asynchronous handling supported also here
            console.log("mask register", addr, andMask, orMask, unitID);
            return;
        },
        setCoil: function(addr, value, unitID) {
            // Asynchronous handling supported also here
            console.log("set coil", addr, value, unitID);
            return;
        },
        readDeviceIdentification: function(addr) {
            return {
                0x00: "MyVendorName",
                0x01: "MyProductCode",
                0x02: "MyMajorMinorRevision",
                0x05: "MyModelName",
                0x97: "MyExtendedObject1",
                0xAB: "MyExtendedObject2"
            };
        }
    };
    
    // set the server to answer for modbus requests
    console.log("ModbusTCP listening on modbus://0.0.0.0:8502");
    const serverTCP = new ModbusRTU.ServerTCP(vector, { host: "0.0.0.0", port: 8502, debug: true, unitID: 1 });
    
    serverTCP.on("socketError", function(err){
        // Handle socket error if needed, can be ignored
        console.error(err);
    });
  6. Write data with typecasting using client.send

    main

    Use client.send to perform Modbus write operations. You can pass an array of values to the value key to write to multiple addresses.

    // Write 2 values to address: 10009 and 10011
    const response = await client.send({
        unit: 1,
        fc: 16,
        address: 10009,
        value: [10999, 10888],
        type: 'int32',
    });
  7. Configure the Worker via setWorkerOptions

    main

    The setWorkerOptions method allows you to configure the behavior of the worker class, which manages data retrieval and typecasting. You can control concurrency and debugging modes.

    const client = new ModbusRTU()
    
    // ... connect client ...
    
    client.setWorkerOptions({
        maxConcurrentRequests: 10, // limits the number of simultaneous requests sent
        debug: true
    })
  8. Read data with typecasting using client.send

    main

    Use client.send to perform Modbus read operations. The worker automatically handles typecasting to types like int32, uint32, and float. Note that requesting a type like int32 will automatically read the required number of registers (e.g., 2 registers for one int32 value).

    // Read 4 values starting from 10009 register.
    // Under the hood it will read 8 registers due to int32 type
    const response = await client.send({
        unit: 1,
        fc: 3,
        address: 10009,
        quantity: 4,
        type: 'int32',
    });
  9. Poll multiple data points using client.poll

    main

    The client.poll method optimizes multiple Modbus requests by building an efficient polling map. It can handle various function codes (fc), addresses (single or arrays), and data types.

    Key options:

    • map: An array of objects defining the registers to read/write. If type is omitted, it defaults to int16.
    • onProgress: A callback function receiving progress (a float from 0 to 1) and the data from the current request.
    • maxChunkSize: The maximum number of registers allowed per request.
    • skipErrors: If false, the polling process stops on error and returns a partial result. If true, it continues through errors.
    const response = await client.poll({
        unit: 1,
        map: [
            { fc: 3, address: [10011, 10013, 10018], type: "int32" },
            { fc: 3, address: 10003, type: "int32" },
            { fc: 3, address: 10005, type: "int32" },
            { fc: 3, address: 10007, type: "int32" },
            { fc: 3, address: 10009, type: "int32" },
            { fc: 2, address: [1,2,3]},
            { fc: 1, address: [1,2,3]},
            { fc: 1, address: 4},
            { fc: 1, address: 5},
            { fc: 1, address: 6},
            { fc: 3, address: [10001]}, // default type is int16
            { fc: 3, address: [10020, 10023, 10026], type: "float"},
            { fc: 3, address: [10030, 10034], type: "double"}
        ],
        onProgress: (progress, data) => {
            console.log(
                progress, // Poll progress from 0...1 where 1 means 100%
                data,     // Data from the current request
            );
        },
        maxChunkSize: 32,  // max registers per request
        skipErrors: false, // if false it will stop poll and return PARTIAL result
    })
  10. Configure Serial Port Options

    main

    When using serial connections (like connectRTUBuffered or connectAsciiSerial), you can pass an options object. This object is passed directly to serialport's openOptions. Default settings are 9600, 8, n, 1.

    Example of setting a custom baud rate and parity:

    client.connectRTUBuffered("/dev/ttyUSB0", { baudRate: 9600, parity: 'even' });
  11. Supported Client Connection Types

    main

    Client Serial (Requires serialport)

    • modbus-RTU: Over serial line (ModbusRTU)
    • modbus-RTU (RTUBufferedPort): Over buffered serial line
    • modbus-ASCII (AsciiPort): Over serial line

    Client TCP/UDP

    • modbus-TCP (TcpPort): Over TCP/IP line
    • modbus-RTU (UdpPort): Over C701 server or UDP to serial bridge
    • modbus-RTU (TcpRTUBufferedPort): Over TCP/IP line (buffered device)
    • modbus-RTU (TelnetPort): Over Telnet server (TCP/IP serial bridge)

    Server

    • modbus-TCP (ServerTCP): Over TCP/IP line
  12. Supported Modbus Function Codes (FC)

    main

    The following Modbus function codes are implemented by the library:

    CodeNameMethod
    FC1Read Coil StatusreadCoils(coil, len)
    FC2Read Input StatusreadDiscreteInputs(addr, arg)
    FC3Read Holding RegistersreadHoldingRegisters(addr, len)
    FC4Read Input RegistersreadInputRegisters(addr, len)
    FC5Force Single CoilwriteCoil(coil, binary)
    FC6Preset Single RegisterwriteRegister(addr, value)
    FC15Force Multiple CoilwriteCoils(addr, valueAry)
    FC16Preset Multiple RegisterswriteRegisters(addr, valueAry)
    FC22Mask Write RegistermaskWriteRegister(addr, andMask, orMask)
    FC43/14Read Device IdentificationreadDeviceIdentification(id, obj)
    CustomFCCustom FunctioncustomFunction(functionCode, data)