nodalis-compiler 1.0.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 +134 -0
- package/package.json +59 -0
- package/src/compilers/CPPCompiler.js +272 -0
- package/src/compilers/Compiler.js +108 -0
- package/src/compilers/JSCompiler.js +293 -0
- package/src/compilers/iec-parser/parser.js +4254 -0
- package/src/compilers/st-parser/expressionConverter.js +155 -0
- package/src/compilers/st-parser/gcctranspiler.js +237 -0
- package/src/compilers/st-parser/jstranspiler.js +254 -0
- package/src/compilers/st-parser/parser.js +367 -0
- package/src/compilers/st-parser/tokenizer.js +78 -0
- package/src/compilers/support/generic/json.hpp +25526 -0
- package/src/compilers/support/generic/modbus.cpp +378 -0
- package/src/compilers/support/generic/modbus.h +124 -0
- package/src/compilers/support/generic/nodalis.cpp +421 -0
- package/src/compilers/support/generic/nodalis.h +798 -0
- package/src/compilers/support/generic/opcua.cpp +267 -0
- package/src/compilers/support/generic/opcua.h +50 -0
- package/src/compilers/support/generic/open62541.c +151897 -0
- package/src/compilers/support/generic/open62541.h +50357 -0
- package/src/compilers/support/jint/nodalis/Nodalis.sln +28 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/ModbusClient.cs +200 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +817 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +16 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +172 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +275 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/NodalisPLC.csproj +19 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +197 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.bat +5 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.sh +5 -0
- package/src/compilers/support/jint/nodalis/build.bat +25 -0
- package/src/compilers/support/jint/nodalis/build.sh +31 -0
- package/src/compilers/support/nodejs/IOClient.js +110 -0
- package/src/compilers/support/nodejs/modbus.js +115 -0
- package/src/compilers/support/nodejs/nodalis.js +662 -0
- package/src/compilers/support/nodejs/opcua.js +194 -0
- package/src/nodalis.js +174 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
// Copyright [2025] Nathan Skipper
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @description Nodalis PLC for NodeJs
|
|
18
|
+
* @author Nathan Skipper, MTI
|
|
19
|
+
* @version 1.0.2
|
|
20
|
+
* @copyright Apache 2.0
|
|
21
|
+
*/
|
|
22
|
+
import {IOClient, IOMap, setTiming, setMemoryAccess} from "./IOClient.js"
|
|
23
|
+
import {ModbusClient} from "./modbus.js";
|
|
24
|
+
import { OPCClient } from "./opcua.js";
|
|
25
|
+
|
|
26
|
+
const MEMORY = Array.from({ length: 64 }, () =>
|
|
27
|
+
Array.from({ length: 16 }, () => new Uint8Array(8))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export let PROGRAM_START = Date.now();
|
|
31
|
+
export function elapsed() {
|
|
32
|
+
return Date.now() - PROGRAM_START;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setTiming(elapsed);
|
|
36
|
+
setMemoryAccess({
|
|
37
|
+
readBitFunc: readBit,
|
|
38
|
+
readByteFunc: readByte,
|
|
39
|
+
readWordFunc: readWord,
|
|
40
|
+
readDWordFunc: readDWord,
|
|
41
|
+
writeBitFunc: writeBit,
|
|
42
|
+
writeByteFunc: writeByte,
|
|
43
|
+
writeWordFunc: writeWord,
|
|
44
|
+
writeDWordFunc: writeDWord
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const Statics = {};
|
|
48
|
+
|
|
49
|
+
export function newStatic(varname, Class){
|
|
50
|
+
var ret = null;
|
|
51
|
+
if(typeof Statics[varname] !== "undefined"){
|
|
52
|
+
ret = Statics[varname];
|
|
53
|
+
}
|
|
54
|
+
else{
|
|
55
|
+
ret = new Class();
|
|
56
|
+
Statics[varname] = ret;
|
|
57
|
+
}
|
|
58
|
+
return ret;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolve(val) {
|
|
62
|
+
return val instanceof RefVar ? val.value : val;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createReference(address){
|
|
66
|
+
return new RefVar(address);
|
|
67
|
+
}
|
|
68
|
+
export class RefVar {
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param {string} address
|
|
72
|
+
*/
|
|
73
|
+
constructor(address) {
|
|
74
|
+
this.address = address;
|
|
75
|
+
this.type = "bit"; // "bit", "byte", "word", "dword"
|
|
76
|
+
const width = address.substring(2, 3).toUpperCase();
|
|
77
|
+
if(address.indexOf(".") === -1){
|
|
78
|
+
switch(width){
|
|
79
|
+
case "X": this.type = "byte"; break;
|
|
80
|
+
case "W": this.type = "word"; break;
|
|
81
|
+
case "D": this.type = "dword"; break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get value() {
|
|
88
|
+
switch (this.type) {
|
|
89
|
+
case 'bit': return readBit(this.address);
|
|
90
|
+
case 'byte': return readByte(this.address);
|
|
91
|
+
case 'word': return readWord(this.address);
|
|
92
|
+
case 'dword': return readDWord(this.address);
|
|
93
|
+
default: throw new Error('Unsupported RefVar type: ' + this.type);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set value(val) {
|
|
98
|
+
switch (this.type) {
|
|
99
|
+
case 'bit': return writeBit(this.address, val);
|
|
100
|
+
case 'byte': return writeByte(this.address, val);
|
|
101
|
+
case 'word': return writeWord(this.address, val);
|
|
102
|
+
case 'dword': return writeDWord(this.address, val);
|
|
103
|
+
default: throw new Error('Unsupported RefVar type: ' + this.type);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getBit(bit) {
|
|
108
|
+
const temp = this.value;
|
|
109
|
+
const buffer = Buffer.allocUnsafe(4);
|
|
110
|
+
if (this.type === 'byte') buffer.writeUInt8(temp);
|
|
111
|
+
else if (this.type === 'word') buffer.writeUInt16LE(temp);
|
|
112
|
+
else if (this.type === 'dword') buffer.writeUInt32LE(temp);
|
|
113
|
+
else throw new Error("Cannot use getBit on bit type");
|
|
114
|
+
|
|
115
|
+
return (buffer[Math.floor(bit / 8)] & (1 << (bit % 8))) !== 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setBit(bit, value) {
|
|
119
|
+
const temp = this.value;
|
|
120
|
+
const buffer = Buffer.allocUnsafe(4);
|
|
121
|
+
if (this.type === 'byte') buffer.writeUInt8(temp);
|
|
122
|
+
else if (this.type === 'word') buffer.writeUInt16LE(temp);
|
|
123
|
+
else if (this.type === 'dword') buffer.writeUInt32LE(temp);
|
|
124
|
+
else throw new Error("Cannot use setBit on bit type");
|
|
125
|
+
|
|
126
|
+
const byteIndex = Math.floor(bit / 8);
|
|
127
|
+
const bitMask = 1 << (bit % 8);
|
|
128
|
+
if (value) buffer[byteIndex] |= bitMask;
|
|
129
|
+
else buffer[byteIndex] &= ~bitMask;
|
|
130
|
+
|
|
131
|
+
const updated =
|
|
132
|
+
this.type === 'byte' ? buffer.readUInt8(0) :
|
|
133
|
+
this.type === 'word' ? buffer.readUInt16LE(0) :
|
|
134
|
+
buffer.readUInt32LE(0);
|
|
135
|
+
this.value = updated;
|
|
136
|
+
}
|
|
137
|
+
valueOf() {
|
|
138
|
+
return this.value; // so JS treats it like a number or boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[Symbol.toPrimitive](hint) {
|
|
142
|
+
return this.value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function parseAddress(address) {
|
|
148
|
+
const regex = /^%([IQM])([XWD])([0-9]+)(?:\.(\d+))?$/i;
|
|
149
|
+
const match = address.match(regex);
|
|
150
|
+
if (!match) throw new Error("Invalid address: " + address);
|
|
151
|
+
const [, space, type, indexStr, bitStr] = match;
|
|
152
|
+
const width = type.toUpperCase() === "X" ? 8 : type.toUpperCase() === "W" ? 16 : 32;
|
|
153
|
+
const index = parseInt(indexStr, 10);
|
|
154
|
+
const bit = bitStr !== undefined ? parseInt(bitStr, 10) : -1;
|
|
155
|
+
return [space.toUpperCase(), width, index, bit];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getMemoryByte(space, addr) {
|
|
159
|
+
let r = -1, c = 0, b = 0;
|
|
160
|
+
switch (space) {
|
|
161
|
+
case 'Q': r = Math.floor((addr * 8) / 64); c = 1; b = addr % 8; break;
|
|
162
|
+
case 'I': r = Math.floor((addr * 8) / 64); c = 0; b = addr % 8; break;
|
|
163
|
+
case 'M': r = Math.floor((addr * 8) / (64 * 14)); c = Math.floor(addr / 112) + 2; b = addr % 8; break;
|
|
164
|
+
default: throw new Error("Invalid space");
|
|
165
|
+
}
|
|
166
|
+
return MEMORY[r]?.[c];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getMemoryTyped(space, addr, type) {
|
|
170
|
+
const byteOffset = type === 2 ? addr * 2 : type === 4 ? addr * 4 : addr;
|
|
171
|
+
const r = Math.floor(byteOffset / 64);
|
|
172
|
+
const c = space === 'Q' ? 1 : space === 'I' ? 0 : Math.floor(addr / 112) + 2;
|
|
173
|
+
const base = MEMORY[r]?.[c];
|
|
174
|
+
if (!base) throw new Error("Invalid memory");
|
|
175
|
+
return new DataView(base.buffer, base.byteOffset, base.byteLength);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function getBit(buffer, bit) {
|
|
179
|
+
const byte = Math.floor(bit / 8);
|
|
180
|
+
const mask = 1 << (bit % 8);
|
|
181
|
+
let compVal = 0;
|
|
182
|
+
if(buffer instanceof Uint8Array){
|
|
183
|
+
compVal = buffer[byte];
|
|
184
|
+
}
|
|
185
|
+
else if(buffer instanceof RefVar){
|
|
186
|
+
compVal = buffer.value;
|
|
187
|
+
}
|
|
188
|
+
else{
|
|
189
|
+
compVal = buffer;
|
|
190
|
+
}
|
|
191
|
+
return (compVal & mask) !== 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function setBit(buffer, bit, value) {
|
|
195
|
+
const byte = Math.floor(bit / 8);
|
|
196
|
+
const mask = 1 << (bit % 8);
|
|
197
|
+
if(buffer instanceof Uint8Array){
|
|
198
|
+
if (value) buffer[byte] |= mask;
|
|
199
|
+
else buffer[byte] &= ~mask;
|
|
200
|
+
}
|
|
201
|
+
else if(buffer instanceof RefVar){
|
|
202
|
+
buffer.setBit(bit, value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function readByte(address) {
|
|
207
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
208
|
+
if (width !== 8 || bit > -1) throw new Error("Invalid byte address: " + address);
|
|
209
|
+
return getMemoryByte(space, index)[0];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function writeByte(address, value) {
|
|
213
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
214
|
+
if (width !== 8 || bit > -1) throw new Error("Invalid byte address: " + address);
|
|
215
|
+
getMemoryByte(space, index)[0] = value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function readWord(address) {
|
|
219
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
220
|
+
if (width !== 16 || bit > -1) throw new Error("Invalid word address: " + address);
|
|
221
|
+
return getMemoryTyped(space, index, 2).getUint16(0, true);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function writeWord(address, value) {
|
|
225
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
226
|
+
if (width !== 16 || bit > -1) throw new Error("Invalid word address: " + address);
|
|
227
|
+
getMemoryTyped(space, index, 2).setUint16(0, value, true);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function readDWord(address) {
|
|
231
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
232
|
+
if (width !== 32 || bit > -1) throw new Error("Invalid dword address: " + address);
|
|
233
|
+
return getMemoryTyped(space, index, 4).getUint32(0, true);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function writeDWord(address, value) {
|
|
237
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
238
|
+
if (width !== 32 || bit > -1) throw new Error("Invalid dword address: " + address);
|
|
239
|
+
getMemoryTyped(space, index, 4).setUint32(0, value, true);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function readBit(address) {
|
|
243
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
244
|
+
if (bit === -1) throw new Error("Missing bit index in address: " + address);
|
|
245
|
+
const buffer = getMemoryByte(space, index);
|
|
246
|
+
return getBit(buffer, bit);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function writeBit(address, value) {
|
|
250
|
+
const [space, width, index, bit] = parseAddress(address);
|
|
251
|
+
if (bit === -1) throw new Error("Missing bit index in address: " + address);
|
|
252
|
+
const buffer = getMemoryByte(space, index);
|
|
253
|
+
setBit(buffer, bit, value);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function readAddress(address){
|
|
257
|
+
if(address.includes(".")){
|
|
258
|
+
return readBit(address);
|
|
259
|
+
}
|
|
260
|
+
else{
|
|
261
|
+
switch(address[2]){
|
|
262
|
+
case "X":
|
|
263
|
+
return readByte(address);
|
|
264
|
+
|
|
265
|
+
case "W":
|
|
266
|
+
return readWord(address);
|
|
267
|
+
|
|
268
|
+
case "D":
|
|
269
|
+
return readDWord(address);
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function writeAddress(address, value){
|
|
276
|
+
if(address.includes(".")){
|
|
277
|
+
writeBit(address, value);
|
|
278
|
+
}
|
|
279
|
+
else{
|
|
280
|
+
switch(address[2]){
|
|
281
|
+
case "X":
|
|
282
|
+
writeByte(address, value);
|
|
283
|
+
break;
|
|
284
|
+
case "W":
|
|
285
|
+
writeWord(address, value);
|
|
286
|
+
break;
|
|
287
|
+
case "D":
|
|
288
|
+
writeDWord(address, value);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Function Block base class pattern
|
|
295
|
+
export class FunctionBlock {
|
|
296
|
+
call() {}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Utility to create simple boolean gates
|
|
300
|
+
function createBoolGate(name, expr) {
|
|
301
|
+
return class extends FunctionBlock {
|
|
302
|
+
constructor() {
|
|
303
|
+
super();
|
|
304
|
+
this.IN1 = false;
|
|
305
|
+
this.IN2 = false;
|
|
306
|
+
this.OUT = false;
|
|
307
|
+
}
|
|
308
|
+
call() {
|
|
309
|
+
this.OUT = expr(this.IN1, this.IN2);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Logic Gates
|
|
315
|
+
export const AND = createBoolGate("AND", (a, b) => a && b);
|
|
316
|
+
export const OR = createBoolGate("OR", (a, b) => a || b);
|
|
317
|
+
export const XOR = createBoolGate("XOR", (a, b) => a !== b);
|
|
318
|
+
export const NOR = createBoolGate("NOR", (a, b) => !(a || b));
|
|
319
|
+
export const NAND = createBoolGate("NAND", (a, b) => !(a && b));
|
|
320
|
+
|
|
321
|
+
export class NOT extends FunctionBlock {
|
|
322
|
+
constructor() {
|
|
323
|
+
super();
|
|
324
|
+
this.IN = false;
|
|
325
|
+
this.OUT = false;
|
|
326
|
+
}
|
|
327
|
+
call() {
|
|
328
|
+
this.OUT = !this.IN;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export class ASSIGNMENT extends FunctionBlock {
|
|
333
|
+
constructor() {
|
|
334
|
+
super();
|
|
335
|
+
this.IN = false;
|
|
336
|
+
this.OUT = false;
|
|
337
|
+
}
|
|
338
|
+
call() {
|
|
339
|
+
this.OUT = this.IN;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export class TON extends FunctionBlock {
|
|
344
|
+
constructor() {
|
|
345
|
+
super();
|
|
346
|
+
this.IN = false;
|
|
347
|
+
this.PT = 0;
|
|
348
|
+
this.Q = false;
|
|
349
|
+
this.ET = 0;
|
|
350
|
+
this._startTime = 0;
|
|
351
|
+
}
|
|
352
|
+
call() {
|
|
353
|
+
if (this.IN) {
|
|
354
|
+
if (this._startTime === 0) this._startTime = elapsed();
|
|
355
|
+
this.ET = elapsed() - this._startTime;
|
|
356
|
+
this.Q = this.ET >= this.PT;
|
|
357
|
+
} else {
|
|
358
|
+
this._startTime = 0;
|
|
359
|
+
this.ET = 0;
|
|
360
|
+
this.Q = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export class TOF extends FunctionBlock {
|
|
366
|
+
constructor() {
|
|
367
|
+
super();
|
|
368
|
+
this.IN = false;
|
|
369
|
+
this.PT = 0;
|
|
370
|
+
this.Q = false;
|
|
371
|
+
this.ET = 0;
|
|
372
|
+
this._startTime = 0;
|
|
373
|
+
}
|
|
374
|
+
call() {
|
|
375
|
+
if (this.IN) {
|
|
376
|
+
this.Q = true;
|
|
377
|
+
this._startTime = 0;
|
|
378
|
+
this.ET = 0;
|
|
379
|
+
} else if (this.Q) {
|
|
380
|
+
if (this._startTime === 0) this._startTime = elapsed();
|
|
381
|
+
this.ET = elapsed() - this._startTime;
|
|
382
|
+
if (this.ET >= this.PT) this.Q = false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export class TP extends FunctionBlock {
|
|
388
|
+
constructor() {
|
|
389
|
+
super();
|
|
390
|
+
this.IN = false;
|
|
391
|
+
this.PT = 0;
|
|
392
|
+
this.Q = false;
|
|
393
|
+
this.ET = 0;
|
|
394
|
+
this._startTime = 0;
|
|
395
|
+
this._lastIN = false;
|
|
396
|
+
}
|
|
397
|
+
call() {
|
|
398
|
+
this.Q = false;
|
|
399
|
+
if (!this._lastIN && this.IN) {
|
|
400
|
+
this._lastIN = this.IN;
|
|
401
|
+
this.ET = 0;
|
|
402
|
+
this._startTime = 0;
|
|
403
|
+
}
|
|
404
|
+
if (this.IN) {
|
|
405
|
+
this.Q = true;
|
|
406
|
+
} else if (this._lastIN && !this.IN) {
|
|
407
|
+
if (this._startTime === 0) this._startTime = elapsed();
|
|
408
|
+
this.ET = elapsed() - this._startTime;
|
|
409
|
+
this.Q = this.PT >= this.ET;
|
|
410
|
+
if (!this.Q) this._lastIN = false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export class R_TRIG extends FunctionBlock {
|
|
416
|
+
constructor() {
|
|
417
|
+
super();
|
|
418
|
+
this.CLK = false;
|
|
419
|
+
this.OUT = false;
|
|
420
|
+
this._lastCLK = false;
|
|
421
|
+
}
|
|
422
|
+
call() {
|
|
423
|
+
this.OUT = this.CLK && !this._lastCLK;
|
|
424
|
+
this._lastCLK = this.CLK;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export class F_TRIG extends FunctionBlock {
|
|
429
|
+
constructor() {
|
|
430
|
+
super();
|
|
431
|
+
this.CLK = false;
|
|
432
|
+
this.OUT = false;
|
|
433
|
+
this._lastCLK = false;
|
|
434
|
+
}
|
|
435
|
+
call() {
|
|
436
|
+
this.OUT = !this.CLK && this._lastCLK;
|
|
437
|
+
this._lastCLK = this.CLK;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export class CTU extends FunctionBlock {
|
|
442
|
+
constructor() {
|
|
443
|
+
super();
|
|
444
|
+
this.CU = false;
|
|
445
|
+
this.R = false;
|
|
446
|
+
this.PV = 0;
|
|
447
|
+
this.CV = 0;
|
|
448
|
+
this.Q = false;
|
|
449
|
+
this._lastCU = false;
|
|
450
|
+
}
|
|
451
|
+
call() {
|
|
452
|
+
if (this.R) {
|
|
453
|
+
this.CV = 0;
|
|
454
|
+
} else if (this.CU && !this._lastCU) {
|
|
455
|
+
this.CV++;
|
|
456
|
+
}
|
|
457
|
+
this.Q = this.CV >= this.PV;
|
|
458
|
+
this._lastCU = this.CU;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export class CTD extends FunctionBlock {
|
|
463
|
+
constructor() {
|
|
464
|
+
super();
|
|
465
|
+
this.CD = false;
|
|
466
|
+
this.LD = false;
|
|
467
|
+
this.PV = 0;
|
|
468
|
+
this.CV = 0;
|
|
469
|
+
this.Q = false;
|
|
470
|
+
this._lastCD = false;
|
|
471
|
+
}
|
|
472
|
+
call() {
|
|
473
|
+
if (this.LD) {
|
|
474
|
+
this.CV = this.PV;
|
|
475
|
+
} else if (this.CD && !this._lastCD && this.CV > 0) {
|
|
476
|
+
this.CV--;
|
|
477
|
+
}
|
|
478
|
+
this.Q = this.CV === 0;
|
|
479
|
+
this._lastCD = this.CD;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export class CTUD extends FunctionBlock {
|
|
484
|
+
constructor() {
|
|
485
|
+
super();
|
|
486
|
+
this.CU = false;
|
|
487
|
+
this.CD = false;
|
|
488
|
+
this.R = false;
|
|
489
|
+
this.LD = false;
|
|
490
|
+
this.PV = 0;
|
|
491
|
+
this.CV = 0;
|
|
492
|
+
this.QU = false;
|
|
493
|
+
this.QD = false;
|
|
494
|
+
this._lastCU = false;
|
|
495
|
+
this._lastCD = false;
|
|
496
|
+
}
|
|
497
|
+
call() {
|
|
498
|
+
if (this.R) {
|
|
499
|
+
this.CV = 0;
|
|
500
|
+
} else if (this.LD) {
|
|
501
|
+
this.CV = this.PV;
|
|
502
|
+
} else {
|
|
503
|
+
if (this.CU && !this._lastCU) this.CV++;
|
|
504
|
+
if (this.CD && !this._lastCD && this.CV > 0) this.CV--;
|
|
505
|
+
}
|
|
506
|
+
this.QU = this.CV >= this.PV;
|
|
507
|
+
this.QD = this.CV === 0;
|
|
508
|
+
this._lastCU = this.CU;
|
|
509
|
+
this._lastCD = this.CD;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function createCompareBlock(expr) {
|
|
514
|
+
return class extends FunctionBlock {
|
|
515
|
+
constructor() {
|
|
516
|
+
super();
|
|
517
|
+
this.IN1 = 0;
|
|
518
|
+
this.IN2 = 0;
|
|
519
|
+
this.OUT = false;
|
|
520
|
+
}
|
|
521
|
+
call() {
|
|
522
|
+
this.OUT = expr(this.IN1, this.IN2);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export const EQ = createCompareBlock((a, b) => a === b);
|
|
528
|
+
export const NE = createCompareBlock((a, b) => a !== b);
|
|
529
|
+
export const LT = createCompareBlock((a, b) => a < b);
|
|
530
|
+
export const GT = createCompareBlock((a, b) => a > b);
|
|
531
|
+
export const GE = createCompareBlock((a, b) => a >= b);
|
|
532
|
+
export const LE = createCompareBlock((a, b) => a <= b);
|
|
533
|
+
|
|
534
|
+
export class MOVE extends FunctionBlock {
|
|
535
|
+
constructor() {
|
|
536
|
+
super();
|
|
537
|
+
this.IN = 0;
|
|
538
|
+
this.OUT = 0;
|
|
539
|
+
}
|
|
540
|
+
call() {
|
|
541
|
+
this.OUT = this.IN;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export class SEL extends FunctionBlock {
|
|
546
|
+
constructor() {
|
|
547
|
+
super();
|
|
548
|
+
this.G = false;
|
|
549
|
+
this.IN0 = 0;
|
|
550
|
+
this.IN1 = 0;
|
|
551
|
+
this.OUT = 0;
|
|
552
|
+
}
|
|
553
|
+
call() {
|
|
554
|
+
this.OUT = this.G ? this.IN1 : this.IN0;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export class MUX extends FunctionBlock {
|
|
559
|
+
constructor() {
|
|
560
|
+
super();
|
|
561
|
+
this.K = false;
|
|
562
|
+
this.IN0 = 0;
|
|
563
|
+
this.IN1 = 0;
|
|
564
|
+
this.OUT = 0;
|
|
565
|
+
}
|
|
566
|
+
call() {
|
|
567
|
+
this.OUT = this.K ? this.IN1 : this.IN0;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export class MIN extends FunctionBlock {
|
|
572
|
+
constructor() {
|
|
573
|
+
super();
|
|
574
|
+
this.IN1 = 0;
|
|
575
|
+
this.IN2 = 0;
|
|
576
|
+
this.OUT = 0;
|
|
577
|
+
}
|
|
578
|
+
call() {
|
|
579
|
+
this.OUT = Math.min(this.IN1, this.IN2);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export class MAX extends FunctionBlock {
|
|
584
|
+
constructor() {
|
|
585
|
+
super();
|
|
586
|
+
this.IN1 = 0;
|
|
587
|
+
this.IN2 = 0;
|
|
588
|
+
this.OUT = 0;
|
|
589
|
+
}
|
|
590
|
+
call() {
|
|
591
|
+
this.OUT = Math.max(this.IN1, this.IN2);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export class LIMIT extends FunctionBlock {
|
|
596
|
+
constructor() {
|
|
597
|
+
super();
|
|
598
|
+
this.MN = 0;
|
|
599
|
+
this.IN = 0;
|
|
600
|
+
this.MX = 0;
|
|
601
|
+
this.OUT = 0;
|
|
602
|
+
}
|
|
603
|
+
call() {
|
|
604
|
+
if (this.IN < this.MN) this.OUT = this.MN;
|
|
605
|
+
else if (this.IN > this.MX) this.OUT = this.MX;
|
|
606
|
+
else this.OUT = this.IN;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const Clients = [];
|
|
611
|
+
|
|
612
|
+
function findClient(map) {
|
|
613
|
+
for (const client of Clients) {
|
|
614
|
+
if (client.hasMapping(map.localAddress)) return client;
|
|
615
|
+
if (client.moduleID === map.moduleID) {
|
|
616
|
+
client.addMapping(map);
|
|
617
|
+
return client;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function createClient(map) {
|
|
624
|
+
if (map.protocol === "MODBUS-TCP") {
|
|
625
|
+
const modbusClient = new ModbusClient();
|
|
626
|
+
modbusClient.addMapping(map);
|
|
627
|
+
modbusClient.connect();
|
|
628
|
+
return modbusClient;
|
|
629
|
+
}
|
|
630
|
+
else if (map.protocol === "OPCUA") {
|
|
631
|
+
const opcClient = new OPCClient();
|
|
632
|
+
opcClient.addMapping(map);
|
|
633
|
+
opcClient.connect();
|
|
634
|
+
return opcClient;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function mapIO(mapStr) {
|
|
641
|
+
try {
|
|
642
|
+
const newMap = new IOMap(mapStr);
|
|
643
|
+
const existing = findClient(newMap);
|
|
644
|
+
if (!existing) {
|
|
645
|
+
const client = createClient(newMap);
|
|
646
|
+
if (client) Clients.push(client);
|
|
647
|
+
}
|
|
648
|
+
} catch (e) {
|
|
649
|
+
console.error("MapIO Exception:", e.message);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function superviseIO() {
|
|
654
|
+
try {
|
|
655
|
+
for (const client of Clients) {
|
|
656
|
+
client.poll();
|
|
657
|
+
}
|
|
658
|
+
} catch (e) {
|
|
659
|
+
console.error("SuperviseIO Exception:", e.message);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|