@vitormnm/node-red-instructions-ladder-iec-61131-3 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,12 +34,20 @@ A Node-RED node that provides common **IEC 61131-3 Ladder Logic instructions** f
34
34
  | SQR | Square root |
35
35
  | MOV | Move value |
36
36
 
37
+ ### counter Instructions
38
+
39
+ | Instruction | Description |
40
+ | ----------- | -------------- |
41
+ | CTU | counter up |
42
+
43
+
37
44
  ### Output Instructions
38
45
 
39
46
  | Instruction | Description |
40
47
  | ----------- | ---------------------------------------- |
41
48
  | SET | Sets the destination variable to `true` |
42
49
  | RESET | Sets the destination variable to `false` |
50
+ | CTU | OBJ {"Q":true,"CV":15} |
43
51
 
44
52
  ---
45
53
 
@@ -4,51 +4,66 @@
4
4
  <script type="text/javascript">
5
5
 
6
6
  var LADDER_META = {
7
- NO: { srcA: true, srcB: false, dest: false, category: 'Contact' },
8
- NC: { srcA: true, srcB: false, dest: false, category: 'Contact' },
9
- EQ: { srcA: true, srcB: true, dest: false, category: 'Compare' },
10
- NEQ: { srcA: true, srcB: true, dest: false, category: 'Compare' },
11
- GT: { srcA: true, srcB: true, dest: false, category: 'Compare' },
12
- GE: { srcA: true, srcB: true, dest: false, category: 'Compare' },
13
- LT: { srcA: true, srcB: true, dest: false, category: 'Compare' },
14
- LE: { srcA: true, srcB: true, dest: false, category: 'Compare' },
15
- ADD: { srcA: true, srcB: true, dest: true, category: 'Math' },
16
- SUB: { srcA: true, srcB: true, dest: true, category: 'Math' },
17
- MUL: { srcA: true, srcB: true, dest: true, category: 'Math' },
18
- DIV: { srcA: true, srcB: true, dest: true, category: 'Math' },
19
- MOD: { srcA: true, srcB: true, dest: true, category: 'Math' },
20
- MOV: { srcA: true, srcB: false, dest: true, category: 'Math' },
21
- ABS: { srcA: true, srcB: false, dest: true, category: 'Math' },
22
- SQR: { srcA: true, srcB: false, dest: true, category: 'Math' },
23
- SET: { srcA: false, srcB: false, dest: true, category: 'Coil' },
24
- RESET: { srcA: false, srcB: false, dest: true, category: 'Coil' }
7
+ NO: { srcA: true, srcB: false, dest: false, category: 'Contact' },
8
+ NC: { srcA: true, srcB: false, dest: false, category: 'Contact' },
9
+ EQ: { srcA: true, srcB: true, dest: false, category: 'Compare' },
10
+ NEQ: { srcA: true, srcB: true, dest: false, category: 'Compare' },
11
+ GT: { srcA: true, srcB: true, dest: false, category: 'Compare' },
12
+ GE: { srcA: true, srcB: true, dest: false, category: 'Compare' },
13
+ LT: { srcA: true, srcB: true, dest: false, category: 'Compare' },
14
+ LE: { srcA: true, srcB: true, dest: false, category: 'Compare' },
15
+ ADD: { srcA: true, srcB: true, dest: true, category: 'Math' },
16
+ SUB: { srcA: true, srcB: true, dest: true, category: 'Math' },
17
+ MUL: { srcA: true, srcB: true, dest: true, category: 'Math' },
18
+ DIV: { srcA: true, srcB: true, dest: true, category: 'Math' },
19
+ MOD: { srcA: true, srcB: true, dest: true, category: 'Math' },
20
+ MOV: { srcA: true, srcB: false, dest: true, category: 'Math' },
21
+ ABS: { srcA: true, srcB: false, dest: true, category: 'Math' },
22
+ SQR: { srcA: true, srcB: false, dest: true, category: 'Math' },
23
+ SET: { srcA: false, srcB: false, dest: true, category: 'Coil' },
24
+ RESET: { srcA: false, srcB: false, dest: true, category: 'Coil' },
25
+ CTU: { srcA: true, srcB: true, srcC: true, dest: true, category: 'Counter' }
25
26
  };
26
27
 
27
28
  RED.nodes.registerType('instructions-ladder-iec-61131-3', {
28
- category: 'function',
29
- color: '#C0392B',
29
+ category: 'function',
30
+ color: '#C0392B',
30
31
  defaults: {
31
- name: { value: '' },
32
+ name: { value: '' },
32
33
  ladderFunc: { value: 'NO' },
33
- srcA: { value: 'payload', validate: function(v) {
34
- var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
35
- return !meta.srcA || (v !== undefined && v !== '');
36
- }},
37
- srcAType: { value: 'msg' },
38
- srcB: { value: 'payload', validate: function(v) {
39
- var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
40
- return !meta.srcB || (v !== undefined && v !== '');
41
- }},
42
- srcBType: { value: 'msg' },
43
- dest: { value: 'payload', validate: function(v) {
44
- var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
45
- return !meta.dest || (v !== undefined && v !== '');
46
- }},
47
- destType: { value: 'msg' }
34
+ srcA: {
35
+ value: 'payload', validate: function (v) {
36
+ var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
37
+ return !meta.srcA || (v !== undefined && v !== '');
38
+ }
39
+ },
40
+ srcAType: { value: 'msg' },
41
+ srcB: {
42
+ value: 'payload', validate: function (v) {
43
+ var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
44
+ return !meta.srcB || (v !== undefined && v !== '');
45
+ }
46
+ },
47
+ srcBType: { value: 'msg' },
48
+ srcC: {
49
+ value: 'payload', validate: function (v) {
50
+ var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
51
+ return !meta.srcC || (v !== undefined && v !== '');
52
+ }
53
+ },
54
+ srcCType: { value: 'msg' },
55
+ dest: {
56
+ value: 'payload', validate: function (v) {
57
+ var meta = LADDER_META[$('#node-input-ladderFunc').val()] || {};
58
+ return !meta.dest || (v !== undefined && v !== '');
59
+ }
60
+ },
61
+ destType: { value: 'msg' },
62
+
48
63
  },
49
- inputs: 1,
64
+ inputs: 1,
50
65
  outputs: 1,
51
- icon: 'ladder.svg',
66
+ icon: 'ladder.svg',
52
67
  paletteLabel: 'ladder iec',
53
68
 
54
69
  label: function () {
@@ -68,18 +83,55 @@
68
83
  typeField: '#node-input-srcBType',
69
84
  types: ['msg', 'flow', 'global', 'num', 'bool', 'str']
70
85
  });
86
+ $('#node-input-srcC').typedInput({
87
+ typeField: '#node-input-srcCType',
88
+ types: ['msg', 'flow', 'global', 'num', 'bool', 'str']
89
+ });
90
+
71
91
  $('#node-input-dest').typedInput({
72
92
  typeField: '#node-input-destType',
73
93
  types: ['msg', 'flow', 'global']
74
94
  });
75
95
 
96
+
76
97
  function updateFields() {
77
- var fn = $('#node-input-ladderFunc').val();
98
+ var fn = $('#node-input-ladderFunc').val();
78
99
  var meta = LADDER_META[fn] || {};
100
+
79
101
  $('#row-srcA').toggle(!!meta.srcA);
80
102
  $('#row-srcB').toggle(!!meta.srcB);
103
+ $('#row-srcC').toggle(!!meta.srcC);
81
104
  $('#row-dest').toggle(!!meta.dest);
105
+ $('#row-destB').toggle(!!meta.destB);
82
106
  $('#ladder-category').text(meta.category ? '[' + meta.category + ']' : '');
107
+
108
+
109
+ if (fn === "CTU") {
110
+
111
+ $("label[for='node-input-srcA']").html(
112
+ '<i class="fa fa-arrow-right"></i> CU'
113
+ );
114
+ $("label[for='node-input-srcB']").html(
115
+ '<i class="fa fa-arrow-right"></i> RESET'
116
+ );
117
+ $("label[for='node-input-srcC']").html(
118
+ '<i class="fa fa-arrow-right"></i> PV'
119
+ );
120
+
121
+ } else {
122
+
123
+ $("label[for='node-input-srcA']").html(
124
+ '<i class="fa fa-arrow-right"></i> Source A'
125
+ );
126
+ $("label[for='node-input-srcB']").html(
127
+ '<i class="fa fa-arrow-right"></i> Source B'
128
+ );
129
+ $("label[for='node-input-srcC']").html(
130
+ '<i class="fa fa-arrow-right"></i> Source C'
131
+ );
132
+ }
133
+
134
+
83
135
  }
84
136
 
85
137
  $('#node-input-ladderFunc').on('change', updateFields);
@@ -103,11 +155,11 @@
103
155
  <div class="form-row">
104
156
  <label for="node-input-ladderFunc"><i class="fa fa-microchip"></i> Instruction</label>
105
157
  <select id="node-input-ladderFunc" style="width:70%;">
106
- <optgroup label="── Contacts ──">
158
+ <optgroup label="Contacts ">
107
159
  <option value="NO">-||- Normally Open Contact </option>
108
160
  <option value="NC">-|/|- Normally Closed Contact</option>
109
161
  </optgroup>
110
- <optgroup label="── Comparators ──">
162
+ <optgroup label="Comparators ">
111
163
  <option value="EQ">EQ – EQal (A == B)</option>
112
164
  <option value="NEQ">NEQ – Not EQal (A != B)</option>
113
165
  <option value="GT">GT – Greater Than (A &gt; B)</option>
@@ -115,7 +167,7 @@
115
167
  <option value="LT">LT – LTs Than (A &lt; B)</option>
116
168
  <option value="LE">LE – LTs or EQal (A &lt;= B)</option>
117
169
  </optgroup>
118
- <optgroup label="── Math ──">
170
+ <optgroup label="Math ">
119
171
  <option value="ADD">ADD – Add (A + B → dest)</option>
120
172
  <option value="SUB">SUB – Subtract (A - B → dest)</option>
121
173
  <option value="MUL">MUL – Multiply (A * B → dest)</option>
@@ -125,9 +177,11 @@
125
177
  <option value="ABS">ABS – Absolute Value (|A| → dest)</option>
126
178
  <option value="SQR">SQR – Square Root (√A → dest)</option>
127
179
  </optgroup>
128
- <optgroup label="── Output Coils ──">
180
+ <optgroup label="Output Coils ">
129
181
  <option value="SET">-(S)- Set/Latch</option>
130
182
  <option value="RESET">-(R)- Reset/Unlatch</option>
183
+ <optgroup label="Counter ">
184
+ <option value="CTU">CTU</option>
131
185
  </optgroup>
132
186
  </select>
133
187
  <span id="ladder-category" style="margin-left:8px; color:#999; font-size:0.85em;"></span>
@@ -147,12 +201,20 @@
147
201
  <input type="text" id="node-input-srcB" style="width:60%;">
148
202
  </div>
149
203
 
204
+ <div class="form-row" id="row-srcC">
205
+ <label for="node-input-srcC"><i class="fa fa-arrow-right"></i> Source C</label>
206
+ <input type="hidden" id="node-input-srcCType">
207
+ <input type="text" id="node-input-srcC" style="width:60%;">
208
+ </div>
209
+
150
210
  <div class="form-row" id="row-dest">
151
211
  <label for="node-input-dest"><i class="fa fa-arrow-left"></i> Destination</label>
152
212
  <input type="hidden" id="node-input-destType">
153
213
  <input type="text" id="node-input-dest" style="width:60%;">
154
214
  </div>
155
215
 
216
+
217
+
156
218
  <hr/>
157
219
 
158
220
  <div class="form-row" style="color:#888; font-size:0.85em;">
@@ -215,4 +277,4 @@
215
277
  <em>Right</em>: resolved values + result.<br><br>
216
278
  🟢 Green dot = TRUE &nbsp;|&nbsp; ⚫ Grey ring = FALSE &nbsp;|&nbsp; 🔴 Red = Error
217
279
  </p>
218
- </script>
280
+ </script>
@@ -1,5 +1,5 @@
1
1
  module.exports = function (RED) {
2
-
2
+ const registry = require("./registry");
3
3
  // ─── Helpers ──────────────────────────────────────────────────────────────
4
4
 
5
5
  function resolveValue(node, msg, type, key) {
@@ -57,41 +57,75 @@ module.exports = function (RED) {
57
57
  const vB = fmt(valB);
58
58
  const res = result ? 'TRUE' : 'FALSE';
59
59
 
60
+ let fill
61
+ let shape
62
+ let text = `${fn} executed`;
63
+
60
64
  // ── Contacts ─────────────────────────────────────────────────────────
61
- // NO motor_run(true)→TRUE
62
- if (fn === 'NO' || fn === 'NC')
63
- return `${fn} ${nA}(${vA})→${res}`;
65
+ if (fn === 'NO' || fn === 'NC') {
66
+ text = `${fn} ${nA}(${vA})→${res}`;
67
+ }
64
68
 
65
69
  // ── Comparators ──────────────────────────────────────────────────────
66
- // GT temp(85)>limit(80)→TRUE
67
70
  const OPS = { EQ: '==', NEQ: '!=', GT: '>', GE: '>=', LT: '<', LE: '<=' };
68
- if (OPS[fn])
69
- return `${fn} ${nA}(${vA})${OPS[fn]}${nB}(${vB})→${res}`;
71
+ if (OPS[fn]) {
72
+ text = `${fn} ${nA}(${vA})${OPS[fn]}${nB}(${vB})→${res}`;
73
+ }
70
74
 
71
75
  // ── Math (two operands) ───────────────────────────────────────────────
72
- // ADD a(10)+b(5)→result=15
73
- // DIV x(9)/y(0)→÷0ERR
74
76
  const MATH2 = { ADD: '+', SUB: '-', MUL: '*', DIV: '/', MOD: '%' };
75
77
  if (MATH2[fn]) {
76
- if ((fn === 'DIV' || fn === 'MOD') && Number(valB) === 0)
77
- return `${fn} ${nA}(${vA})${MATH2[fn]}${nB}(0)→÷0ERR`;
78
- return `${fn} ${nA}(${vA})${MATH2[fn]}${nB}(${vB})→${nD}=${fmt(computed)}`;
78
+ if ((fn === 'DIV' || fn === 'MOD') && Number(valB) === 0) {
79
+ text = `${fn} ${nA}(${vA})${MATH2[fn]}${nB}(0)→÷0ERR`;
80
+ } else {
81
+ text = `${fn} ${nA}(${vA})${MATH2[fn]}${nB}(${vB})→${nD}=${fmt(computed)}`;
82
+ }
79
83
  }
80
84
 
81
85
  // ── Math (one operand) ────────────────────────────────────────────────
82
- // MOV src(42)→dest
83
- // ABS |x(-7)|→out=7
84
- // SQR √n(64)→root=8
85
- if (fn === 'MOV') return `MOV ${nA}(${vA})→${nD}`;
86
- if (fn === 'ABS') return `ABS |${nA}(${vA})|→${nD}=${fmt(computed)}`;
87
- if (fn === 'SQR') return `SQR √${nA}(${vA})→${nD}=${fmt(computed)}`;
86
+ if (fn === 'MOV') {
87
+ text = `MOV ${nA}(${vA})→${nD}`;
88
+ }
89
+
90
+ if (fn === 'ABS') {
91
+ text = `ABS |${nA}(${vA})|→${nD}=${fmt(computed)}`;
92
+ }
93
+
94
+ if (fn === 'SQR') {
95
+ text = `SQR √${nA}(${vA})→${nD}=${fmt(computed)}`;
96
+ }
88
97
 
89
98
  // ── Output Coils ──────────────────────────────────────────────────────
90
- // SET →motor=TRUE | RST →alarm=FALSE
91
- if (fn === 'SET') return `SET →${nD}=TRUE`;
92
- if (fn === 'RESET') return `RST →${nD}=FALSE`;
99
+ if (fn === 'SET') {
100
+ text = `SET →${nD}=TRUE`;
101
+ }
102
+
103
+ if (fn === 'RESET') {
104
+ text = `RST →${nD}=FALSE`;
105
+ }
106
+
107
+ if (fn === 'CTU') {
108
+ text = `CTU→CV(${result.CV})=${result.Q}`;
109
+ }
110
+
111
+ //Build color
112
+ if (fn == "CTU") {
113
+ fill = result.Q ? 'green' : 'grey';
114
+ shape = result.Q ? 'dot' : 'ring';
115
+
116
+ } else {
117
+ fill = result ? 'green' : 'grey';
118
+ shape = result ? 'dot' : 'ring';
119
+ }
120
+
121
+
122
+
93
123
 
94
- return `${fn} executed`;
124
+ return {
125
+ text: text,
126
+ fill: fill,
127
+ shape: shape
128
+ }
95
129
  }
96
130
 
97
131
  // ─── Instruction Execution ────────────────────────────────────────────────
@@ -101,13 +135,20 @@ module.exports = function (RED) {
101
135
  const fn = config.ladderFunc;
102
136
  const valA = resolveValue(node, msg, config.srcAType, config.srcA);
103
137
  const valB = resolveValue(node, msg, config.srcBType, config.srcB);
138
+ const valC = resolveValue(node, msg, config.srcCType, config.srcC);
139
+
140
+
141
+
104
142
  const nameA = config.srcA || '';
105
143
  const nameB = config.srcB || '';
106
- const nameDest = config.dest || '';
144
+ const nameC = config.srcC || '';
107
145
 
146
+ const nameDest = config.dest || '';
147
+ const nameDestB = config.destB || '';
108
148
 
109
149
  let payload = null;
110
150
  let result = false;
151
+ let resultB = false;
111
152
  let sendMsg = true;
112
153
  let Msg = true;
113
154
  let computed; // for math instructions that produce a new value
@@ -184,6 +225,50 @@ module.exports = function (RED) {
184
225
  case 'RESET':
185
226
  result = false;
186
227
  resultOperation = true
228
+ break;
229
+
230
+ case 'CTU':
231
+ // SET VALUE
232
+ var memory = registry.getMemory(node)
233
+ if (valA === true) {
234
+ var memory = registry.getMemory(node)
235
+ //Set memory
236
+ if (memory) {
237
+ memory = memory + 1
238
+ registry.setMemory(node, memory)
239
+ } else {
240
+ memory = 1
241
+ registry.setMemory(node, memory)
242
+ }
243
+ }
244
+
245
+ if(memory === undefined){
246
+ memory = 0;
247
+ }
248
+
249
+ //RESET
250
+ if (valB === true) {
251
+ memory = 0
252
+ registry.setMemory(node, memory)
253
+ }
254
+
255
+
256
+ //set result
257
+ if (memory >= valC) {
258
+ result = {
259
+ Q: true,
260
+ CV: memory
261
+ }
262
+
263
+ resultOperation = true
264
+
265
+ } else {
266
+ resultOperation = true
267
+ result = {
268
+ Q: false,
269
+ CV: memory
270
+ }
271
+ }
187
272
 
188
273
  break;
189
274
 
@@ -192,12 +277,13 @@ module.exports = function (RED) {
192
277
  result = false;
193
278
  }
194
279
 
195
- const statusText = buildStatus(fn, nameA, nameB, nameDest, valA, valB, result, computed);
196
- return { result, resultOperation, statusText, sendMsg };
280
+ const statusConfig = buildStatus(fn, nameA, nameB, nameDest, valA, valB, result, computed);
281
+ return { result, resultOperation, statusConfig, sendMsg };
197
282
  }
198
283
 
199
284
  function writeNode(node, msg, config, sendMsg, result, resultOperation) {
200
285
 
286
+
201
287
  const ladderFunc = config.ladderFunc;
202
288
  const dest = config.dest;
203
289
  const destType = config.destType;
@@ -219,6 +305,8 @@ module.exports = function (RED) {
219
305
  }
220
306
  }
221
307
 
308
+
309
+
222
310
  // ─── Node Definition ──────────────────────────────────────────────────────
223
311
 
224
312
  function instructions_ladder_IEC_61131_3(config) {
@@ -229,15 +317,15 @@ module.exports = function (RED) {
229
317
 
230
318
  node.on('input', function (msg) {
231
319
  try {
232
- const { result, resultOperation, statusText, sendMsg } = executeLadder(node, msg, config);
320
+ const { result, resultOperation, statusConfig, sendMsg } = executeLadder(node, msg, config);
233
321
 
234
322
  node.status({
235
- fill: result ? 'green' : 'grey',
236
- shape: result ? 'dot' : 'ring',
237
- text: statusText
323
+ fill: statusConfig.fill,
324
+ shape: statusConfig.shape,
325
+ text: statusConfig.text,
238
326
  });
239
327
 
240
- msg.ladder = { func: config.ladderFunc, result, status: statusText };
328
+ msg.ladder = { func: config.ladderFunc, result, status: statusConfig };
241
329
  //Write payload and msg
242
330
  writeNode(node, msg, config, sendMsg, result, resultOperation)
243
331
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitormnm/node-red-instructions-ladder-iec-61131-3",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/registry.js ADDED
@@ -0,0 +1,17 @@
1
+ const ctuMemory = new Map();
2
+
3
+
4
+ function setMemory(node , value) {
5
+ ctuMemory.set(node.id, value);
6
+ }
7
+
8
+
9
+ function getMemory(node) {
10
+ return ctuMemory.get(node.id);
11
+ }
12
+
13
+
14
+ module.exports = {
15
+ setMemory,
16
+ getMemory
17
+ }