Skip to content

WIP testing-mapping #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 182 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ PLC.connect("192.168.1.1", 0).then(async () => {

#### 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`...
```javascript
const { Controller, Tag } = require("ethernet-ip");
Expand All @@ -125,7 +123,6 @@ 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`...
Expand Down Expand Up @@ -237,6 +234,188 @@ PLC.forEach(tag => {
});
```

### User Defined Types

User Defined Types must have a Template. Templates are managed by the controller.
Create a new template and add it to the controller. The template's name can be passed in as the type when creating a Tag.
```javascript
const { Controller, Tag, Template, CIP } = require("ethernet-ip");
const { Types} = CIP.DataTypes;

const PLC = new Controller();

// add template to controller with name and type definition
// the type definition is an object where the key is the member name
// and the value is the member type
PLC.addTemplate({
name: "udt1",
definition: {
member1: Types.DINT;
member2: Types.DINT;
}
});

// create
const fooTag = new Tag("tag", null, "udt1");

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

// udt tags must be read before use
await PLC.readTag(fooTag);

console.log(fooTag.value.member1);
console.log(fooTag.value.member2);

fooTag.value.member1 = 5;
fooTag.value.member2 = 10;

await PLC.writeTag(fooTag);

});
```

Specify arrays by setting a member to an object with a `type` and `length`.
```javascript
const { Controller, Tag, Template, CIP } = require("ethernet-ip");
const { Types} = CIP.DataTypes;

const PLC = new Controller();

// member 2 is an array of DINT with length 2
PLC.addTemplate({
name: "udt1",
definition: {
member1: Types.DINT,
member2: { type: Types.DINT, length: 2 },
}
});

const fooTag = new Tag("tag", null, "udt1");

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

// udt tags must be read before use
await PLC.readTag(fooTag);

console.log(fooTag.value.member1);
console.log(fooTag.value.member2[0]);
console.log(fooTag.value.member2[1]);

fooTag.value.member1 = 5;
fooTag.value.member2[0] = 10;
fooTag.value.member2[1] = 20;

await PLC.writeTag(fooTag);
});
```

Nest UDTs by specifying a UDT name as a type. The child UDT template *MUST* be added before the parent UDT template.
```javascript
const { Controller, Tag, Template, CIP } = require("ethernet-ip");
const { Types} = CIP.DataTypes;

const PLC = new Controller();

// this template MUST be added first
PLC.addTemplate({
name: "udt1",
definition: {
member1: Types.DINT,
member2: { type: Types.DINT, length: 2 },
}
});

// this template references "udt1" and must be added AFTER "udt1"
PLC.addTemplate({
name: "udt2",
definition: {
nestedUdt: "udt1",
anotherMember: Types.REAL,
}
});

const fooTag = new Tag("tag", null, "udt2");

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

// udt tags must be read before use
await PLC.readTag(fooTag);

console.log(fooTag.value.nestedUdt.member1);
console.log(fooTag.value.nestedUdt.member2[0]);
console.log(fooTag.value.nestedUdt.member2[1]);
console.log(fooTag.value.anotherMember);

fooTag.value.nestedUdt.member1 = 5;
fooTag.value.nestedUdt.member2[0] = 10;
fooTag.value.nestedUdt.member2[1] = 20;
fooTag.value.anotherMember = 40;

await PLC.writeTag(fooTag);
});
```

### Strings

Strings can either be specified with their LEN and DATA members or by passing in a "string_length" value.
All templates with a STRING signature will have `getString()` and `setString(value)` functions on the
string member to allow for converstion between strings and the `LEN` and `DATA` members.
```javascript
const { Controller, Tag, Template, CIP } = require("ethernet-ip");
const { Types} = CIP.DataTypes;

const PLC = new Controller();

// create a string with LEN and DATA members
PLC.addTemplate({
name: "String10",
definition: {
LEN: Types.DINT;
DATA: { type: Types.DINT, length: 10 };
}
});

// create a string by passing in string_length
PLC.addTemplate({
name: "AnotherString",
string_length: 12
});

const fooTag = new Tag("tag1", null, "STRING"); // predefined 82 char string
const barTag = new Tag("tag2", null, "String10"); // user defined 10 char string
const bazTag = new Tag("tag3", null, "AnotherString"); // user defined 12 char string

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

// udt tags must be read before use
await PLC.readTag(fooTag);
await PLC.readTag(barTag);
await PLC.readTag(baxTag);

// access LEN, DATA, or getString()
console.log(fooTag.value.LEN, fooTag.value.DATA, fooTag.value.getString());
console.log(barTag.value.LEN, barTag.value.DATA, barTag.value.getString());
console.log(bazTag.value.LEN, bazTag.value.DATA, bazTag.value.getString());

// set LEN and DATA
fooTag.value.LEN = 8;
fooTag.value.DATA[0] = 110;
fooTag.value.DATA[1] = 101;
fooTag.value.DATA[2] = 119;
fooTag.value.DATA[3] = 32;
fooTag.value.DATA[4] = 116;
fooTag.value.DATA[5] = 101;
fooTag.value.DATA[6] = 120;
fooTag.value.DATA[7] = 116;

// or use the setString(value) function
barTag.value.setString("new text");

await PLC.writeTag(fooTag);
await PLC.writeTag(barTag);
});
```

## Demos

- **Monitor Tags for Changes Demo**
Expand Down
Binary file modified manuals/Data Access.pdf
Binary file not shown.
Binary file added manuals/TypeEncode_CIPRW.pdf
Binary file not shown.
13 changes: 13 additions & 0 deletions src/controller/controller.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,17 @@ describe("Controller Class", () => {
expect(plc.time).toMatchSnapshot();
});
});

describe("Add Templates Method", () => {
it("Should add templates", () => {
const plc = new Controller();

expect(plc.templates).not.toHaveProperty("udt");

plc.addTemplate({name: "udt"});

expect(plc.templates).toHaveProperty("udt");

});
});
});
36 changes: 34 additions & 2 deletions src/controller/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const { ENIP, CIP } = require("../enip");
const dateFormat = require("dateformat");
const TagGroup = require("../tag-group");
const Template = require("../template");
const TemplateMap = require("../template/atomics");
const { delay, promiseTimeout } = require("../utilities");
const Queue = require("task-easy");

Expand Down Expand Up @@ -33,7 +35,8 @@ class Controller extends ENIP {
},
subs: new TagGroup(compare),
scanning: false,
scan_rate: 200 //ms
scan_rate: 200, //ms
templates: TemplateMap()
};

this.workers = {
Expand Down Expand Up @@ -85,6 +88,17 @@ class Controller extends ENIP {
return this.state.controller;
}

/**
* Gets the Controller Templates Object
*
* @readonly
* @memberof Controller
* @returns {object}
*/
get templates() {
return this.state.templates;
}

/**
* Fetches the last timestamp retrieved from the controller
* in human readable form
Expand Down Expand Up @@ -453,6 +467,16 @@ class Controller extends ENIP {
forEach(callback) {
this.state.subs.forEach(callback);
}

/**
* Adds new Template to Controller Templates
*
* @param {object} template
* @memberof Controller
*/
addTemplate(template){
new Template(template).addToTemplates(this.state.templates);
}
// endregion

// region Private Methods
Expand All @@ -474,12 +498,13 @@ class Controller extends ENIP {
* @memberof Controller
*/
async _readTag(tag, size = null) {
tag.controller = this;

const MR = tag.generateReadMessageRequest(size);

this.write_cip(MR);

const readTagErr = new Error(`TIMEOUT occurred while writing Reading Tag: ${tag.name}.`);

// Wait for Response
const data = await promiseTimeout(
new Promise((resolve, reject) => {
Expand Down Expand Up @@ -507,6 +532,8 @@ class Controller extends ENIP {
* @memberof Controller
*/
async _writeTag(tag, value = null, size = 0x01) {
tag.controller = this;

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

this.write_cip(MR);
Expand All @@ -522,6 +549,7 @@ class Controller extends ENIP {
if (err) reject(err);

tag.unstageWriteRequest();

resolve(data);
});

Expand Down Expand Up @@ -549,6 +577,8 @@ class Controller extends ENIP {
* @memberof Controller
*/
async _readTagGroup(group) {
group.setController(this);

const messages = group.generateReadMessageRequests();

const readTagGroupErr = new Error("TIMEOUT occurred while writing Reading Tag Group.");
Expand Down Expand Up @@ -585,6 +615,8 @@ class Controller extends ENIP {
* @memberof Controller
*/
async _writeTagGroup(group) {
group.setController(this);

const messages = group.generateWriteMessageRequests();

const writeTagGroupErr = new Error("TIMEOUT occurred while writing Reading Tag Group.");
Expand Down
2 changes: 1 addition & 1 deletion src/enip/cip/data-types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const Types = {
EPATH: 0xdc,
ENGUNIT: 0xdd,
STRINGI: 0xde,
STRUCT: 0xa002
STRUCT: 0x02a0
};

/**
Expand Down
10 changes: 5 additions & 5 deletions src/enip/cip/epath/segments/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ const build = (data, ANSI = true) => {
throw new Error("Data Segment Data Must be a String or Buffer");

// Build Element Segment If Int
if (data % 1 === 0) return elementBuild(parseInt(data));
if (data % 1 === 0) return _elementBuild(parseInt(data));

// Build symbolic segment by default
return symbolicBuild(data, ANSI);
return _symbolicBuild(data, ANSI);
};

/**
* Builds EPATH Symbolic Segment
*
* @param {string|buffer} data
* @param {boolean} [ANSI=true] Declare if ANSI Extended or Simple
* @param {boolean} ANSI Declare if ANSI Extended or Simple
* @returns {buffer}
*/
const symbolicBuild = (data, ANSI = true) => {
const _symbolicBuild = (data, ANSI) => {
// Initialize Buffer
let buf = Buffer.alloc(2);

Expand All @@ -59,7 +59,7 @@ const symbolicBuild = (data, ANSI = true) => {
* @param {string} data
* @returns {buffer}
*/
const elementBuild = data => {
const _elementBuild = data => {
// Get Element Length - Data Access 2 - IOI Segments - Element Segments
let type;
let dataBuf;
Expand Down
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const Controller = require("./controller");
const Tag = require("./tag");
const TagGroup = require("./tag-group");
const Template = require("./template");
const EthernetIP = require("./enip");
const Types = require("./enip/cip/data-types"); // ok?
const util = require("./utilities");

module.exports = { Controller, Tag, TagGroup, EthernetIP, util };
module.exports = { Controller, Tag, TagGroup, Template, EthernetIP, Types, util };
Loading