Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

node-drivers

jmmoser245MIT2.0.2

A layered approach to protocol drivers.

logix5000, cip, controllogix, pccc, modbus, plc, driver, protocol, tcp, udp

readme

node-drivers

A layered approach to protocol drivers.

Install

npm install node-drivers

Examples

Read and list tags from a Logix5000 processor:

const { TCP, CIP } = require('node-drivers');

const tcpLayer = new TCP('1.2.3.4');
const logix5000 = new CIP.Logix5000(tcpLayer);

console.log(await logix5000.readTag('tagname'));

/** Read an element from a 1-dimensional tag */
console.log(await logix5000.readTag('TagThatIs1DArray[0]'));

/** Read an entire 1-dimensional tag */
console.log(await logix5000.readTag('TagThatIs1DArray'));

/** Read the first 4 elements of a 1-dimensional tag */
console.log(await logix5000.readTag('TagThatIs1DArray', 4))

/**
 * Read a slice of a 1-dimensional tag
 * example returns an array containing the values of elements 3 through 7
 */
console.log(await logix5000.readTag('TagThatIs1DArray[3]', 5));

/** Read an element of a structure member of a tag */
console.log(await logix5000.readTag('tag.member[0].anothermember'));

/**
 * Read all tags scoped to a program
 * returns an object containing all of the scoped tags
 */
console.log(await logix5000.readTag('TheProgramName'));

/** Read a program scoped tag */
console.log(await logix5000.readTag('TheProgramName.tag'));

/**
 * Read a tag using the symbol instance id
 * (available in controller version 21 and above)
 */
console.log(await logix5000.readTag(2130));

/** List all global tags */
for await (const tag of logix5000.listTags()) {
  console.log(tag);
}

/** List all tags scoped to a program */
for await (const tag of logix5000.listTags('Program:Alarms')) {
  console.log(tag);
}

await tcpLayer.close();

Read/Write a tag from a PLC-5, SLC 5/03, or SLC 5/04 processor using PCCC embedded in CIP:

const { TCP, CIP, PCCC } = require('node-drivers');

const tcpLayer = new TCP('1.2.3.4');
const cipLayer = new CIP(tcpLayer);
const pccc = new PCCC(cipLayer);

/** Write an integer */
console.log(await pccc.typedWrite('N10:47', 1000));

/** Read an integer */
console.log(await pccc.typedRead('N10:47'));

/** Write a float */
console.log(await pccc.typedWrite('F8:1', 5.5));

await tcpLayer.close();

Find all EtherNet/IP devices in a subnet using the UDP broadcast address or by explicitly pinging each host:

const { UDP, CIP } = require('node-drivers');

const udpLayer = new UDP('1.2.3.255');
const eipLayer = new CIP.EIP(udpLayer);

/** Broadcast */
console.log(await eipLayer.listIdentity());


/** Explicitly */
const hosts = [];
for (let i = 1; i < 255; i++) {
  hosts.push(`1.2.3.${i}`);
}

/* hosts overrides whatever host was specified in the Layers.UDP() constructor */
console.log(await eipLayer.listIdentity({ hosts }));

await udpLayer.close();

Retrieve information from an EtherNet/IP device over TCP:

const { TCP, CIP } = require('node-drivers');

const tcpLayer = new TCP('1.2.3.4');
const eipLayer = new CIP.EIP(tcpLayer);

console.log(await eipLayer.listInterfaces());

console.log(await eipLayer.listServices());

/** no response, used to test underlying transport layer */
await eipLayer.nop();

await tcpLayer.close();

Communicate with a Modbus device over TCP:

const { TCP, Modbus } = require('node-drivers');

const tcpLayer = new TCP('1.2.3.4');
const modbusLayer = new Modbus(tcpLayer);

// read holding register 40004
console.log(await modbusLayer.readHoldingRegisters(3, 1));

await tcpLayer.close();

Drivers/Protocols

  • CIP
    • EtherNet/IP
    • Logix5000
  • PCCC
    • embedded in CIP
  • Modbus
    • TCP frame format
  • TCP
  • UDP

changelog

Changelog

2.0.2 (2020-03-14)

2.0.1 (2020-03-10)

  • Publishing again for release on github
  • Added keywords to package.json

2.0.0 (2020-03-10)

  • Published

2.0.0-beta.9 (2020-03-10)

  • CIPAttribute.Get(instance) instance parameter sets to zero if instance is null/undefined and attribute is instanceof CIPClassAttribute
  • Added encoding support for internal CIP data type TRANSFORM

2.0.0-beta.8 (2020-01-23)

  • Added CIPAttribute.Get() as a helper method for creating a GetAttributeSingle service CIPRequest
    /**
     * Creates a GetAttributeSingle service CIPRequest for retrieving the device
     * type of Identity object instance 1
     */
    const request = CIP.Core.Objects.Identity.InstanceAttribute.DeviceType.Get(1);
  • Improved CIPRequest and CIPMultiServiceRequest

    • A CIPRequest can be specified as the data handler for another CIPRequest (see ConnectionManager's UnconnectedSend method)
    • CIPMultiServiceRequest (CIPRequest.Multi) now works
    • Here is an example of two requests inside of a multi service request inside of an unconnected send `javascript const { TCP, CIP } = require('node-drivers');

    const tcpLayer = new TCP('1.2.3.4'); const cipLayer = new CIP(tcpLayer);

    /** Create an Unconnected Send request / const request = CIP.Core.Objects.ConnectionManager.UnconnectedSend( /* multi service request */ new CIP.Core.Request.Multi([

    /** two different GetAttributeSingle requests */
    CIP.Core.Objects.MessageRouter.InstanceAttribute.ObjectList.Get(1),
    CIP.Core.Objects.Port.InstanceAttribute.Name.Get(1)

    ]), /** routing out of port 1 to address 0 */ CIP.Core.EPath.Encode(true, [

    new CIP.Core.EPath.Segments.Port(1, 0)

    ]) );

    /** Use the CIP layer to send the unconnected message */ const res = await cipLayer.sendRequest(false, request);

    /** res.value is an array of response objects (2 responses in this case) */ console.log(res.value);

    await tcpLayer.close(); `

  • Fixed CIP Layer sendRequest not propagating errors

2.0.0-beta.7 (2020-01-22)

  • The Layers object exported by the package has been removed.

    /** Before */
    const { TCP } = require('node-drivers').Layers;
    
    /** After */
    const { TCP } = require('node-drivers');
  • EIP layer has been moved under CIP

    /** Before */
    const EIPLayer = require('node-drivers').EIP;
    
    /** After */
    const EIPLayer = require('node-drivers').CIP.EIP;
  • EIP layer is now automatically inserted in the layer stack by CIP layers when the lower layer is TCP or UDP.

    const { TCP, CIP } = require('node-drivers');
    
    /** If access to EIP layer is necessary: */
    const tcpLayer = new TCP('1.2.3.4');
    const eipLayer = new CIP.EIP(tcpLayer);
    const logix = new CIP.Logix5000(eipLayer);
    
    /** If access to EIP layer is not necessary: */
    const tcpLayer = new TCP('1.2.3.4');
    const logix = new CIP.Logix5000(tcpLayer);
  • CIP.PCCC and CIP.Modbus layers have been removed. CIP layer should now be directly used when forwarding PCCC embedded in CIP. Modbus over CIP will come soon.

    const { TCP, CIP, PCCC } = require('node-drivers');
    
    /** Before */
    const tcpLayer = new TCP('1.2.3.4');
    const cipPCCCLayer = new CIP.PCCC(tcpLayer); // CIP.PCCC has been removed
    const pccc = new PCCC(cipPCCCLayer);
    
    /** After */
    const tcpLayer = new TCP('1.2.3.4');
    const cipLayer = new CIP(tcpLayer); // Use CIP layer directly
    const pcccLayer = new PCCC(cipLayer);
  • Added CIPObject DecodeInstanceAttributesAll static method
  • Overall improvement of CIP core and added external access
    const CIPCore = require('node-drivers').CIP.Core;
  • Fixed EIP ListIdentity response data decoding

2.0.0-beta.6 (2019-12-30)

  • Fixed CIPRequest handling response data when length is 0
  • EPath Port Segment now encodes the pad byte ensuring the pad byte is 0 and the buffer is large enough
  • EPath DataType Segment fixed encodeSize for ARRAY and reference error when decoding abbreviated array
  • EPath Data Segment finished
  • Added unit tests for Data and DataType EPath segments
  • Logix5000.readControllerAttributes() now throws descriptive error if attribute status is not successful

2.0.0-beta.5 (2019-12-29)

  • Logix5000 fixed boolean decoding
  • Logix5000 listTags now allows async iterator style as well as callback style
    • async iterator style:
      let i = 0;
      for await (const tag of logix.listTags()) {
      i++;
      console.log(i, tag);
      if (i >= 10) {
        break;
      }
      }
    • callback style:
      let i = 0;
      logix.listTags(function(tag) {
      if (tag != null) {
        i++;
        console.log(i, tag);
        return i < 10; // return true to continue listing tags
      } else {
        // tag is null so listing is finished
      }
      });
  • Improved EIP Layer listIdentities timeout handling, it should be much faster to resolve
  • CIP added encoding and decoding for ENGUNIT data type

2.0.0-beta.4 (2019-12-23)

  • Logix5000 can read program symbols, returns an object with all scoped symbols
  • Logix5000 can now read program scoped symbols
  • Logix5000 can now determine size of single-dimension arrays and reads the entire array if elements is not specified (multidimensional array read support coming soon)
  • Logix5000 added readTemplateClassAttributes
  • UDP layer now receives default port 44818 from upper EIP layer if user or previous layer does not specify port
  • CIP added decoding for data types LREAL, LWORD, and LTIME, STRINGN, STRINGI STRUCT (formal encoding), EPATH, ARRAY
  • CIP added encoding for data types USINT, BYTE, LWORD, LREAL, STRING, SHORT_STRING, STRING2, EPATH, ARRAY, ABBREV_ARRAY, STRUCT
  • CIP Connection Slot can now be a number, a string, or a buffer
  • CIP Identity added Device Type Names
  • CIP Connection/ConnectionManager now supports LargeForwardOpen (connection size greater than 511 bytes), automatically falls back to regular ForwardOpen if device does not support LargeForwardOpen

2.0.0-beta.3 (2019-12-7)

  • Logix5000.readTag() now reads the entire array if the tag is a 1-dimensional array
    • It is still possible to only return one element or a slice of the array by specifying the elements argument and/or including the accessed element in the tagname. Here are some examples:
      • Return the first two elements
        await logix.readTag('tagname', 2);
      • Return the fourth element
        await logix.readTag('tagname[3]');
      • Return the second through the third element
        await logix.readTag('tagname[1]', 2);
  • Layers can now specify default options and pass them to lower layers
    • TCP layer now receives default port 44818 from upper EIP layer if user or previous layer does not specify port
    • TCP layer now receives default port 502 from upper Modbus layer if user or previous layer does not specify port
  • TCP layer's options argument can now just be a host string if an upper layer specifies a port in the default options (EIP and Modbus layer)
    • Before:
      const tcpLayer = new TCP({ host: '1.2.3.4', port: 44818 });
    • After:
      const tcpLayer = new TCP('1.2.3.4');
  • Fixed PCCCLayer.typedWrite() type/data parameter encoding
  • PCCCLayer.typedRead() added items parameter, allows reading multiple items
    • Examples:
      await plc5.typedRead('N7:0', 6); // Read the first 6 elements from integer file 7
      await plc5.typedRead('F8:44', 20); // Read elements 44 through 63 from float file 8
  • CIP added encoding and decoding for 8 byte integer (LINT) and unsigned integer (ULINT)
  • Logix5000.readTagAttributesAll() added ArrayDimensionLengths
  • Logix5000 fixed error descriptions

2.0.0-beta.2 (2019-12-6)

  • Logix5000 CAN NOW READ STRUCTURES 🔥
  • TCP layer automatically handles reconnects
  • Logix5000 listTags now accepts a scope (e.g. Program:SymbolName)
  • Logix5000 added data types Program, Map, Routine, Task, Cxn
  • Removed Logix5000.readTagFragmented(), it is now called automatically when needed
  • Added PCCCLayer.echo()
  • CIP ConnectionManager can now send unconnected messages - API still a work in progress

2.0.0-beta.1 (2019-11-15)

Added

  • Logix5000.readControllerAttributes
    • reads the 0xAC class instance attributes in the controller
    • Use to determine when the tags list and/or structure information has changed
  • Logix5000.readTagAttributesList
  • Logix5000.readTagAttributesAll

    Changed

  • Logix5000.readTag now accepts a tag name string, a symbol instance id, or a tag object for the tag argument

    Fixed

  • Logix5000 error description handling

2.0.0-beta.0 (2019-11-14)

Added

  • Modbus layer - one layer for all Modbus frame formats
    • TCP, RTU (future), and ASCII (future)

      Removed

  • ModbusTCP layer has been removed, use Modbus layer instead

    Changed

  • Logix5000.listTags
    • now returns an async iterator
    • structure tags now include template information
  • src structure has been simplified

1.5.4 / 2019-05-10

  • Added CIP.Connection disconnect timeout of 10000 milliseconds

    1.5.3 / 2019-05-06

  • CIP Decode returns true or false for boolean data type

    1.5.2 / 2019-05-06

  • Layer contextCallback added timeout parameter
  • CIP.Logix5000 listTags added options parameter, allowed fields:
    • timeout - timeout in milliseconds, will return tags instead of timeout error if at least one response received with tags (default 10000)

      1.5.1 / 2019-04-12

  • CIP.Logix5000 allows reading multiple elements from tags
    • e.g. logix.readTag('tagname', 2)
    • resolves an array of values if number is greater than 1

      1.5.0 / 2019-04-12

  • CIP.Logix5000 no longer requires including CIP.Connection as a lower layer.
  • CIP.Connection only connects if needed
    • e.g. getting all attributes of identity object does not require a connection