st-ethernet-ip
Version:
A simple node interface for Ethernet/IP.
228 lines (207 loc) • 7.41 kB
text/typescript
import net from "net";
import Controller from "../controller";
import { EventEmitter } from "events";
import Tag from "../tag";
type cmAllValues = {
[index: string]: any
}
type cmAllControllersValues = {
[index: string] : cmAllValues;
}
class ControllerManager{
controllers: extController[]
/**
* Controller Manager manages PLC connections and tags. Automatically scans and writes tags that have values changed. Reconnects automatically.
*/
constructor() {
this.controllers = [];
}
/**
* Adds controller to be managed by controller manager
*
* @param ipAddress - controller IP address
* @param slot - Slot number or custom path
* @param rpi - How often to scan tag value in ms
* @param connected - Use connected messaging
* @param retrySP - How long to wait to retry broken connection in ms
* @param opts - custom options for future use
* @returns Extended Controller object
*/
addController(ipAddress: string, slot : number | Buffer = 0, rpi: number = 100, connected: boolean = true, retrySP: number = 3000, opts: any = {}): extController {
const contLength = this.controllers.push(new extController(ipAddress, slot, rpi, connected, retrySP, opts));
return this.controllers[contLength - 1];
}
/**
* Returns all current controller tags
*
* @returns tag values indexed by controller ip address and tag name
*/
getAllValues(): cmAllControllersValues {
let allTags: cmAllControllersValues = {};
this.controllers.forEach(controller => {
let tags = {};
controller.tags.forEach(tag => {
tags[tag.tag.name] = tag.tag.value;
});
allTags[controller.ipAddress] = tags;
});
return allTags;
}
}
export declare interface extController {
reconnect: boolean;
ipAddress: string;
slot: number | Buffer;
opts: any;
rpi: any;
PLC: Controller;
tags: any[];
connected: boolean;
conncom: any;
retryTimeSP: number;
on(event: string, listener: Function): this;
on(event: 'Connected', listener: (this: this) => {}): this;
on(event: 'TagChanged', listener: (tag: Tag, previousValue: any) => {}): this;
on(event: 'TagInit', listener: (tag: Tag) => {}): this;
on(event: 'TagUnknown', listener: (tag: Tag) => {}): this;
on(event: 'Disconnected', listener: () => {}): this;
}
export class extController extends EventEmitter{
/**
* Extended Controller Class To Manage Rebuilding Tags after as disconnect / reconnect event
*
* @param ipAddress - controller IP address
* @param slot - Slot number or custom path
* @param rpi - How often to scan tag value in ms
* @param connected - Use connected messaging
* @param retrySP - How long to wait to retry broken connection in ms
* @param opts - custom options for future use
*/
constructor(ipAddress: string, slot: number | Buffer = 0, rpi: number = 100, connCom: boolean = true, retrySP: number = 3000, opts: any = {}) {
super();
this.reconnect = true;
this.ipAddress = ipAddress;
this.slot = slot;
this.opts = opts;
this.rpi = rpi;
this.PLC = null;
this.tags = [];
this.connected = false;
this.conncom = connCom;
this.retryTimeSP = retrySP;
}
/**
* Connect To Controller
*/
connect(reconnect = true) {
this.reconnect = reconnect;
this.PLC = new Controller(this.conncom);
this.PLC.connect(this.ipAddress, this.slot).then(async () => {
this.connected = true;
this.PLC.scan_rate = this.rpi;
this.tags.forEach(tag => {
tag.tag = this.PLC.newTag(tag.tagname, tag.program, true, tag.arrayDims, tag.arraySize);
this.addTagEvents(tag.tag);
});
this.PLC.scan().catch(e => {this.errorHandle(e);});
this.emit("Connected", this);
}).catch((e: Error) => {this.errorHandle(e);});
}
/**
* Add Tag Events to emit from controller
*
* @param tag
*/
addTagEvents(tag: Tag) {
tag.on("Changed", (chTag, prevValue) => {
this.emit("TagChanged", tag, prevValue);
});
tag.on("Initialized", () => {
this.emit("TagInit", tag);
});
tag.on("Unknown", () => {
this.emit("TagUnknown", tag);
})
}
/**
* Handle Controller Error during connect or while scanning
*
* @param e - Error emitted
*/
errorHandle(e: any) {
this.emit("Error", e);
this.connected = false;
this.PLC.destroy();
this.PLC._removeControllerEventHandlers();
this.emit("Disconnected");
if(this.reconnect) {setTimeout(() => {this.connect();}, this.retryTimeSP);}
}
/**
* Add tag to controller scan list.
*
* @param tagname - Tag Name
* @param program - Program Name
* @param arrayDims - Array Dimensions
* @param arraySize - Array Size
* @returns Tag object
*/
addTag(tagname: string, program: string = null, arrayDims: number = 0, arraySize: number = 0x01):Tag {
let tagItem = this.tags.find(tag => {
return tag.tagname === tagname && tag.program === program;
})
if (tagItem) {
return tagItem.tag
} else {
let tag;
if (this.connected) {
tag = this.PLC.newTag(tagname, program, true, arrayDims, arraySize);
this.addTagEvents(tag);
}
this.tags.push({
tagname: tagname,
program: program,
arrayDims: arrayDims,
arraySize: arraySize,
tag: tag
});
return tag;
}
}
/**
* Remove tag from controller scan list.
*
* @param tagname - Tag Name
* @param program - Program Name
*/
removeTag(tagname: string, program: string = null) {
tagname = (program) ? tagname.slice(tagname.indexOf(".") + 1) : tagname;
let tagIndex = this.tags.findIndex(tag => {
return tag.tagname === tagname && tag.program === program;
})
if (tagIndex > -1) {
this.PLC.state.subs.remove(this.tags[tagIndex].tag);
this.tags.splice(tagIndex, 1);
}
}
/**
* Disconnect Controller Completely
*
* @returns Promise resolved after disconnect of controller
*/
disconnect(): Promise<void> {
return new Promise<void>((resolve) => {
this.connected = false;
this.reconnect = false;
this.PLC.disconnect().then(() => {
this.emit("Disconnected");
resolve();
}).catch(() => {
net.Socket.prototype.destroy.call(this.PLC);
this.emit("Disconnected");
resolve();
});
});
}
}
export default ControllerManager;
export {Tag};