Giter Club home page Giter Club logo

node-ethernet-ip's Introduction

Vue logo

npm Gitter license Travis Coveralls github Known Vulnerabilities GitHub stars

Node Ethernet/IP

A simple and lightweight node based API for interfacing with Rockwell Control/CompactLogix PLCs.

Prerequisites

latest version of NodeJS

Getting Started

Install with npm

npm install ethernet-ip --save

The API

How the heck does this thing work anyway? Great question!

The Basics

Getting Connected

const { Controller } = require("ethernet-ip");

const PLC = new Controller();

// Controller.connect(IP_ADDR[, SLOT])
// NOTE: SLOT = 0 (default) - 0 if CompactLogix
PLC.connect("192.168.1.1", 0).then(() => {
    console.log(PLC.properties);
});

Controller.properties Object

 {
    name: String, // eg "1756-L83E/B"
    serial_number: Number, 
    slot: Number,
    time: Date, // last read controller WallClock datetime
    path: Buffer,
    version: String, // eg "30.11"
    status: Number,
    faulted: Boolean,  // will be true if any of the below are true
    minorRecoverableFault: Boolean,
    minorUnrecoverableFault: Boolean,
    majorRecoverableFault: Boolean,
    majorUnrecoverableFault: Boolean,
    io_faulted: Boolean
}

Set the Clock of the Controller

NOTE Controller.prototype.readWallClock and Controller.prototype.writeWallClock are experimental features and may not be available on all controllers. 1756-L8 ControlLogix Controllers are currently the only PLCs supporting these features.

Sync Controller WallClock to PC Datetime

const { Controller } = require("ethernet-ip");

const PLC = new Controller();

PLC.connect("192.168.1.1", 0).then(async () => {
    // Accepts a JS Date Type
    // Controller.writeWallClock([Date])
    await PLC.writeWallClock(); // Defaults to 'new Date()'
});

Set Controller WallClock to a Specific Date

const { Controller } = require("ethernet-ip");

const PLC = new Controller();

PLC.connect("192.168.1.1", 0).then(async () => {
    const partyLikeIts1999 = new Date('December 17, 1999 03:24:00');
    await PLC.writeWallClock(partyLikeIts1999); // Pass a custom Datetime
});

Reading Tags

NOTE: Currently, the Tag Class only supports Atomic datatypes (SINT, INT, DINT, REAL, BOOL). Not to worry, support for STRING, ARRAY, and UDTs are in the plans and coming soon! =]

Reading Tags Individually...

const { Controller, Tag } = require("ethernet-ip");

const PLC = new Controller();

// Create Tag Instances
const fooTag = new Tag("contTag"); // Controller Scope Tag
const barTag = new Tag("progTag", "prog"); // Program Scope Tag in PLC Program "prog"

PLC.connect("192.168.1.1", 0).then(async () => {
    await PLC.readTag(fooTag);
    await PLC.readTag(barTag);

    console.log(fooTag.value);
    console.log(barTag.value);
});

Additional Tag Name Examples ...

const fooTag = new Tag("Program:prog.progTag"); // Alternative Syntax for Program Scope Tag in PLC Program "prog"
const barTag = new Tag("arrayTag[0]"); // Array Element
const bazTag = new Tag("arrayTag[0,1,2]"); // Multi Dim Array Element
const quxTag = new Tag("integerTag.0"); // SINT, INT, or DINT Bit
const quuxTag = new Tag("udtTag.Member1"); // UDT Tag Atomic Member
const quuzTag = new Tag("boolArray[0]", null, BIT_STRING); // bool array tag MUST have the data type "BIT_STRING" passed in

Reading Tags as a Group...

const { Controller, Tag, TagGroup } = require("ethernet-ip");

const PLC = new Controller();
const group = new TagGroup();

// Add some tags to group
group.add(new Tag("contTag")); // Controller Scope Tag
group.add(new Tag("progTag", "prog")); // Program Scope Tag in PLC Program "prog"

PLC.connect("192.168.1.1", 0).then(async () => {
    await PLC.readTagGroup(group);

    // log the values to the console
    group.forEach(tag => {
        console.log(tag.value);
    });
});

Writing Tags

NOTE: You MUST read the tags first or manually provide a valid CIP datatype. The following examples are taking the latter approach.

Writing Tags Individually...

const { Controller, Tag, EthernetIP } = require("ethernet-ip");
const { DINT, BOOL } = EthernetIP.CIP.DataTypes.Types;

const PLC = new Controller();

// Create Tag Instances
const fooTag = new Tag("contTag", null, DINT); // Controller Scope Tag
const barTag = new Tag("progTag", "prog", BOOL); // Program Scope Tag in PLC Program "prog"

PLC.connect("192.168.1.1", 0).then(async () => {

    // First way to write a new value
    fooTag.value = 75;
    await PLC.writeTag(fooTag);

    // Second way to write a new value
    await PLC.writeTag(barTag, true);

    console.log(fooTag.value);
    console.log(barTag.value);
});

Writing Tags as a Group...

const { Controller, Tag, TagGroup, EthernetIP } = require("ethernet-ip");
const { DINT, BOOL } = EthernetIP.CIP.DataTypes.Types;

const PLC = new Controller();
const group = new TagGroup();

// Create Tag Instances
const fooTag = new Tag("contTag", null, DINT); // Controller Scope Tag
const barTag = new Tag("progTag", "prog", BOOL); // Program Scope Tag in PLC Program "prog"

group.add(fooTag); // Controller Scope Tag
group.add(barTag); // Program Scope Tag in PLC Program "prog"

PLC.connect("192.168.1.1", 0).then(async () => {
    // Set new values
    fooTag.value = 75;
    barTag.value = true;

    // Will only write tags whose Tag.controller_tag !== Tag.value
    await PLC.writeTagGroup(group);

    group.forEach(tag => {
        console.log(tag.value);
    });
});

Lets Get Fancy

Subscribing to Controller Tags

const { Controller, Tag } = require("ethernet-ip");

const PLC = new Controller();

// Add some tags to group
PLC.subscribe(new Tag("contTag")); // Controller Scope Tag
PLC.subscribe(new Tag("progTag", "prog")); // Program Scope Tag in PLC Program "prog"

PLC.connect("192.168.1.1", 0).then(() => {
    // Set Scan Rate of Subscription Group to 50 ms (defaults to 200 ms)
    PLC.scan_rate = 50;

    // Begin Scanning
    PLC.scan();
});

// Catch the Tag "Changed" and "Initialized" Events
PLC.forEach(tag => {
    // Called on the First Successful Read from the Controller
    tag.on("Initialized", tag => {
        console.log("Initialized", tag.value);
    });

    // Called if Tag.controller_value changes
    tag.on("Changed", (tag, oldValue) => {
        console.log("Changed:", tag.value);
    });
});

Demos

  • Monitor Tags for Changes Demo

Simple Demo

const { Controller, Tag } = require("ethernet-ip");

// Intantiate Controller
const PLC = new Controller();

// Subscribe to Tags
PLC.subscribe(new Tag("TEST_TAG"););
PLC.subscribe(new Tag("TEST", "Prog"););
PLC.subscribe(new Tag("TEST_REAL", "Prog"););
PLC.subscribe(new Tag("TEST_BOOL", "Prog"););

// Connect to PLC at IP, SLOT
PLC.connect("10.1.60.205", 5).then(() => {
    const { name } = PLC.properties;

    // Log Connected to Console
    console.log(`\n\nConnected to PLC ${name}...\n`);

    // Begin Scanning Subscription Group
    PLC.scan();
});

// Initialize Event Handlers
PLC.forEach(tag => {
    tag.on("Changed", (tag, lastValue) => {
        console.log(`${tag.name} changed from ${lastValue} -> ${tag.value}`);
    });
})

Built With

Contributers

Related Projects

Wanna become a contributor? Here's how!

License

This project is licensed under the MIT License - see the LICENCE file for details

node-ethernet-ip's People

Contributors

cmseaton42 avatar ericvicenti avatar jhenson29 avatar mudrekh avatar patrickjmcd avatar pcnate avatar pfumagalli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-ethernet-ip's Issues

tag.generateReadMessageRequest is not a function

When trying to read a tag and it's value, this error message is generated:

tag.generateReadMessageRequest is not a function

The error refers to ethernet-ip/src/controller/index.js
In the read and write tag methods at appx lines 480 and 513.

const MR = tag.generateWriteMessageRequest(value, size);

Unable to determine why this is happening.

Memory leak from not removing listeners on promise reject

Event listeners added inside of a promise are not removed on reject creating a memory leak.
(node:2012) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 Multiple Service Packet listeners added. Use emitter.setMaxListeners() to increase limit

Current Behavior

Repeated calls create new listeners without removing the old ones.

Expected Behavior

...not leak memory.

Possible Solution (Optional)

Event listener should be removed prior to rejecting.
Possibly just always remove listener as fist step in listener callback.

Context

Steps to Reproduce (for bugs only)

  1. Subscribe to non-existent tag.
  2. Wrap contoller.scan() in a try/catch.
  3. Wrap try/catch in while(true){}

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Question: Can I get tag descriptions?

In newer PLCs (version >20) there is a description field in RXLogics, that saves to the PLC. Is there a way to retrieve the tag comments/description with this library?

Note: I may interchange in this issue the words description and comment, as we refer to them as comments but in RSLogix it is called description

Current Behavior

On the tag properties, currently, there is not a description field. That would contain a comment/name for the tag.

Expected Behavior

When querying a tag, it would have a field property for the tag description.

Context

Most of the PLCs I work with nowadays have tag comments on almost all of the tags. When using this library sometimes I would need to see what the comment is for that tag. Currently, I have to find the tag and then hunt it down in RSLogix to find the description. However, when I am in the field this can be quite cumbersome.

Allow use of Fully-Qualified Domain Name instead of IP Address

Instead of only allowing an IP address, allow the use of a FQDN for retrieving data.

Current Behavior

Allows more options for PLC addresses.

Expected Behavior

use PLC.connect("1.location.address.plc.com", 0)... as well as PLC.connect("192.168.1.10", 0)

plc5

would this work with plc5s?

Unable to read from Control Logix PLC

Unable to read anything from a control logic PLC.
Unable to display controller properties, unable to read a tag value.

Current Behavior

When connecting to PLC, an UnhandledPromiseRejectionWarning is given.
I am able to ping the PLCs IP. (EN2TR card) I am able to go online with Studio 5000 programming software.
PLC is in Run mode.

Expected Behavior

Connection to PLC should complete, and plc properties would be written to console

Steps to Reproduce

  1. Download current version of Node and install. node-v9.9.0-x64
  2. Create a project directory and install node-ethernet-ip ("npm install ethernet-ip --save")
  3. Create plc01.js file with sample connection code
  4. Run node plc01.js
  5. Receive error on console

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
    [email protected]
    [email protected]
  • Node Version (Use node --version - e.g. 9.8.0):
    v9.9.0
  • Operating System and version:
    Windows 7 - SP1 - x64
  • Controller Type (eg 1756-L83E/B):
    1756-L75
  • Controller Firmware (eg 30.11):
    28.012

4 slot rack, plc is in slot 0. EN2TR cards in slot 1 and slot 2. Slot 3 is open.

Error output

(node:14792) UnhandledPromiseRejectionWarning: #
(node:14792) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:14792) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Test Code

const { Controller } = require("ethernet-ip");
const PLC = new Controller();
// Controller.connect(IP_ADDR[, SLOT])
// NOTE: SLOT = 0 (default) - 0 if CompactLogix
PLC.connect("10.115.45.55", 0).then(() => {
 console.log("Connected to PLC!");
 console.log(PLC.properties);
});

No support for tags or type STRING, ARRAY, or LINT.

Current Behavior

Currently, there is no support for tags of type String, Array, or LINT.

Expected Behavior

Proposed API Usage

Array

const arr = new Tag("ArrayName[1:5]"); // To read elements 1 through 5

String

const arr = new Tag("StringName");

Lint

const arr = new Tag("LintName");

Your Environment

  • Package version (Use npm list - e.g. 1.0.6): latest
  • Node Version (Use node --version - e.g. 9.8.0): 9.10.0
  • Operating System and version: win7x64
  • Controller Type (eg 1756-L83E/B): n/a
  • Controller Firmware (eg 30.11): n/a

Decimal precision is not correct

Current Behavior

Returns extra decimal places that do not exist in PLC

Expected Behavior

Return exact value as exists in PLC

Possible Solution (Optional)

Unknown...

Context

Values are not true to what exist in the PLC.

Steps to Reproduce (for bugs only)

  1. Set a value of 0.12345679 in the PLC.
  2. ethernet-ip returns 0.12345679104328156

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
    [email protected]

  • Node Version (Use node --version - e.g. 9.8.0):
    v12.18.2

  • Operating System and version:
    Ubuntu 18.04

  • Controller Type (eg 1756-L83E/B):
    1756-L73 or 1769-L18ER/B

  • Controller Firmware (eg 30.11):
    30.11 or 27.11, respectively

[FEATURE REQUEST] support tag enumeration

I'm not very familiar with Ethernet/IP, CIP, etc. but I know that some Rockwell/AB PLCs can enumerate - send a list of - all the tags that are available, along with some information about each tag. I know rslinx can do this. I'd like my stand-alone applications to be able to do it too.

Current Behavior

Expected Behavior

Seems like this would be a new Controller method that actively enumerates tags (maybe with some filtering options) and returns an array or object representing a tag collection.
In the simplest case, after connecting to a PLC, you'd call getTagList or getSymbolList(options) - it would return an object similar to a TagGroup but with more info per tag.

Possible Solution (Optional)

Would be very happy to contribute code, if I can figure out the code and how to integrate it.

Context

My company uses relatively lightweight edge devices running either Windows or Linux, and we distribute these to multiple customers. We encounter a variety of PLCs running a wide variety of programs, some well documented, some not at all. We prefer not to go on-site if we don't have to, and we don't want a complex or labor intensive setup process. I'd prefer not to rely on (or install) bulky or platform-dependent tools (not naming names but rslinx and CCW).
I want the ability to 'browse' a PLC in the field with platform-independent node code, automatically, given only its IP address.

Your Environment

  • Operating System and version: Windows 7 and 10, Linux (stripped down Ubuntu).
  • Controller Type (eg 1756-L83E/B): multiple, but often 1769's

BOOL is always read as False

Current Behavior

PLC.connect("192.168.0.2", 0).then(async () => {
    const fooTag = new Tag("BoolTag");
    setInterval(async function(){
        await PLC.readTag(fooTag);
        console.log(fooTag);
    }, 2500);
});

Expected Behavior

Any BOOL tag I attempt to read is always false.

Possible Solution (Optional)

Context

Cannot read bool values, I have updated library.

Steps to Reproduce (for bugs only)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6): 1.2.0
  • Node Version (Use node --version - e.g. 9.8.0): v10.0.0
  • Operating System and version: Windows 7
  • Controller Type (eg 1756-L83E/B): 1769-L33ERMS/A
  • Controller Firmware (eg 30.11): 30.11

Changing PLC Scan Rate has no effect

Changing PLC Scan rate has no effect.
Tags are scanned as fast as possible

Current Behavior

Tags are scanned as fast as possible

Expected Behavior

Tags should be scanned every X ms

Possible Solution (Optional)

ethernet-ip\src\controller\index.js
line 433 - should be "await delay(this.state.scan_rate);"

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
    ethernet-ip 1.1.1
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Reading of some AOI tags throws a generalStatusCode:30 error

When reading some tags from custom made Add-On Instruction on PLC.scan() it throws an error.

My Code

const PLC = new Controller();

const tags = [
    new Tag("test_duty_cycle.EnableIn"),
    new Tag("test_duty_cycle.I_RunRequest")
];

PLC.connect("192.168.98.1", 0).then(() => {
    for (let t of tags) {
        PLC.subscribe(t);
    }
    PLC.scan().catch((err) => {
         console.log(err);
    });
});

Error

{"generalStatusCode":30,"extendedStatus":[]}"
Which says Embedded service error and no more explanation in the protocol literature.

My AOI

Can be found here DUTY_CYCLE.L5X

Current Behaviour

When my code reads test_duty_cycle.EnableIn it's ok, but reading test_duty_cycle.I_RunRequest throws the error.

Question

Is it a bug or I am missing something?

My Environment

  • ethernet-ip Version: 1.2.5
  • Node Version: v10.16.0
  • RSLogix version: V20.05.00 (CPR 9 SR 10)

Unable to connect to CompactLogix PLC

When using the code in "Getting Connected", I'm getting an error while attempting to read PLC properties.

My code

const { Controller } = require("ethernet-ip");

const PLC = new Controller();

// Controller.connect(IP_ADDR[, SLOT])
// NOTE: SLOT = 0 (default) - 0 if CompactLogix
PLC.connect("192.168.1.11", 0).then(() => {
    console.log(PLC.properties);
}).catch(err => console.log(err));

Error message

{ generalStatusCode: 8, extendedStatus: [] }

Environment

node --version
v8.5.0
  • PLC: CompactLogix 1769-L18ER-BB1B, V30.11

It looks like the issue is being caused by the readWallClock function not being supported by CompactLogix PLC's

Custom Queue length

Current Behavior

The current limit for any given queue is the default defined in task-easy

Expected Behavior

Pass any queue length so projects controlling a larger set of data can work.

Possible Solution (Optional)

Define opts in controller constructor and instantiate queues with the passed in parameter.

Context

We are trying to poll larger subset of data individually, and would like to be able to add more than 100 points to the task queue.

TCP connection leaking on closing connection

Current Behavior

When calling the (undocumented) Controller.destroy() function on an unconnected session, the connection and the controller's instance don't get actually destroyed, leaking the instance itself a TCP socket handle (including its file descriptor). When done multiple times, this leads to a file descriptor starvation, causing any subsequent I/O on the whole process to fail with EMFILE.

Expected Behavior

Calling Controller.destroy() should completely destroy the instance, without leaking any instance nor file descriptors

Possible Solution (Optional)

The root cause is that Controller.destroy() tries to write a unregister session packet before actually destroying the underlying socket. When the packet can't be written (like when the connection hasn't completed in the first place), the socket doesn't get destroyed.
Some possible solutions:

  1. Timing out the disconnection: We could setup a timeout for the callback of the disconnection packet src/enip/index.js#L201, so that, if it doesn't get called in x ms, we'd time out and destroy the socket anyway.
  2. Implementing a close() function: The Socket.destroy() function, according to the Node.JS API, is meant to be called when we want to ultimately kill the socket. The Socket.close() is meant to gracefully close the connection. This node could follow the standard having a Controller.close() for gracefully closing the connection (like writing the unregister session packet and sending TCP FIN), and letting Controller.destroy() to forcefully destroy the socket and connection. This has been discussed on #53. Downside is breaking the current API.

Context

This issue has surfaced on node-red-contrib-cip-ethernet-ip when connecting to a PLC that is not always online. When the PLC is offline, the above mentioned issue happens, but as the node keeps trying to connect to the PLC (in case it is back online), a lot of file descriptors are leaked, leading to a file descriptor starvation and compromising the whole Node-RED process.

Steps to Reproduce (for bugs only)

  1. Create a new instance of Controller, connecting to a dummy IP address (so that it doesn't connect)
  2. When the connection fails, destroy the Controller instance and try again.
  3. Watch the number of file descriptors being used increase (e.g. by watching /proc/xxxx/fd, where xxxx is the PID of the node.js process)
  4. The increase of TCP, TCPWrapper and Controller instances can also be checked by using Chrome's Developer Console.

Your Environment

  • Package version (Use npm list - e.g. 1.0.6): 1.2.5
  • Node Version (Use node --version - e.g. 9.8.0): 10.15.3
  • Operating System and version: Linux 4.19.42-v7+
  • Controller Type (eg 1756-L83E/B): irrelevant
  • Controller Firmware (eg 30.11): irrelevant

Can't find this module

I can't find it when I search for it in Manage palette. When downloading it using npm, it isn't shown anywhere?

while tag is "Ver.motoer.I" ,error Tag is not string

src/tag.js
const regex = /^[a-zA-Z_][a-zA-Z0-9_]*([a-zA-Z0-9_]|[\d+])$/i;

Current Behavior

Expected Behavior

Possible Solution (Optional)

Context

Steps to Reproduce (for bugs only)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Length failing to return on Tag Group

.length() is not returning the number of groups within tag

Current Behavior

As above (node-ethernet-ip/src/tag-group/index.js)

Expected Behavior

Length of group should be returned

Possible Solution (Optional)

Change (Line 31) return Object.keys(this.tags).length; to return Object.keys(this.state.tags).length;

Detect disconnection

Provide a disconnect event handler

Current Behavior

Disconnecting the PLC from the network does not get handled natively with the library. Attempting to read or write to it causes timeouts.

Expected Behavior

It would be nice to provide a disconnect callback to make it easier cleanup old connections and attempt to reconnect.

Possible Solution

    PLC.disconnect().then( () => {
      // do some connection cleanup, report offline, attempt reconnect etc
    });

Context

In order to detect whether the PLC is online, have to check for TIMEOUT errors thrown by _readTag, _writeTag, _readTagGroup, and _writeTagGroup but this requires actually reading or writing something. Some sort of defined watchdog type monitoring of the connecting would make code more concise and easier to understand.

High PLC CPU utilization and large number of connection errors.

I am loadtesting the library to a CLX L35E. I am subscribing to 1000 integer tags and 1000 float tags at 250ms scan rate to test the load capabilities of the system. I am simultaneously running multiple instances of this nodejs script to test multiple connections to the tags and compare to connections from native DASABCIP drivers in Wonderware.

I am observing the L35E's CPU utilization to be around 75% with 4 simultaneous nodejs scripts running, each one subscribed to 1000 dint and 1000 real tags.

I am comparing this with a production PLC being polled by Wonderware DASABCIP, for around 1000 tags of dint, bool, and float. The production plc is running at around 75% utilization also, but with 16 simultaneous connections (vs 4 simultaneous nodejs tasks).

Ethernet-ip library latency is very high. Even at a scan rate of 250ms, the subscription notifications only happen around every 5 seconds, even though the tags are being changed on the test plc every 100ms (so for every scan, they should have different values).

After testing for a couple of hours, the PLC's status page reports many thousands of Conn Opens and Open Errors as well as Conn Timeouts. When compared to the production PLC (running for many months with no power cycle), only a few hundred of each of these are present. See the two screenshots below.

I think the library is dropping and re-connecting too frequently. IMO the TCP connections should be left open permanently while the script is running, only reinstated if a connection error occurs. The overhead of re-establishing these connections may be introducing the aforementioned latency and higher CPU utilization. I'm not sure, however, why there are so many Conn Errors being reported, unless the old TCP connections aren't being torn down properly.

Here's a screenshot of the test PLC's status, after running tests for only a couple hours:
image

Here is the live PLC, running many months with 16 simultaneous connections serving at least 1000 tags via DASABCIP driver:
image

Please let me know how I can reduce the connection errors and improve latency in supporting around 1000 to 2000 tags without killing the PLC's CPU.

Thanks!

TimeOut Error

Hi Everbody;
I am trying to read tags from Allen Bradley Micro 850 PLC with node-red-contrib-cip-ethernet-ip node
but I am taking this error : Error connecting to PLC: Error: TIMEOUT occurred while reading Controller Props. How Can I solve this problem.

bool read bug

bool var not right

Current Behavior

in tag/index.js this.controller_value = data.readUInt8(2) === 0x01 ? true : false;

i test while the bool is true ,the return data is <buff 00 ff>
so this.controller_value = data.readUInt8(2) === 0xff ? true : false;

Expected Behavior

Possible Solution (Optional)

Context

Steps to Reproduce (for bugs only)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Keep values current even if value hasn't changed.

Add a parameter to the Tag class to allow values to be updated even if the value has not changed.

Current Behavior

Currently, if a tag value is initialized or changed, the Tag emits an Initialized or Changed event. If values are being graphed and the value has not changed, this could result in graphs looking really weird. It also could seem to the user that the data connection has been broken.

Expected Behavior

In order to keep values current, implement a KeepAlive event to periodically (but infrequently) update the stored value of the tag based on a keepAlive property of the tag.

Possible Solution (Optional)

Implement a KeepAlive event to periodically (but infrequently) update the stored value of the tag based on a keepAlive property of the tag.

Context

Graphs that include values that don't change look weird.

Wrong comparison reading Atomic BOOL

Current Behavior

I am trying to read from a Logix5572 (firmware 31.011) and when reading an Atomic BOOL, I see that the value returned by the PLC in the data buffer at index 2 is either 0x00 for false or 0x01 for true.

In line 426 of src/tag/index.js the code does a hard comparison matching only 0xff to true

This also seem to be the value written to the PLC in lines 551/552 of src/tag/index.js

Expected Behavior

I personally think that any non-zero value should be considered as true.

Possible Solution (Optional)

Change line 426 of src/tag/index.js of from:

this.controller_value = data.readUInt8(2) === 0xff ? true : false;

to

this.controller_value = data.readUInt8(2) !== 0;

A pull request is available as #30

Context

We are reading whether some tags in the PLC are either true or false, and the value returned by the library does not change when the PLC value changes

Steps to Reproduce (for bugs only)

  1. Define a BOOL value in the PLC (currently doing this with Logix Designer studio 5000)
  2. Change the value of said value from 0 to 1 and vice versa
  3. The library always returns false

Your Environment

  • Package version 1.2.0
  • Node Version 8.11.4
  • Operating System and version: Ubuntu Linux 18.04.01
  • Controller Type: Logix5572
  • Controller Firmware: 31.011

Batch Expectations of writeTagGroup?

We have some code to send a group of tag values:

// fooTag and barTag have been previously defined
fooTag.value = 123;
barTag.value = 456;
const outputGroup = new TagGroup();
outputGroup.add(fooTag);
outputGroup.add(barTag);
await plc.writeTagGroup(outputGroup);

In our PLC, we expect that foo and bar have both been changed for the same scan. But occasionally it seems that one tag is applied in one scan, and the other is applied later.

Again, this issue is flaky.. it seems that both tags usually get applied on the same scan, but every once in a while, it seems they don't.

Is this the expected behavior of Ethernet IP? Or are tag write groups supposed to be batched properly? Is there any way to write several tags to the PLC that will be picked up on the same scan?

Tag Names starting with "_" are considered invalid

Current Behavior

Tag names that start with an underscore (often used for bringing important tags to the top of the controller tag list) throw an error Error: Tagname Must be of Type <string>

Context

Reading tags such as "_Rev" or "_dt"

Steps to Reproduce (for bugs only)

const fooTag = new Tag("_dt");

Question: is it possible to use without PLC

I have a Keyence Vision system that I am trying to grab data off of (just one result number for now). I am hoping to do this without needing to attach the keyence system to a PLC, and instead just grab the data directly from my Linux machine. Is this possible with this library?

On the keyence side, looking at the EtherNet/IP configurations, it indicates that it is disconnected from the PLC, which I would expect, but I was hoping there was some way I could configure my Linux machine (running Ubuntu 16.04) to pull the data out without needing to also connect to the PLC (which we don't have, running this mostly for research purposes, not heavy-weight industrial use, yet)

Is this Possible?

Tag Group Length not working anymore

Tag Group Length is not working

Current Behavior

Returns undefined

Expected Behavior

Should return length

Possible Solution (Optional)

Change this
get length() {
return Object.keys(this.tags).length;
}
To this
get length() {
return Object.keys(this.state.tags).length;
}

Context

Recent update has stopped program working

Error: TIMEOUT occurred while reading Controller Props. - Micrologix 1100

I cannot connect to a Micrologix 1100

Current Behavior

Any operation after connect fails. Failure happens in connect.then() (therefore apparently upon successful connection).

Message is
Error: TIMEOUT occurred while reading Controller Props.
happening in
await this.readControllerProps()
on controller/index.js, line 125

Changing the slot number does not modify the result.

Port 44818 is open

$ nmap -Pn -p 44818 192.168.150.44
Starting Nmap 7.80 ( https://nmap.org ) at 2020-10-26 19:01 PDT
Nmap scan report for 192.168.150.44
Host is up (0.0044s latency).

PORT      STATE SERVICE
44818/tcp open  EtherNetIP-2

Nmap done: 1 IP address (1 host up) scanned in 0.06 seconds

Steps to Reproduce

const { Controller } = require("ethernet-ip");

const PLC = new Controller();

PLC.connect("192.168.150.44", 0)
    .then(() => {
        console.log(PLC.properties);
    })
    .catch((error) => {
        console.log(error);
    });

Environment

[email protected]
Node Version v14.13.1
MacOS
Micrologix 1100 1763 L16AWA B/14.00

Add worker to better handle concurrent requests

Expected Behavior

Calling readTagGroup/writeTagGroup while scanning or calling multiple readTag/writeTag methods in a row without the use of ASYNC/AWAIT will be handled appropriately without the chance of tags being assigned the wrong values by mistake.

Current Behavior

Currently, If a user were to do something like the following...

const group = new TagGroup();
group.add(new Tag("dint1"));
group.add(new Tag("dint2"));

const PLC = new Controller();
PLC.subscribe(new Tag("dint3"));

PLC.connect("192.168.1.1").then(() => {
    PLC.scan();
    PLC.readTagGroup(group);
});

or...

const tag1 = new Tag("dint1");
const tag2 = new Tag("dint2");

PLC.connect("192.168.1.1").then(() => {
    PLC.readTag(tag1);
    PLC.readTag(tag2);
});

then there is a chance that the Controller class can step on other outgoing network requests due to the async nature of js as well as the non-descriptive structure of the CIP Read Tag Service network responses. This could cause the tags to be assigned wrong values.

Possible Solution

Offload Read/Write responsibilities to a worker to synchronously run the requested tasks. This could be implemented as a priority queue (giving call priority to requests originating to the scan group).

Environment

  • Package version: 1.0.6
  • Node Version: 9.8.0
  • Operating System and version: Windows 10 x64
  • Controller Type: 1756-L83E/B
  • Controller Firmware: 30.11

Process crash when using destroy methods

Current Behavior

On a specific devices or/and particular network failures , use the destroy method to ends the devices connection will end all the process

10 Jan 11:09:35 - Error: This socket has been ended by the other party
    at Controller.writeAfterFIN [as write] (net.js:407:14)
    at Controller.destroy (/home/aurelien/repos/braincubeiot2-contrib-ethernetip/.bin/red/ethernetip.js:1118:14)
    at destroyPLC (/home/aurelien/repos/braincubeiot2-contrib-ethernetip/.bin/red/ethernetip.js:8162:31)

Expected Behavior

Normally when a socket is gone don't try to write something on it.

Possible Solution (Optional)

Apply this changement in your destroy method like that

destroy(exception) {
        const { unregisterSession } = encapsulation;
        if(this.destroyed) return ;
        this.write(unregisterSession(this.state.session.id), () => {
            this.state.session.established = false;
            super.destroy();
        });
    }

Context

My connection pass through a proxy and fucked up some times. And close connections.

Steps to Reproduce (for bugs only)

  1. Make a connection to your plc
  2. Disconnect it mecanicly
  3. Apply the destroy method
  4. See whats happened !

Your Environment

  • Package version (Use npm list - e.g. 1.0.6): 6.9.0
  • Node Version (Use node --version - e.g. 9.8.0): 12.2.0
  • Operating System and version: linux debian 10
  • Controller Type (eg 1756-L83E/B): 1756-L83
  • Controller Firmware (eg 30.11): 30.12

Reading tags-UDTs

Good morning
I have read on the npm website that you will soon be able to read tags for UDTs.
My question is when this functionality will be active or if there is at this time any beta version that includes this.
My question is when this functionality will be active or if there is at this time any beta version that includes this.
Thanks for your help

Add an option to configure the timeout of "Unconnected Send" messages

When building an "Unconnected Send" message, there's no way of controlling the value of the timeout parameter. The function defaults to 2000ms, but the call doesn't allow us to customize it

Current Behavior

Currently, all connected messages are send with the fixed timeout of 2000 ms

Expected Behavior

The library user should be able to set other values to it

Possible Solution (Optional)

We could add a new constructor option that would control the timeout value, and defaulting it to the current 2000ms value

Context

We'd like to make node-red-contrib-cip-ethernet-ip (and therefore this library too) compatible with Omron controllers. There's even an issue there about it.

Last week, a very nice guy at Omron have done some tests and have identified that Omron controllers need this value to be at least 5024ms. So, if we can make this value configurable, we could add support to Omron controllers as well.

Read/Write Tags/Groups error friendly await

Read/Write Tags/Groups should be more await friendly by allowing const [ error, data ] = PLC.readTag( tag )

Current Behavior

You can currently await any of these functions but if an error happens, it throws an uncaught promise error which are pretty annoying to track down. The workaround is to write a wrapper for each function that returns a promise and catch errors yourself which makes the current level of support for await pointless.

Expected Behavior

You should be able to handle all errors like so:

const [ error, data ] = PLC.readTag( tag )
if ( error ) {
    // handle the error, log error, break you current block, etc
}

Another option could be to pass an error callback but... or an options object to control return structure

Possible Solution (Optional)

Each of these functions should contain a then and a catch and both should return something and not cause unhandled promise rejection errors. Returning [ false, data ] could be a breaking change however so a callback could be added to be executed on the catch block.

I currently use await-to-js to "awaitify" these functions.

Context

Trying to use async await much more than I used to and encountering unexpected issues or forgetting to use helper functions.

Steps to Reproduce (for bugs only)

  1. Use any await example from the README
  2. Try it on a tag that doesn't exist or throws some other error
  3. Program dies

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Micro800 Support

Provide support for accessing Micro800 PLCs

Current Behavior

The system is currently unable to connect to a Micro800 PLC. The connection times out when attempting to access the PLC.

Expected Behavior

Reading/writing data for Micro800 PLC's should work exactly the same as CLX and CPLX

Possible Solution

Determine what connection parameter changes need to occur and modify or extend the PLC class to accommodate changes.

Context

This will broaden support for AB PLC's to include the Micro800 line of PLCs.

What is the data format for STRING ?

I've tried to implement the STRING dataformat myself, as I need it.
I was filling up the buffer as a null-ending string , however, the data is not sent correctly.
Is there a specific data format for STRING ?

Tag.value is undefined after writing to PLC

After writing a tag to the PLC, tag.value becomes undefined.

Current Behavior

Tag value get set to undefined instead of the value it had been set to.

Expected Behavior

Tag value should remain what it was set to.

Possible Solution (Optional)

tag.unstageWriteRequest references this.state.controllerValue instead of this.state.tag.controllerValue
This is the source of the undefined value.

Context

Steps to Reproduce (for bugs only)

  1. plc.writeTag(tag);
  2. console.log(tag.value)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Uncaught Index Out of Range Error

Current Behavior

RangeError: Index out of range (Most recent call first)
at checkOffset (buffer.js line 977 col 11)
at Uint8Array.Buffer.readUInt16LE (buffer.js line 1023 col 5)
at Object.header.parse.buf [as parse] (/opt/losant/node_modules/ethernet-ip/src/enip/encapsulation/index.js line 227 col 26)
commandCode: buf.readUInt16LE(0),
at Controller._handleDataEvent (/opt/losant/node_modules/ethernet-ip/src/enip/index.js line 240 col 41)
const encapsulatedData = header.parse(data);
at emitOne (events.js line 116 col 13)
at Controller.emit (events.js line 211 col 7)
at addChunk (_stream_readable.js line 263 col 12)
at readableAddChunk (_stream_readable.js line 250 col 11)
at Controller.Readable.push (_stream_readable.js line 208 col 10)
at TCP.onread (net.js line 601 col 20)

Expected Behavior

Possible Solution (Optional)

Add a check maybe.

Context

Interrupts multiple reads.

Steps to Reproduce (for bugs only)

Random occurrence, not exactly sure how to reproduce (know that sucks). In a three second loop reading a Group of Tags.

Your Environment

  • Package version: yarn 1.12.3, npm 6.4.1
  • Node Version: 10.14.2
  • Operating System and version: Ubuntu 16.04 AMD64
  • Controller Type (eg 1756-L83E/B): 1769-L16ER/B LOGIX5316ER
  • Controller Firmware (eg 30.11): 31.11

Add support for specifying non standard EtherNet/IP port number

Add support for being able to add port number to IP address in Controller connect method.

Current Behavior

Port number is set to 44818 and cannot be changed.

Expected Behavior

Optionally specify port after IP address in connect method.
e.g. plc.connect('192.168.1.100:5000');

Possible Solution (Optional)

Parse connect IP address string and check for port number. Use port number passed for eip connect. Default to 44818 if no port provided (no change to current operation).

Context

Mapping different ports on a WAN to different PLCs on a LAN.

Steps to Reproduce (for bugs only)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Changed Event is missed for previously written tags

Changed Event is missed for previously written tags.

Current Behavior

When a tag is written to the PLC, in the following read cycles is no Changed Event produced.

Expected Behavior

A Changed Event is also produced for tags previously written to the PLC.

Possible Solution (Optional)

In file /src/tag/index.js remove line (574):

    unstageWriteRequest() {
        const { tag } = this.state;
        tag.stage_write = false;
        //tag.controllerValue = tag.value;
    }

Context

If node-ethernet-ip is used in Node-RED and eth-ip in and eth-ip out nodes are connected to the same PLC tag, the value is properly written to the PLC but cannot be read back.
The reasons for this behaviour are:

  1. In unstageWriteRequest the value of tag.controllerValue is already updated to the new value.
  2. In controller_value the comparison (line 239) if (newValue !== this.state.tag.controllerValue) does not see a difference and there no Changed Event has been emitted.
  3. In Node-RED no message is generated at eth-ip in node.

Steps to Reproduce (for bugs only)

  1. In Node-Red, create a small flow consisting in: numeric to eth-ip in and eth-ip out to text. Both eth-ip nodes are linked to the same PLC DINT tag. Screenshot:
    Flow
  2. Run it and change the value of numeric node (input) on the dashboard.
  3. Check if the value for the text node (output), it does not change.
    Dashboard

Your Environment

  • Package version (Use npm list - e.g. 1.0.6): 1.2.5
  • Node Version (Use node --version - e.g. 9.8.0): 12.16.2
  • Operating System and version: Linux raspberrypi 4.19.97-v7+
  • Controller Type (eg 1756-L83E/B): 1769-L32E
  • Controller Firmware (eg 30.11): 20.14

I added the ability to read strings by modifying 'parseReadMessageResponse(data)' method. I add this to the case/ switch: case 672: this.controller_value = data.toString('ascii', 8); break;

Current Behavior

Expected Behavior

Possible Solution (Optional)

Context

Steps to Reproduce (for bugs only)

Your Environment

  • Package version (Use npm list - e.g. 1.0.6):
  • Node Version (Use node --version - e.g. 9.8.0):
  • Operating System and version:
  • Controller Type (eg 1756-L83E/B):
  • Controller Firmware (eg 30.11):

Ambiguous resource closing and examples do not close after completion.

After calling PLC.connect, when the promises have completed, the application stays open.

The Readme doesn't indicate to expect this behavior, nor does it seem to indicate how to properly close open connections.

I tried PLC.destroy, PLC.destroySoon, PLC.end, and PLC.close. I either got errors (no function or promise rejection), or the API continues to run in the background. I tried methods inside the callback and in .finally(()=.{})

I looked through the source file for controller and EINC, I could not see a standout example of explicitly closing a socket, and recommendations for TCP sockets didn't seem to work either.

Current Behavior

When running the info or read examples from the Readme, the console does not return to prompt when all code is complete.

Expected Behavior

The connection should close when the callback is complete, or the method for properly closing a connection should be explicitly documented.

Possible Solution (Optional)

  • Document the correct procedure and considerations for shutting down the Controller.
  • Have a PLC.once(addr, socket, callback) method, which closes after the callback is executed.
  • Have a PLC.close() method that properly shuts down the scanner, workers, tcp, etc from inside and outside the callback, allowing the application to complete after the callback returns, without triggering any errors.

Context

I'm trying to bypass the vendor locked in applications we currently rely on to generate some time-series logs of important metrics. I'm in the stage of subscribing to the proper tags, and streaming the information to the console, in order to verify it before forwarding it to some sort of datastore. Noticed this issue immediately, and am concerned about the proper way to handle in order to prevent resource leaks on production machines.

Your Environment

  • Package version 1.2.3
  • Node Version 10.16.0
  • Operating System and version: Win 7 x64
  • Controller Type (eg 1756-L83E/B): 1769-L24ER-QBFC1B/A LOGIX5324ER
  • Controller Firmware (eg 30.11): 26.13

READING ERRORS

I am trying to read DINT values, but sometimes these values ​​are incorrect, how can I know if a value I am reading is incorrect?

I want to read values ​​that are stored in the plc. These values ​​are voltage and energy. The voltage values ​​range from 427 to 429, but suddenly they go to 16187392 and again go back to normal. This seems to me a reading error

  • Package version: 6.9.0
  • Node Version:10.16.3
  • Operating System and version: Linux 4.15.0-60-generic #67-Ubuntu x86_64
  • Controller Type:1768-L43
  • Controller Firmware:20.14

Error connecting to PLC: Error: TIMEOUT occurred while reading Controller Props.

Hello,

I try to connect to a PLC that has a Duagon CIP Stack embedded.

When I use Schneider PLC, everything works.
Then I tried with Node-Red ethernet-ip and get a : "Error connecting to PLC: Error: TIMEOUT occurred while reading Controller Props."

Doing a bit of Wireshark on it, and below the extract of a working Schneider Communication :
image

Then the Node-Red one :
image

So the main difference between the two is that node-red request for a service Code 0x52 instead of 0x54 for the Schneider one.
Wireshark identifies this two services as :

  • Schneider : 0x54 : Service: Forward Open (Request)
  • Node-Red : 0x52 : Service: Unconnected Send (0x52)

Do you know why node-red is using this 0x52 instead of 0x54 ?
And is it possible to change it ?

Thanks for your help !

Can't Read Tag by using attribute ID

Trying to read tag value with Instance ID instead of symbolic name. Symbolic name works fine, but Instance ID keeps getting same error response 'Service Not Supported'.

Tested on compactlogix 1769-L32E

Instance ID: 55838
Request:

0000   f4 54 33 96 4d 73 e0 3f 49 0e 48 d9 08 00 45 00   .T3.Ms.?I.H...E.
0010   00 60 88 80 40 00 80 06 43 e0 c0 a8 56 1e c0 a8   .`[email protected]...
0020   56 c8 ec cb af 12 ff 67 2b 04 6f ac b9 5d 50 18   V......g+.o..]P.
0030   01 ff 7b e6 00 00 70 00 20 00 00 89 02 0b 00 00   ..{...p. .......
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0050   00 00 00 00 02 00 a1 00 04 00 41 44 25 00 b1 00   ..........AD%...
0060   0c 00 06 00 4c 03 20 6b 25 00 1e da 01 00         ....L. k%.....

Response:

0000   e0 3f 49 0e 48 d9 f4 54 33 96 4d 73 08 00 45 00   .?I.H..T3.Ms..E.
0010   00 5a f3 8f 40 00 40 06 18 d7 c0 a8 56 c8 c0 a8   .Z..@[email protected]...
0020   56 1e af 12 ec cb 6f ac b9 5d ff 67 2b 3c 50 18   V.....o..].g+<P.
0030   10 00 21 a5 00 00 70 00 1a 00 00 89 02 0b 00 00   ..!...p.........
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0050   00 00 00 00 02 00 a1 00 04 00 00 2c 9b 6e b1 00   ...........,.n..
0060   06 00 06 00 cc 00 08 00                           ........

Symbolic Name: Integer2
Request:

0000   f4 54 33 96 4d 73 e0 3f 49 0e 48 d9 08 00 45 00   .T3.Ms.?I.H...E.
0010   00 64 89 44 40 00 80 06 43 18 c0 a8 56 1e c0 a8   [email protected]...
0020   56 c8 ed b7 af 12 c5 87 fc 27 83 16 d8 69 50 18   V........'...iP.
0030   02 00 69 06 00 00 70 00 24 00 00 8c 02 0b 00 00   ..i...p.$.......
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0050   00 00 00 00 02 00 a1 00 04 00 c1 45 25 00 b1 00   ...........E%...
0060   10 00 02 00 4c 05 91 08 49 6e 74 65 67 65 72 32   ....L...Integer2
0070   01 00                                             ..

Response:

0000   e0 3f 49 0e 48 d9 f4 54 33 96 4d 73 08 00 45 00   .?I.H..T3.Ms..E.
0010   00 60 f4 4c 40 00 40 06 18 14 c0 a8 56 c8 c0 a8   .`.L@[email protected]...
0020   56 1e af 12 ed b7 83 16 d8 69 c5 87 fc 63 50 18   V........i...cP.
0030   10 00 70 59 00 00 70 00 20 00 00 8c 02 0b 00 00   ..pY..p. .......
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0050   00 00 00 00 02 00 a1 00 04 00 fc ad 58 7f b1 00   ............X...
0060   0c 00 02 00 cc 00 00 00 c4 00 69 05 00 00         ..........i...

Followed exactly how to do it in Logix 5000 manual, but it won't work.

I want to use Instance ID because it is faster and could help with others who have performance issues with large number of tags

Any guess at what is going on?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.