st-ethernet-ip
Version:
A simple node interface for Ethernet/IP.
252 lines (251 loc) • 9.88 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const enip_1 = require("../enip");
const structure_1 = require("../structure");
class TagList {
/**
* TagList Class for retrieving a list of all tags on a PLC
*/
constructor() {
this.tags = [];
this.templates = {};
}
/**
* Generates the CIP message to request a list of tags
*
* @param instanceID- instance id to start getting a list of object tags
* @param program - (optional) name of the program to search
* @returns message to be sent to PLC
*/
_generateListMessageRequest(instanceID = 0, program) {
const { LOGICAL, DATA } = enip_1.CIP.EPATH.segments;
let pathArray = [];
if (program) {
pathArray.push(DATA.build("Program:" + program));
}
pathArray.push(LOGICAL.build(LOGICAL.types.ClassID, 0x6b)); //Symbol Class ID
if (instanceID === 0) {
pathArray.push(Buffer.from([0x25, 0x00, 0x00, 0x00])); //Start at Instance 0;
}
else {
pathArray.push(LOGICAL.build(LOGICAL.types.InstanceID, instanceID));
}
const requestData = Buffer.from([0x02, 0x00, 0x01, 0x00, 0x02, 0x00]); // 2 Attributes - Attribute 1 and Attribute 2
const request = enip_1.CIP.MessageRouter.build(enip_1.CIP.MessageRouter.services.GET_INSTANCE_ATTRIBUTE_LIST, Buffer.concat(pathArray), requestData);
return request;
}
/**
* Parse CIP response into tag data
*
* @param data - Buffer data to parse
* @param program - (optional) name of the program tag is from (optional)
* @returns Last instance id parsed
*/
_parseAttributeListResponse(data, program) {
let instanceID;
let pointer = 0;
while (pointer < data.length) {
instanceID = data.readUInt32LE(pointer); //Parse instance ID
pointer += 4;
const nameLength = data.readUInt16LE(pointer); // Parse tag Name Length
pointer += 2;
const tagName = data.slice(pointer, pointer + nameLength).toString(); // Parse tag Name
pointer += nameLength;
const tagType = data.readUInt16LE(pointer); // Parse tag type
pointer += 2;
const lastTag = this.tags.findIndex(tag => {
return (tag.id === instanceID && tag.program === program);
});
const tagObj = {
id: instanceID,
name: tagName,
type: this._parseTagType(tagType),
program: program
};
if (lastTag !== -1) {
this.tags[lastTag] = tagObj;
}
else {
this.tags.push(tagObj);
}
}
return instanceID; // Return last instance id
}
/**
* Get and store tag type name from code for all tags
*/
_getTagTypeNames() {
for (const tag of this.tags) {
tag.type.typeName = enip_1.CIP.DataTypes.getTypeCodeString(tag.type.code);
if (!tag.type.typeName && this.templates[tag.type.code]) {
tag.type.typeName = this.templates[tag.type.code]._name;
}
}
}
/**
*
* @param tagType - tag type numerical value
* @returns tag list type object
*/
_parseTagType(tagType) {
let typeCode = null;
let sintPos = null;
if ((tagType & 0x00ff) === 0xc1) {
typeCode = 0x00c1;
sintPos = (tagType & 0x0f00) >> 8;
}
else {
typeCode = tagType & 0x0fff;
}
const structure = !!(tagType & 0x8000);
const reserved = !!(tagType & 0x1000);
const arrayDims = (tagType & 0x6000) >> 13;
return {
code: typeCode,
sintPos: sintPos,
typeName: null,
structure: structure,
arrayDims: arrayDims,
reserved: reserved
};
}
/**
* Parse CIP response into tag data
*
* @param PLC - Controller to get tags from
* @param program - (optional) name of the program tag is from (optional)
* @returns Promise resolves taglist array
*/
getControllerTags(PLC, program = null) {
return new Promise((resolve, reject) => {
const getListAt = (instanceID = 0) => {
const cipData = this._generateListMessageRequest(instanceID, program); // Create CIP Request
PLC.write_cip(cipData); // Write CIP data to PLC
// Response Handler
PLC.on("Get Instance Attribute List", async (err, data) => {
PLC.removeAllListeners("Get Instance Attribute List"); // Make sure we don't handle future calls in this instance
// Check For actual error (Skip too much data)
if (err && err.generalStatusCode !== 6) {
const errData = {
func: "getControllerTags",
instanceID: instanceID,
program: program,
cipReq: cipData,
};
if (Array.isArray(err.ext)) {
err.ext.push(errData);
}
else {
err.ext = [errData];
}
reject(err);
return;
}
// If too much data, call function again starting at last instance + 1
if (err && err.generalStatusCode === 6) {
const lastInstance = this._parseAttributeListResponse(data, program); // Parse response data
getListAt(lastInstance + 1);
}
else {
this._parseAttributeListResponse(data, program); // pArse response data
// If program is not defined fetch tags for existing programs
if (!program) {
for (let prg of this.programs) {
await this.getControllerTags(PLC, prg).catch(reject);
}
await this._getAllTemplates(PLC).catch(reject); // Get All templates for structures
}
this._getTagTypeNames();
resolve(this.tags);
}
});
};
getListAt(0); // Call first time
});
}
/**
* Gets Controller Program Names
*
* @returns array of program names
*/
get programs() {
return this.tags.filter(tag => tag.name.slice(0, 8) === "Program:").map(tag => {
return tag.name.slice(8, tag.name.length);
});
}
/**
* Gets tag info from tag name and program name
*
* @param tagName
* @param program
* @returns
*/
getTag(tagName, program = null) {
return this.tags.find(tag => tag.name === tagName && tag.program === program);
}
/**
*
* @param tagName
* @param program
* @returns tag template or null if none
*/
getTemplateByTag(tagName, program = null) {
const tagArray = tagName.split(".");
const tag = this.tags.find(tag => tag.name.toLowerCase().replace(/\[.*/, "") === tagArray[0].toLowerCase().replace(/\[.*/, "") && String(tag.program).toLowerCase() === String(program).toLowerCase());
if (tag) {
let finalTemplate = this.templates[tag.type.code];
let tagArrayPointer = 1;
while (finalTemplate && tagArrayPointer < tagArray.length) {
const memberName = String(tagArray[tagArrayPointer]).replace(/\[.*/, ""); //removes array indication
const nextTag = finalTemplate._members.find(member => member.name === memberName);
if (nextTag) {
finalTemplate = this.templates[nextTag.type.code];
}
else {
finalTemplate = null;
}
tagArrayPointer++;
}
return finalTemplate;
}
else {
return null;
}
}
/**
* Get all templates from a PLC
*
* @param PLC
* @returns Promise that resolves after all templates are retrieved from PLC
*/
_getAllTemplates(PLC) {
return new Promise(async (resolve, reject) => {
for (const tag of this.tags) {
if (tag.type.structure && !this.templates[tag.type.code]) {
try {
const template = new structure_1.Template();
await template.getTemplate(PLC, tag.type.code);
this.templates[tag.type.code] = template;
}
catch (e) { /* ignore template fetching errors */ }
}
}
let foundTemplate = true;
while (foundTemplate) {
foundTemplate = false;
for (const temp in this.templates) {
for (const member of this.templates[temp]._members) {
if (member.type.structure && !this.templates[member.type.code]) {
foundTemplate = true;
const template = new structure_1.Template();
await template.getTemplate(PLC, member.type.code).catch(reject);
this.templates[member.type.code] = template;
}
}
}
}
resolve();
});
}
}
exports.default = TagList;