serafintech / st-node-ethernet-ip Goto Github PK
View Code? Open in Web Editor NEWConnect to PLC controllers with Node and Ethernet/ip
License: MIT License
Connect to PLC controllers with Node and Ethernet/ip
License: MIT License
The SHORT_STRING data type is a single byte character string. It is sent with the first byte being the length, followed by the string data. Supporting symbolic (Rockwell) tags with this data type is relatively straightforward. Also add support for USINT (unsigned 8bit data) Code changes here:
https://github.com/greg9504/ST-node-ethernet-ip/tree/Add_Additional_DataTypes_Support
There was also a change for writing SINT data, the code used Buffer.writeUInt8 when I think it should have used Buffer.writeInt8.
Further testing should probably be done, as I've only be able to test using the Micro850 simulator in Connected Components Workbench.
Tested with
CCW version 22
PLC - 2080-LC50-48QWB-SIM (Micro850 Simulator, not actual hardware)
@SerafinTech I write here to request some small thing :
I opened 4 issues in https://github.com/st-one-io/node-red-contrib-cip-ethernet-ip/issues but maybe is the wrong repository for your latest commit :
Would love for this package to support Micro800 controllers. Any attempt at a connection returns
{ generalStatusCode: 1, extendedStatus: [ 785 ] }
I've found a similar package that works with Micro800, seemingly with some small changes from other PLCs (See node-logix) but it isn't actively maintained and doesn't have support for some of the nice things built into this package like TagChanged or ControllerManager.
I'm new to this library and PLC communication in general but even if I could get a couple pointers on where these changes would go I would make a PR and add it.
npm list
- e.g. 1.0.6):node --version
- e.g. 9.8.0):I am trying to make backend server with node.
It is running fine with attached program at first time but it has error after short 2 min. long 30 min.
< Error Message >
C:\react-app\prodboard\backend\node_modules\ethernet-ip\src\controller\index.js:654
throw new Error(<SCAN_GROUP>\n ${e.message}
);
^
Error: <SCAN_GROUP>
TIMEOUT occurred while writing Reading Tag Group.
at C:\react-app\prodboard\backend\node_modules\ethernet-ip\src\controller\index.js:654:31
at async Controller.scan (C:\react-app\prodboard\backend\node_modules\ethernet-ip\src\controller\index.js:647:13)
While testing IO connections with a Banner Engineering XS26 safety controller I found that an exception would be generated during the forwardClose call.
Code change to fix is here:
https://github.com/greg9504/ST-node-ethernet-ip/tree/ioForwardCloseNoOTconnID
Exception during forwardClose, while attempting to decode the OTconnID id. It seems that the client (XS26) did not send back the OTconnID in the message.
Check length of data before attempting to decode. The decoded OTconnID does not appear to be used anyway, so a default of 1 is set in this case.
let OTconnID = 1;// id not sent back by some clients
//some clients may not include OTconnID in forwardClose response
//found while testing with Banner Engineering XS26 safetly controller
if (data.length > 0) {
OTconnID = data.readUInt32LE(0); // first 4 Bytes are O->T connection ID
}
super.id_conn = OTconnID;
Can not use lib with Banner XS26 for IO connections without fix.
Setup IO connection with Banner Engineering XS26 then close connection...
function GenericEthernetIPclient() {
var ioscanner; // Connection Manager for IO exchange
var ioconnections = []; // IO connections
// open the io connection
var makeIOConnection = function {
// simplified
ioscanner = new STEthernetIp.IO.Scanner(2222, '0.0.0.0');
const ioconn = ioscanner.addConnection(config, module.rpi, addr, ioport, false);
}
...
// close the connection
var _disconnectAll = async function () {
const result = await _closeScannerWrapper();
for (let ioconn of ioconnections) {
if (ioconn) {
ioconn.run = false;
try {
console.log('closing io connection...')
await ioconn.tcpController.disconnect();
console.log('io connection closed');
} catch (error) {
console.log('Error disconnecting io connection');
console.log(error);
ioconn.tcpController.destroy();
ioconn.tcpController._removeControllerEventHandlers();
console.log('forced io connection closed');
}
ioconn = undefined;
}
}
ioconnections = [];
}
// close the UDP listening port
var _closeScanner = function (successCallback, errorCallback) {
try {
if (ioscanner) {
ioscanner.socket.close(() => {
ioscanner = undefined;
console.log('scanner closed');
successCallback(true);
});
} else {
console.log('scanner not open, success');
successCallback(true);
}
} catch (err) {
errorCallback(false);
}
}
var _closeScannerWrapper = function () {
return new Promise((resolve, reject) => {
_closeScanner((successResponse) => {
resolve(successResponse);
}, (errorResponse) => {
reject(errorResponse);
return;
});
});
}
call makeIOConnection then later call disconnectAll.
Typescript is unsupported as there is no @types/st-ethernet-ip
which makes it difficult to work with typescript.
I should be able to either install this lib or install @types/st-ethernet-ip
and get type declarations.
index.d.ts
to define all public types.My team is beginning to adopt this library as one of our core libs and using it is "janky" because we have to comb through source code to figure out what everything does. Adding a declaration file along with some basic documentation will make working with this library much easier.
[email protected]
18.12.0
Hi, I have an error 15 with plc subscription, PLC is a controllogix 5580 my connection to this PLC is:
const PLC = new Controller(false);
And the error ir :
{generalStatusCode: 15, extendedStatus: Array(1) }
When structure member of type BIT_STRING is expanded in the value, its type is an array of integers rather than an array of bools.
A PLC type BOOL[64] will be expanded as if it were typed DINT[2].
A PLC type BOOL[64] should be expanded into an array of bools with length 64.
If the PLC programmer has chosen type BOOL[], there was a purposeful decision to use boolean values rather than integers. Reading this data out of the PLC should reflect the same type.
myTag = new Structure("MyTag", null, tagList);
await PLC.readTag(myTag);
console.log(myTag.value.MyBits);
npm list
- e.g. 1.0.6): 2.3.2node --version
- e.g. 9.8.0): 14.17.5Controller Manager does not auto-reconnect to a PLC. When reading a value, it retains the value from before the download and will not update.
After a download to a PLC, tags are no longer read from that PLC.
Tags should resume polling from that PLC.
npm list
- e.g. 1.0.6): 2.6.7node --version
- e.g. 9.8.0):Code to retrieve all tags and values. Works only with newest version.
const eip = require('st-ethernet-ip')
let cont = new eip.Controller()
cont.connect(IP_ADDRESS)
.then(async () => {
let tagGroup = new eip.TagGroup()
for (let i = 0; i < cont.tagList.length; i++) {
let tag = cont.newTag(cont.tagList[i].name, cont.tagList[i].program, false, cont.tagList[i].type.arrayDims)
tagGroup.add(tag)
if (cont.tagList[i].type.arrayDims > 0) {
await cont.getTagArraySize(tag)
}
}
await cont.readTagGroup(tagGroup)
tagGroup.forEach(t => {
console.log(t.name, t.value)
})
})
How do you reference an element of an array of UUDT's using the ControllerManager addTag() function?
This example code from the README works fine for a single integer tag.
const {ControllerManager} = require('st-ethernet-ip')
const cm = new ControllerManager();
//addController(ipAddress, slot = 0, rpi = 100, connected = true, retrySP = 3000, opts = {})
const cont = cm.addController('192.168.86.200');
cont.connect();
//addTag(tagname, program = null, arrayDims = 0, arraySize = 0x01)
cont.addTag('TheInteger')
cont.on('TagChanged', (tag, prevValue) => {
console.log(tag.name, ' changed from ', prevValue, ' => ', tag.value)
})
cont.on('error', (e) => {
console.log(e)
})
What is the correct syntax for adding a tag which is an element of a UDDT (or atomic) array.
cont.addTag(?????)
Is it something similar to:
cont.addTag(tagname, program = null, arrayDims = 0, arraySize = 0x01)
I have not has success with that other than getting the first element only.
Ultimately I want to use this with your node-red node (node-red-contrib-cip-st-ethernet-ip).
npm list
- e.g. 1.0.6): 2.5.0node --version
- e.g. 9.8.0): 16.14.2Great improvement from the original fork. I'm wondering if you have an idea of when UDTs & arrays will be included?
A user contacted me by email and wanted to see a way to get a complete list of all tag names that would valid on the controller including drilling down through arrays and structures. I came up with this solution. Would this be useful as a utility function?
const eip = require('st-ethernet-ip')
let cont = new eip.Controller()
cont.connect('192.168.121.10')
.then(async () => {
let directory = {}
// Separate tags by program / controller scope
cont.tagList.forEach( t => {
directory[t.program] = [];
})
// Iterate through each scope
for (program in directory) {
let nameList = []
let progValue = (program === 'null') ? null : program;
let taglist = cont.tagList.filter(t2 => t2.program === progValue);
// Iterate through each tag of each scope
for (let i = 0; i < taglist.length; i++) {
await drillDown(cont, nameList, taglist[i])
}
directory[program] = nameList
}
// Change 'null' name to 'Controller'
directory['Controller'] = directory['null']
delete directory.null
console.log(directory)
})
// Recursive tag name listing function
async function drillDown(cont, nameList, tagInfo, previousName) {
let tagLength = 1;
if (tagInfo.type.arrayDims > 0) {
if (tagInfo.info === undefined) {
let contTag = cont.newTag(tagInfo.name, tagInfo.program, false, 1);
tagLength = await cont.getTagArraySize(contTag);
} else {
tagLength = tagInfo.info;
}
for (let i = 0; i < tagLength; i++) {
let newName
if (previousName) {
newName = previousName + '.' + tagInfo.name + '[' + i + ']';
} else {
newName = tagInfo.name + '[' + i + ']';
}
nameList.push(newName);
if (tagInfo.type.structure) {
let members = cont.templateList[tagInfo.type.code]._members;
for (let a = 0; a < members.length; a++) {
await drillDown(cont, nameList, members[a], newName);
}
}
}
} else {
let newName
if (previousName) {
newName = previousName + '.' + tagInfo.name
} else {
newName = tagInfo.name
}
nameList.push(newName);
if (tagInfo.type.structure) {
let members = cont.templateList[tagInfo.type.code]._members;
for (let a = 0; a < members.length; a++) {
await drillDown(cont, nameList, members[a], newName);
}
}
}
}
Supposedly you can write to a tag without reading from it first so long as you declare the data type. This does not work.
let heartbeatTag = new Tag("collectorHeartBeat", null, BOOL);
await PLC[i][0].writeTag(heartbeatTag, true);
produces this:
2022-11-10 05:45:36Heartbeat Error: undefined
which is my catch block when the heartbeat tag write fails
Write the tag successfully
npm list
- e.g. 1.0.6):node --version
- e.g. 9.8.0):So, here I found a strange behavior and also I want to suggest one enhancement.
Whenever you create an ExtController
you can also define the scan_rate
trough the rpi
parameter which by default is 100.
The deal with the connect()
function is that you are assuming that the consumer want to scan the controller for the previously created Tags
and add the event_emitters
to them to instantly emit the events on the tags. This is very nice, but what about if I dont want to inmediately scan the variables in order to controll the reading in a more granular way.
One example to give you is: If I want to do a controller read on demand to a TagGroup
I'd like to do it without loosing all this great features that you are already exposing... like the reconnecting and managing in a more abstract way the controllers instead of use the basic Controller
.
Yet, this can be done, on demand, but still there is an ongoing background process happening due to "un asked" scans and emitting events on tags which possible I dont want to listen.
This abstraction is great, but we can create two different functions or allow the scan
and the addTagEvents
at will. This will ensure performance and not wasting resources when we dont want to listen the events of the tags.
Now.. moving on the next topic... I found that when we increate the rpi
for the scan rate above 2800ms, this error is thrown:
and this dont tell us much.. but in anycase, there are specific scenariosn in which I dont want to constanlty read on the PLC (like the one I told you, I want some screenshot of the values and not interested on changed
evfents)
Thanks in advance to read... I hope we can support this feature.
Do you want to pull your changes to the original repository? Feel free to open a PR if you like. I can make you a maintainer so you pull your own PRs as I rarely have the time to look through code reviews these days.
Also, I am in the middle of a typescript rewrite of the repository that is much more stable (currently 100% coverage). Feel free to check that guy out as well if you like. The long term plan (assuming I ever get the time) is to fully transition to typescript.
Place holder for Node-Red compatability
I am now having some issues now with the subscription/scan methods. The following works and I can verify it is setting the Tag value in Connected Components Workbench, but when I uncomment the PLC.scan() method, with or without the other writeTag code I get an unhandled rejection error
Code:
const { Controller, Tag, EthernetIP } = require("st-ethernet-ip");
const { BOOL } = EthernetIP.CIP.DataTypes.Types;
const PLC = new Controller();
let tag1 = new Tag("TEST_TAG_1", null, BOOL);
PLC.subscribe(tag1);
PLC.connect("192.168.1.10", Buffer.from([]))
.then(async () => {
tag1.value = true;
PLC.writeTag(tag1);
// PLC.scan_rate = 50;
// PLC.scan();
})
.catch((err) => {
console.log(err);
});
Error:
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: 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(). The promise rejected with the reason "#<Object>".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Any thoughts what might be causing that?
I'm trying connect to PLC with this code:
const {Controller} = require('st-ethernet-ip')
const PLC = new Controller();
PLC.connect("192.168.1.222", 0).then(async () => {
console.log("Connected!")
}).catch(async e => {
console.log(e)
});
And I get this in the console:
{ generalStatusCode: 5, extendedStatus: [ 0 ] }
I read here: https://assets.omron.eu/downloads/manual/en/v2/w506_nx_nj-series_cpu_unit_built-in_ethernet_ip_port_users_manual_en.pdf that is a "Path destination unknown" error.
When I replace library "st-ethernet-ip" with "ethernet-ip", everything works ok. But I need this library, because of UDTs.
Can someone help?
Thank you
Connect to PLC
Run the code see above
I am trying to run a sample code under the heading 'Getting a List of Available Controller Tags and Structure Templates'
but I keep into running this error upon execution of this line:
PLC.connect("192.168.1.1", 0).then(async () => {...}
Uncaught Error UnhandledPromiseRejection: 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(). The promise rejected with the reason "#".
at getErrorWithoutStack (undefined:320:15)
at generateUnhandledRejectionError (undefined:339:15)
at processPromiseRejections (undefined:286:24)
at processTicksAndRejections (undefined:96:32)
keep into running this error:
Uncaught Error UnhandledPromiseRejection: 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(). The promise rejected with the reason "#".
at getErrorWithoutStack (undefined:320:15)
at generateUnhandledRejectionError (undefined:339:15)
at processPromiseRejections (undefined:286:24)
at processTicksAndRejections (undefined:96:32)
upon execution of this line:
PLC.connect("192.168.1.1", 0).then(async () => {...}
return of tag list
const PLC = new Controller();
const tagList = new TagList();
PLC.connect("192.168.1.1", 0).then(async () => {
// Get all controller tags and program tags
await PLC.getControllerTagList(tagList)
// Displays all tags
console.log(tagList.tags)
// Displays all templates
console.log(tagList.templates)
// Displays program names
console.log(tagList.programs)
});
npm list
- e.g. 1.0.6):node --version
- e.g. 9.8.0):It doesn't appear like we can write to tags from a ControllerManager connection, just subscribe to tag changes. Is that the case? In our application we would like to use the long-lived connection of a ControllerManager and also be able to read/write tags
cannot connect to safety controller
when I try to connect to a normal PLC, there are no issues. If I try to connect to a safety PLC (1756-L61S) I cannot establish a connection.
successfully connect without timeout.
I will have to dive deeper into the code, to check the connection, perhaps I am able to fix it myself.
// connect with a safety in slot 7
const {ControllerManager} = require('st-ethernet-ip')
const cm = new ControllerManager();
const cont = cm.addController('192.168.20.3', slot=7);
cont.connect();
cont.addTag('testtag')
cont.on('TagChanged', (tag, prevValue) => {
console.log(tag.name, ' changed from ', prevValue, ' => ', tag.value)
})
cont.on('error', (e) => {
console.log(e)
})
npm list
- e.g. 1.0.6): 2.4.4node --version
- e.g. 9.8.0): latest LTSIf i try to read string value i got error: Unrecognized Type Passed Read from Controller: 208
My code is:
const stringStructure = new Structure('A_AUTOMATIC.RunRecipeName', tagList);
try {
await PLC.readTag(stringStructure);
console.log(stringStructure.value);
} catch(e) {
console.log(e);
}
On an exception, the ENIP class's destroy
function attempts to write to the TCP socket, to gracefully close it. But, if the exception was caused by unexpectedly closed socket, this results in an endless loop.
The destroy function should not attempt to write to a closed socket, triggering an exception.
If the exception.code
is EPIPE
, don't write to the socket.
I was attempting to set up node-red to work with eth-ip messaging. Whenever I closed the ethip server simulator, node-red crashed.
npm list
- e.g. 1.0.6): [email protected]node --version
- e.g. 9.8.0):There is a super cool feature you have expose.
This is nice, but you know that we need to pass a Tag Object in to it... and if for example we dont save that instance in memory but still know the name of the tag.. it implies extra logic to handle this scenarios..
having this feature passing a string variable name as parameter should be nice... and very helpful!
For explicit messages sent using getAttributeSingle it would be useful to add an optional parameter with a buffer. This buffer is appended to the message. Some ethernet/IP devices require additional information beyond the class/instance/attribute. See this issue for a description for such a case for a SEW drive #62
Code/documentation change for PR is here:
https://github.com/greg9504/ST-node-ethernet-ip/tree/ExplicitMsgWithData
May be related to Issue #56
Steps to reproduce:
const plc = new Controller()
plc.connect(ip_address, slot);
(At this point the plc.established
property changes to true
to indicate an active connection.)
Wait a while and start reading tags repeatedly using plc.readTag()
After tags are reading successfully, unplug the PLC's Ethernet cable, power it off, or change its' IP address, causing a loss of connectivity to the PLC.
After a moment, the readTag()
function starts to throw errors, and the plc.established
property changes to false
.
Wait a few moments, then restore the PLC's connectivity.
After a moment, the plc.established
property turns to true
again, indicating that the library has reconnected to the PLC. However, any subsequent attempts to execute plc.readTag()
fail with the following error: (note that the tag being read in this example is named "Int1" and is a Controller-scoped tag.
All subsequent readTag()
functions fail until a new plc.connect(...)
function is issued, at which point everything starts reading again.
Error: TIMEOUT occurred while writing Reading Tag: Int1.
at Controller._readTag ([snip]\node_modules\st-ethernet-ip\dist\controller\index.js:727:28)
at TaskEasy._runTask ([snip]\node_modules\task-easy\src\index.js:118:9)
at TaskEasy._next ([snip]\node_modules\task-easy\src\index.js:138:18)
at [snip]\node_modules\task-easy\src\index.js:56:22
at new Promise (<anonymous>)
at TaskEasy.schedule ([snip]\node_modules\task-easy\src\index.js:51:16)\
at Controller.readTag ([snip]\node_modules\st-ethernet-ip\dist\controller\index.js:591:34
at Driver_EthernetIP.read ([snip]\Driver_EthernetIP.class.js:150:19)
โฆ โฆ
I'm trying to get ethernet-ip working with SEW VFD's. These do NOT support the 0x6b class object used for tags. The drives support the Assembly (0x04) and Register (0x07) object classes. I have been able to connect and configure the drives using the library and the getAttributeSingle/setAttributeSingle methods. I did have to make one small change to the library to get it to work. Code is below for how I'm using the library.
What I'm wondering is:
I'm pretty green on Ethernet-IP. So any suggestions welcome.
Anyway here is how I'm using it to talk to the SEW drive. The small modification I had to make to the library was to add a Buffer parameter to the getAttributeSingle call. This is for getting drive parameter values, you need to send some data identifying which parameter you want to get.
import { ControllerManager, TagList, IO, Tag, Controller } from "st-ethernet-ip";
async function main() {
const PLC = new Controller();
// connect to the SEW VFD, do not do setup, while the VFD supports
// Identity Class 0x01 (readControllerProps), it does not support
// Class 0x6b for tags (getControllerTagList)
PLC.connect("192.168.1.159", 0,false).then(async () => {
// write process output data, there are 3 words, PO1/PO2/PO3
// example configuration on the VFD:
// PO1 = Control Word (direction of rotation, control command, param set, etc) (0x0005)
// PO2 = Setpoint Speed (rpm of motor) (6000 rpm, encoded as 6000/0.2 = 30000 = 0x7530)
// PO3 = Unassigned (set to 0x0000)
const buf = Buffer.from("050030750000", 'hex');
await PLC.setAttributeSingle(0x04,120, 3, buf );// class 0x04 Assembly Object, Instance 120 (decimal) output process data, attribute 3
console.log('wrote PO data');
// read the process input data, that is the actual values of the drive
// example configuration
// PI1 = Status Word
// PI2 = Actual Speed
// PI3 = Output Current
const value = await PLC.getAttributeSingle(0x04, 130, 3);// class 0x04 Assembly Object, Instance 130 (decimal) input process data, attribute 3
console.log('Got actual values from VFD: ' + value.toString('hex'));
//SEW drive alays returns 10 words, we only care about the first 3
//now read a parameter setting, this is done using the Register Object class 0.07
//data needs to be passed in the getAttributeSingle call to identify which parameter
//is to be read
// read parameter 006 Motor Utilization, parameter index is 0x2083
const paraBuf = Buffer.from("832000000000000000000000", 'hex');
// paramBuf is in format of SEW paameter channel:
// Index: UINT : SEW parameter index (NOT the parameter number)
// Data: UDINT : Data(32 bit)
// SubIndex: BYTE: Sew unit subindex (usually 0)
// Reserved: BYTE: 0
// SubAddress1: BYTE: 0 if Parameter of Drive or com card, 1-63 for sbus
// SubChannel1: BYTE: 0 if Parameter of Drive or com card, 2 sbus
// SubAddress2: BYTE: 0
// SubChannel2: BYTE: 0
const paramValue = await PLC.getAttributeSingle(0x07, 1, 4, paraBuf); //class 0x07 Register, instance 1 to read (2 to set), attribute 4 param value
console.log('Got actual parameter value from VFD: ' + paramValue.toString('hex'));
//we can set SEW paramters values as well, instance 2 must be used, the parameter and value is encoded same as for read
//but the Data bytes are filled in with the new parameter value.
// parameter 130 Speed Ramps1::Ramp t11 up CW, index 0x2116, value 6 seconds (6 seconds is transmitted as 6000)
const newParaBuf = Buffer.from("1621701700000000000000000", 'hex');
const newParamValue = await PLC.setAttributeSingle(0x07, 2, 4, newParaBuf); //class 0x07 Register, instance 1 to read (2 to set), attribute 4 param value
console.log('Set parameter succeeded');
}).catch((error) => {
console.log(error);
});
;
console.log('Hello world!');
}
main();
getAttributeSingle:
async getAttributeSingle(classID: number, instance: number, attribute: number, attData?: Buffer): Promise<Buffer> {
const { GET_ATTRIBUTE_SINGLE } = CIP.MessageRouter.services;
const { LOGICAL } = CIP.EPATH.segments;
const identityPath = Buffer.concat([
LOGICAL.build(LOGICAL.types.ClassID, classID),
LOGICAL.build(LOGICAL.types.InstanceID, instance),
LOGICAL.build(LOGICAL.types.AttributeID, attribute)
]);
const MR = CIP.MessageRouter.build(GET_ATTRIBUTE_SINGLE, identityPath, attData ? attData : Buffer.from([]));
super.write_cip(MR, super.established_conn);
...
I have a lot tags that are structures. If I read the tag with PLC.readTag(tag)
the return is an array of objects with a key and value. When I add that same tag to a tag group the return value is a buffer. Is there any way to get the same return value as a tag read of a structure in a tag group?
npm list
- e.g. 1.0.6):node --version
- e.g. 9.8.0):Hey, I'm giving a try to your lib, and I found that for some specific cases, the extController
class, can be used at will without having to use the ControllerManager
class.. this will be handy for example when we want to create just one controller and attach some custom information to it in an outer scope.
as you are exporting the ControllerManager
by default, you are being forced to instatiate always the manager to get the extController which is pointless.
Also, I noted that the extController
has a great support for auto-reconnection, but it will be nice to also expose the capability to desable the auto-reconnection, which right now we can not change or initialize at building time of the extController
It would be awesome if we can do it.
I realize that you are working on String support but I it looks like the README was last update 2 years ago and string support still isn't there. I work more with PLC's that utilize the LGX style of tags and I noticed that the LGX string is a STRUCT and has the same value as your STRUCT define (0x02a0). It's name, however, is "String" and its handle is 4060 when you grab the UDT information. Anyways, we rarely use any UDT's other than the string and so I would like to propose a temporary solution to support the String type which can then potentially be utilized for the future UDT support (I have some ideas there).
case LGXSTR: // struct for string
var len = data.readUInt32LE(4);
var tmp = "";
// build string here so controller_value doesn't fire too many
// OnChange events for those subscribed to changes
// or use a different converstion method
for(var i = 0; i < len; i++)
{
// LGX string header = 8 bytes
tmp += String.fromCharCode(data[8+i]);
}
this.controller_value = tmp;
break;
case LGXSTR: // LGX string
// override size with struct handle in header
buf.writeUint16LE(0x0FCE,2);
// string struct is a total of 90 by default
var header = Buffer.alloc(6);
header.writeInt16LE(1); // writing only one string
header.writeInt32LE(tag.value.length,2); // length of string
valBuf = Buffer.alloc(84); // standard string length
valBuf.write(tag.value);
buf = Buffer.concat([buf,header,valBuf]);
break;
I have been testing this without any issues thus far. What I don't know is the impact it may have on reading other UDT's and so other checks will likely need to be added to avoid issues. Not sure where it's stored but if you had the handle from an initial read you could thrown an exception or exit with an error if the handle isn't 4060 (perhaps reading the UDT info to always get the correct handle to check may be necessary).
What are your thought on this solution?
returns:
node:436) UnhandledPromiseRejectionWarning: #
(Use node --trace-warnings ...
to show where the warning was created)
(node:436) 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(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict
(see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:436) [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.
it should get tag list from PLC.
const {Controller, Tag, TagList} = require('ethernet-ip');
const PLC = new Controller();
const tagList = new TagList();
PLC.connect('address ip', 0).then(async () => {
await PLC.getControllerTagList(tagList);
console.log(tagList);
});
npm list
- e.g. 1.0.6):node --version
- e.g. 9.8.0):mainint is defined as an INT tag in Controller scope.
boolarray is defined as BOOL[32] in Controller scope.
Reading from "mainint.1" works with no error.
Reading from "boolarray.1" works with no error.
Writing to "mainint.1" throws a blank exception (no message or error).
Writing to "boolarray.1" throws a blank exception (no message or error)
UPDATE: if the exception is caught and output as console.error(JSON.stringify(err))
it's blank.
If not caught, the console outputs the following:
connected to PLC
Uncaught Error Error: Bit Indexes can only be used on SINT, INT, DINT, or BIT_STRING data types.
at generateWriteMessageRequestForBitIndex (---/node_modules\st-ethernet-ip\dist\tag\index.js:594:23)
at generateWriteMessageRequest (---node_modules\st-ethernet-ip\dist\tag\index.js:557:25)
at _writeTag (---node_modules\st-ethernet-ip\dist\controller\index.js:805:24)
at _runTask (---node_modules\task-easy\src\index.js:118:9)
at _next (---node_modules\task-easy\src\index.js:138:18)
at <anonymous> (---node_modules\task-easy\src\index.js:56:22)
at schedule (---node_modules\task-easy\src\index.js:51:16)
at writeTag (---node_modules\st-ethernet-ip\dist\controller\index.js:608:35)
at run (---test\st-ethernet-ip.test.js:24:13)
at <anonymous> (---test\st-ethernet-ip.test.js:11:2)
at processTicksAndRejections (internal/process/task_queues:95:5)
Process exited with code 1
Here's the code:
const { Controller, Tag, TagList, EthernetIP } = require('st-ethernet-ip');
const { SINT, INT, DINT, REAL, BOOL, USINT, UINT, UDINT, WORD, DWORD } = EthernetIP.CIP.DataTypes.Types;
const plc = new Controller();
plc.connect('192.168.1.77', 0)
.then(() => {
console.log(`connected to PLC`)
run();
})
async function run()
{
const t = new Tag('boolarray.5', null, BOOL);
t.value = true;
await plc.writeTag(t);
}
See above
Expect to write to a bit field of INT tag via "mainint.1"
Expect to write a bit in "boolarray"
Unable to write bits of integer tags or elements of BOOL array tags.
See above
npm list
- e.g. 1.0.6): 2.7.2node --version
- e.g. 9.8.0): 21.7.1Can not read a predefined tag of type ALARM or ALARM_DIGITAL.
The readTag() function returns the following error:
{ generalStatusCode: 15, extendedStatus: [] }
Read the tag without error.
Might have something to do with the extra SINT members that come along with structures that have BOOL members (one SINT for every 8 BOOLs). Just a thought because I am also experiencing incorrect data being written to DINT members, if the structure contains any BOOL members.
Run this code with a tag named 'ALM_1' defined as type ALARM.
const {Controller} = require('st-ethernet-ip')
const PLC = new Controller()
PLC.connect('10.0.0.250',0).then(async () => {
console.log('Connected...')
const udt = PLC.newTag('ALM_1')
await PLC.readTag(udt)
console.log('ALM_1:', udt.value)
}).catch(e => {
console.log('Error: ', e)
})
npm list
- e.g. 1.0.6): 2.5.0node --version
- e.g. 9.8.0): 16.14.2Hi there, I'm the maintainer of the Node-RED node for Ethernet/IP. We currently use there the original library by Canaan, but it looks like it's not maintained anymore. I'm considering switching to this fork here.
Taking advantage that we'll be modifying the node, there's a small change that would be nice to have in order to support Omron PLCs that are currently not supported.
The default value of the unconnected send is currently 2000 ms here, but it needs to be at least 5024 ms for Omron PLCs to accept and process the request.
In my tests, changing it to 5024 doesn't affect the communication to the Rockwell PLCs I've tested, so we could either:
Either way, I can create a PR with the needed changes. What do you think about it?
Thanks!
Maybe I'm jumping the gun, but I thought the support for STRING data types was added; however, upon trying to read them, I'm only able to get Buffer data. This also occurs when trying to read a string array of length 1.
value: <Buffer 08 00 00 00 30 31 2d 30 33 2d 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 38 more bytes>
Returns buffer data instead of string text
Return string text
npm list
- e.g. 1.0.6): [email protected]node --version
- e.g. 9.8.0): 12.18I want to write to some indexes inside an array in a program scope tag.
I'm able to read the tags, but when I attempt to write I get the following error:
Error: TIMEOUT occurred while writing Writing Tag: Program:[program].[tag-name]
Do you guys have a discord server?
When attempting to write to a tag array it's rejecting the promise on a TIMEOUT error.
Write to manipulated tag array indexes.
export const sendCommand = async (command: Command) => {
try {
const PLC = new Controller();
await PLC.connect(command.ip, command.slot);
const programName = command.commandTag.split(":")[1].split(".")[0];
const tagName = command.commandTag.split(".")[1];
const tagObj = new Tag(tagName, programName, Types.REAL, 0, 1, 1000);
await PLC.readTag(tagObj);
for (let i = 0; i < command.parameters.length; i++) {
const parameter = command.parameters[i];
tagObj.value[i] = parameter.value;
}
tagObj.value[command.offsetNum] = command.command;
await PLC.writeTag(tagObj); // rejects this promise
await PLC.disconnect();
} catch (error) {
console.log(error);
}
};
npm list
- e.g. 1.0.6): 2.6.7node --version
- e.g. 9.8.0): 18.16.1Great work guys, I can see this wonderful package.
I am trying to use this to build a application pulling data from PLCs and dump all the data on the information screen, I don't want the information go for problem as I setup my client screen auto refresh mode (every x seconds). once I unplug ethernet cable after a certain period I got time out time out problem, I use try catch to catch the error and replace with initialization tags information but I seem can not bypass the connection even though I saw the time out error, so basically my screen change to white question screen unless I plug the cable back. when I have SQL query running in the same situation, I could catch error and dump screen with initialization data of the tags from SQL, but this technology used for MS SQL seems not working for this package. I wonder if you have any idea can lighting up?
error message is:
(node:7932) UnhandledPromiseRejectionWarning: Error: TIMEOUT occurred while attempting to establish TCP connection with Controller.
at Controller.connect (C:\Users\Public\Documents\marquee_hdb\node_modules\st-ethernet-ip\src\enip\index.js:163:28)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async Controller.connect (C:\Users\Public\Documents\marquee_hdb\node_modules\st-ethernet-ip\src\controller\index.js:191:24)
I expect when I lost connection, the PLC connection error can be caught by try catch block and replace the tags reading with their initialization value.
npm list
- e.g. 1.0.6): "st-ethernet-ip": "^2.3.2"node --version
- e.g. 9.8.0): v14.16.1When monitoring a UDT with BOOL members, the first member's value unexpectedly changes if there is a subsequent BOOL member in the UDT.
Value of the first member of the UDT unexpectedly changes. Could create unpredictable behavior of the control system.
Monitor UDT's with BOOL members without effecting the first member.
This use case only needs to read values, never write.
const {ControllerManager} = require('st-ethernet-ip')
const cm = new ControllerManager();
//addController(ipAddress, slot = 0, rpi = 100, connected = true, retrySP = 3000, opts = {})
const cont = cm.addController('10.0.0.250');
cont.connect();
//addTag(tagname, program = null, arrayDims = 0, arraySize = 0x01)
cont.addTag('UDT_1')
cont.on('TagChanged', (tag, prevValue) => {
console.log(tag.name, ' changed from ', prevValue, ' => ', tag.value)
})
cont.on('error', (e) => {
console.log(e)
})
npm list
- e.g. 1.0.6): 2.5.0node --version
- e.g. 9.8.0): 16.14.2Hello creator and good work with this repository!!
I have a few questions and I am unsure where to bring them up other than here, so excuse me if this is the wrong forum.
I was thrilled to see that this repository had I/O Scanner as I am currently trying to establish communication to a Fanuc Robot Controller R-30iB Mate. The robot controller works as Ethernet/IP Adapter and writes its inputs to Instance number e.g 101 and outputs to Instance number e.g 151.
The way I would like to achieve communication is through explicit message (tcp) to be able to read both I/Os and register acyclically, cyclic is not important so implicit message (udp) might be good to have for future case.
I am currently looking to try to implement this to Node-RED where configuration can simply be made what instances are to be read and written by specifying:
Reading the instance numbers e.g 101 and 151 is priority and will get me on the way for what I am currently working on, but having the flexibility to specifiy what to read and write will give me almost complete data exchange with any Ethernet/IP adapter.
Now to the questions,
Attached is a .pdf file for the EIP for the Fanuc Robot Controller, especially interesting is page 92-142, page 139 is good for what I want to achieve foremost.
I need to know, what exactly does "status" means in propetries object. Example:
{
name: '5069-L340ERM/A',
serial_number: 1620037161,
slot: 0,
time: null,
path: <Buffer 01 00>,
version: '34.11',
status: 8304,
faulted: false,
minorRecoverableFault: false,
minorUnrecoverableFault: false,
majorRecoverableFault: false,
majorUnrecoverableFault: false,
io_faulted: false
}
I need it to know if the PLC is in run mode or not. Although I trace which numbers belong to which modes, but I would like to know the full meaning. I know it's some kind of bit word, but I couldn't find anywhere, what the bits means. Is there any documentation for this? Can anyone advise me, please? Thank you.
One of the things I've noticed is that, whenever a tag is unkown this error is thrown:
{ generalStatusCode: 4, extendedStatus: [ 0 ] }
This is not really usefull, because instead of saying which variable is failing... it throws this message but all the other variables can not be read, and we get a bunch of nulls.
it should be nice to have the conflicted variable with some specific status or null and the other ones performing the readings. if this is not possible, at least improve the error so we can know what is happening.
This can be reproduces adding an unexisting tag.
Also, not having this can lead us to a memory leak eventually due to multiple listeners set and undestroyed in the underlayer of the emitter like so:
(node:16260) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 Multiple Service Packet listeners added to [Controller]. Use emitter.setMaxListeners() to increase limit
Hi @SerafinTech,
You bumped the version to 2.7.0 - any chance you could push latest version "to" npm?
When an unknown tag is encountered the timestamp is not updated.
Timestamp should reflect time of emit.
Will submit pull request momentarily.
PLC.connect("192.168.2.235", 1).then(async (err) => { PLC.readTag(tag); });
If the code to read a tag is within that callback, it works fine; however, if it lies within another function, say on an interval, it times out. Although, I can read PLC.properties just fine in the external functions.
Error: TIMEOUT occurred while writing Reading Tag: Analog_Inputs[1]
Once the PLC is connected, I should be able to summon PLC.readTag from elsewhere. (This works on the original repo)
Set up an interval where a tag's value is read and displayed in the console.
npm list
- e.g. 1.0.6): 2.4.4node --version
- e.g. 9.8.0): 12.18.3At random times, the script will stop reading data from a 1756-L81E ControlLogix. The script also reads from a 1769-L32E and that data continues to read normally. This error occurred before the introduction of the controller manager, too.
From my PM2 log:
0|plc | 2023-08-29 07:26:16: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:16: Reading Error - Analog_Inputs[6] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:17: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:18: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:18: Reading Error - Discrete_Outputs[35].Out - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:19: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:20: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:21: Reading Error - F8[41] - TIMEOUT occurred while writing Reading Tag Group.
0|plc | 2023-08-29 07:26:22: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:23: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:23: Reading Error - Discrete_Outputs[35].Out - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:24: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:25: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:26: Reading Error - F8[41] - Micro Queue is already full at size of 100!!!
0|plc | 2023-08-29 07:26:26: Reading Error - Analog_Inputs[6] - Micro Queue is already full at size of 100!!!
Should continue reading PLC.
Unable to reproduce. Will work fine for days or weeks before throwing these errors.
npm list
- e.g. 1.0.6): 2.6.1node --version
- e.g. 9.8.0): 12.21Controller: 1769-L35E, firmware 20.19
Controller-scoped boolean array tag: BoolTag, type BOOL[32]
Attempting to read BoolTag[2] throws exception: {generalStatusCode: 5, extendedStatus: Array(0)}
Attempting to write BoolTag[2] throws exception: {"generalStatusCode":255,"extendedStatus":[8455]}
Attempting to read BoolTag[0] returns an array of all array elements (32 boolean values).
Code:
const tag1 = new Tag("BoolTag[0], null, BOOL);
const tag2 = new Tag("BoolTag[2], null, BOOL);
plc.readTag(tag1)
.then(() => {
console.log(`Tag value: ${tag1.value} of type ${typeof tag1.value}`);
// Output: Tag value: false,true,false,true,false,true,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false of type object
})
.catch((err) => {
console.error(err);
});
plc.readTag(tag2)
.then(() => {
console.log(`Tag value: ${tag2.value} of type ${typeof tag2.value}`);
})
.catch((err) => {
console.error(err);
// Output: {generalStatusCode: 5, extendedStatus: Array(0)}
});
Reading INT array tags works with no issues (i.e. "IntTag[2]" returns the tag integer value)
Expect that any attempt to read a BOOL tag array element returns a single BOOL value, not an array of values, and not throws an exception.
n/a
Trying to read a few individual BOOL array tags for status values, without reading the entire array, which could be many hundreds of elements.
See code above.
npm list
- e.g. 1.0.6): [email protected]node --version
- e.g. 9.8.0): v21.7.1A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.