Skip to content

Commit fb7dffa

Browse files
author
BMh
committed
extend listIdentity to include Socket-Parameters.
Remove legacy discovery method and add more information to return of discovery. Add attached interface to return value of Discovery method.
1 parent 6670cd8 commit fb7dffa

File tree

2 files changed

+63
-78
lines changed

2 files changed

+63
-78
lines changed

src/enip/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,21 @@ class ENIP extends Socket {
236236
listIdentityErr
237237
);
238238

239-
let ptr = 24; // Other data is not relevant.
239+
let ptr = 8; // Starting with Socket Address
240+
this.plcProperties.socketAddress = {};
241+
this.plcProperties.socketAddress.sin_family = listData.readUInt16BE(ptr);
242+
ptr+=2;
243+
this.plcProperties.socketAddress.sin_port = listData.readUInt16BE(ptr);
244+
ptr+=2;
245+
this.plcProperties.socketAddress.sin_addr = listData.readUInt8(ptr).toString()+
246+
"."+listData.readUInt8(ptr+1).toString()+
247+
"."+listData.readUInt8(ptr+2).toString()+
248+
"."+listData.readUInt8(ptr+3).toString();
249+
ptr+=4;
250+
this.plcProperties.socketAddress.sin_zero = 0;
251+
ptr+=8;
252+
253+
// Now follows the asset data
240254
this.plcProperties.vendorID = listData.readUInt16LE(ptr);
241255
ptr+=2;
242256
this.plcProperties.deviceType = listData.readUInt16LE(ptr);

src/utilities/index.js

Lines changed: 48 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -26,66 +26,6 @@ const promiseTimeout = (promise, ms, error = new Error("ASYNC Function Call Time
2626
*/
2727
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
2828

29-
/**
30-
* Sends a broadcast to a specified IPv4 Interface in order to discover all present EthernetIP
31-
* devices. If no interface is specified, checks all available interfaces and sends a broadcast to them.
32-
*
33-
* @param {string} IPv4Interface - The interface we want to check the PLCs of, if not specified, all interfaces will be checked
34-
* @param {function} cb - The callback that is to be executed after the search is finished
35-
* @memberof Utilities
36-
*/
37-
function discover(cb,IPv4Interface = undefined) {
38-
const ENIPList = new Array();
39-
const IPv4List = new Array();
40-
/* No specified interface means we need to discover them on our own */
41-
if(IPv4Interface == undefined) {
42-
const interfaceList = os.networkInterfaces();
43-
const iFaceListKeys = Object.keys(interfaceList);
44-
const iFaceListLen = iFaceListKeys.length;
45-
for (let i = 0; i < iFaceListLen; i += 1) {
46-
let interfaces = interfaceList[iFaceListKeys[i]];
47-
for (const addresses of interfaces) {
48-
if(addresses["family"] == "IPv4") {
49-
IPv4List.push(addresses);
50-
}
51-
}
52-
}
53-
}
54-
/* An interface has been specified! */
55-
else {
56-
const IPv4RegEx = new RegExp("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
57-
if (!IPv4RegEx.test(IPv4Interface)) throw new Error("Interface must match IPv4 format!");
58-
IPv4List.push({address: IPv4Interface});
59-
}
60-
/* For each interface, we send a broadcast with a listIdentity command */
61-
for (const addresses of IPv4List) {
62-
let dsock = dgram.createSocket("udp4");
63-
dsock.bind(0,addresses["address"], () => {
64-
dsock.setBroadcast(true);
65-
const { listIdentity } = encapsulation;
66-
dsock.send(listIdentity(),44818,"255.255.255.255", (err) => {
67-
if (err) throw new Error ("Error when sending via UDP: "+err);
68-
});
69-
});
70-
71-
dsock.on("error", function(err) {
72-
console.log("UDP Error caught: " + err.stack);
73-
});
74-
75-
dsock.on("message", function(msg) {
76-
if(msg.readUInt16LE(0) == 0x0063) { //Got em! Caught a listIdentity response.
77-
const enipPort = msg.readUInt16BE(34);
78-
const ipString = msg.readUInt8(36).toString()+
79-
"."+msg.readUInt8(37).toString()+
80-
"."+msg.readUInt8(38).toString()+
81-
"."+msg.readUInt8(39).toString();
82-
ENIPList.push({port:enipPort,ip:ipString});
83-
}
84-
});
85-
}
86-
setTimeout(cb, 100, ENIPList);
87-
}
88-
8929
/**
9030
* Sends a broadcast to a specified IPv4 Interface in order to discover all present EthernetIP
9131
* devices.
@@ -94,7 +34,7 @@ function discover(cb,IPv4Interface = undefined) {
9434
* @returns {Promise}
9535
* @memberof Utilities
9636
*/
97-
function getENIPDevicesProm(ipInterface){
37+
function _getENIPDevicesProm(ipInterface){
9838
return new Promise((resolve,reject)=>{
9939
const ENIPList = new Array();
10040
let dsock = dgram.createSocket("udp4");
@@ -123,13 +63,44 @@ function getENIPDevicesProm(ipInterface){
12363
});
12464

12565
dsock.on("message", function(msg) {
126-
if(msg.readUInt16LE(0) == 0x0063) { //Got em! Caught a listIdentity response.
127-
const enipPort = msg.readUInt16BE(34);
128-
const ipString = msg.readUInt8(36).toString()+
129-
"."+msg.readUInt8(37).toString()+
130-
"."+msg.readUInt8(38).toString()+
131-
"."+msg.readUInt8(39).toString();
132-
ENIPList.push({port:enipPort,ip:ipString});
66+
if(msg.readUInt16LE(0) === 0x0063) { //Got em! Caught a listIdentity response.
67+
const plcProperties = {};
68+
let ptr = 32; // Starting with Socket Address
69+
plcProperties.socketAddress = {};
70+
plcProperties.socketAddress.sin_family = msg.readUInt16BE(ptr);
71+
ptr+=2;
72+
plcProperties.socketAddress.sin_port = msg.readUInt16BE(ptr);
73+
ptr+=2;
74+
plcProperties.socketAddress.sin_addr = msg.readUInt8(ptr).toString()+
75+
"."+msg.readUInt8(ptr+1).toString()+
76+
"."+msg.readUInt8(ptr+2).toString()+
77+
"."+msg.readUInt8(ptr+3).toString();
78+
ptr+=4;
79+
plcProperties.socketAddress.sin_zero = 0;
80+
ptr+=8;
81+
82+
// Now follows the asset data
83+
plcProperties.vendorID = msg.readUInt16LE(ptr);
84+
ptr+=2;
85+
plcProperties.deviceType = msg.readUInt16LE(ptr);
86+
ptr+=2;
87+
plcProperties.productCode = msg.readUInt16LE(ptr);
88+
ptr+=2;
89+
plcProperties.majorRevision = msg.readUInt8(ptr);
90+
ptr+=1;
91+
plcProperties.minorRevision = msg.readUInt8(ptr);
92+
ptr+=1;
93+
plcProperties.status = msg.readUInt16LE(ptr);
94+
ptr+=2;
95+
plcProperties.serialNumber = msg.readUInt32LE(ptr);
96+
ptr+=4;
97+
plcProperties.productNameLength = msg.readUInt8(ptr);
98+
ptr+=1;
99+
plcProperties.productName = msg.toString("ascii",ptr,msg.length-1);
100+
ptr+=plcProperties.productNameLength;
101+
plcProperties.state = msg.readUInt8(ptr);
102+
103+
ENIPList.push(plcProperties);
133104
}
134105
});
135106
dsock.on("close", function() {
@@ -147,22 +118,22 @@ function getENIPDevicesProm(ipInterface){
147118
* devices. If no interface is specified, checks all available interfaces and sends a broadcast to them.
148119
*
149120
* @param {string} IPv4Interface - The interface we want to check the PLCs of, if not specified, all interfaces will be checked
150-
* @returns {Promise}
121+
* @returns {Promise} - an object with a key: interface - value: list of attaches devices pair.
151122
* @memberof Utilities
152123
*/
153-
async function discoverProm(IPv4Interface = undefined) {
154-
const ENIPDeviceList = new Array();
124+
async function discover(IPv4Interface = null) {
125+
const ENIPDevice = {};
155126
const IPv4List = new Array();
156127

157128
/* No specified interface means we need to discover them on our own */
158-
if(IPv4Interface == undefined) {
129+
if(IPv4Interface === null) {
159130
const interfaceList = os.networkInterfaces();
160131
const iFaceListKeys = Object.keys(interfaceList);
161132
const iFaceListLen = iFaceListKeys.length;
162133
for (let i = 0; i < iFaceListLen; i += 1) {
163134
let interfaces = interfaceList[iFaceListKeys[i]];
164135
for (const addresses of interfaces) {
165-
if(addresses["family"] == "IPv4") {
136+
if(addresses["family"] === "IPv4") {
166137
IPv4List.push(addresses);
167138
}
168139
}
@@ -177,15 +148,15 @@ async function discoverProm(IPv4Interface = undefined) {
177148

178149
/* For each interface, we send a broadcast with a listIdentity command */
179150
for (const addresses of IPv4List) {
180-
const ENIPDevices = await getENIPDevicesProm(addresses); // Wait for an Interface to get all Devices
151+
const ENIPDevices = await _getENIPDevicesProm(addresses); // Wait for an Interface to get all Devices
181152
if (!Array.isArray(ENIPDevices) || !ENIPDevices.length) {
182153
// No Devices returned
183154
}
184155
else {
185-
ENIPDeviceList.push(ENIPDevices);
156+
ENIPDevice[addresses.address] = ENIPDevices;
186157
}
187158
}
188-
return ENIPDeviceList;
159+
return ENIPDevice;
189160
}
190161

191-
module.exports = { promiseTimeout, delay, discover, discoverProm };
162+
module.exports = { promiseTimeout, delay, discover };

0 commit comments

Comments
 (0)