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.
Files changed (37) hide show
  1. package/README.md +134 -0
  2. package/package.json +59 -0
  3. package/src/compilers/CPPCompiler.js +272 -0
  4. package/src/compilers/Compiler.js +108 -0
  5. package/src/compilers/JSCompiler.js +293 -0
  6. package/src/compilers/iec-parser/parser.js +4254 -0
  7. package/src/compilers/st-parser/expressionConverter.js +155 -0
  8. package/src/compilers/st-parser/gcctranspiler.js +237 -0
  9. package/src/compilers/st-parser/jstranspiler.js +254 -0
  10. package/src/compilers/st-parser/parser.js +367 -0
  11. package/src/compilers/st-parser/tokenizer.js +78 -0
  12. package/src/compilers/support/generic/json.hpp +25526 -0
  13. package/src/compilers/support/generic/modbus.cpp +378 -0
  14. package/src/compilers/support/generic/modbus.h +124 -0
  15. package/src/compilers/support/generic/nodalis.cpp +421 -0
  16. package/src/compilers/support/generic/nodalis.h +798 -0
  17. package/src/compilers/support/generic/opcua.cpp +267 -0
  18. package/src/compilers/support/generic/opcua.h +50 -0
  19. package/src/compilers/support/generic/open62541.c +151897 -0
  20. package/src/compilers/support/generic/open62541.h +50357 -0
  21. package/src/compilers/support/jint/nodalis/Nodalis.sln +28 -0
  22. package/src/compilers/support/jint/nodalis/NodalisEngine/ModbusClient.cs +200 -0
  23. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +817 -0
  24. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +16 -0
  25. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +172 -0
  26. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +275 -0
  27. package/src/compilers/support/jint/nodalis/NodalisPLC/NodalisPLC.csproj +19 -0
  28. package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +197 -0
  29. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.bat +5 -0
  30. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.sh +5 -0
  31. package/src/compilers/support/jint/nodalis/build.bat +25 -0
  32. package/src/compilers/support/jint/nodalis/build.sh +31 -0
  33. package/src/compilers/support/nodejs/IOClient.js +110 -0
  34. package/src/compilers/support/nodejs/modbus.js +115 -0
  35. package/src/compilers/support/nodejs/nodalis.js +662 -0
  36. package/src/compilers/support/nodejs/opcua.js +194 -0
  37. package/src/nodalis.js +174 -0
@@ -0,0 +1,421 @@
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
+ * @description Nodalis PLC Code
17
+ * @author Nathan Skipper, MTI
18
+ * @version 1.0.2
19
+ * @copyright Apache 2.0
20
+ */
21
+ #include "nodalis.h"
22
+ #include <iostream>
23
+ #include <map>
24
+ #include "modbus.h"
25
+ #include "opcua.h"
26
+
27
+ uint64_t PROGRAM_COUNT = 0;
28
+ uint64_t MEMORY[64][16] = { 0 };
29
+
30
+ std::chrono::steady_clock::time_point PROGRAM_START = std::chrono::steady_clock::now();
31
+ uint64_t elapsed() {
32
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
33
+ std::chrono::steady_clock::now() - PROGRAM_START
34
+ ).count();
35
+ }
36
+
37
+
38
+ uint32_t readDWord(std::string address){
39
+ std::vector<int> parts = parseAddress(address);
40
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
41
+
42
+ if(width != 32){
43
+ throw std::invalid_argument("Invalid address type: " + address);
44
+ }
45
+ if(space == -1){
46
+ throw std::invalid_argument("Invalid address space: " + address);
47
+ }
48
+ if(index == -1){
49
+ throw std::invalid_argument("Invalid address index: " + address);
50
+ }
51
+ if(bit > -1){
52
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
53
+ }
54
+ return *getMemoryDWord(space, index);
55
+ }
56
+ uint16_t readWord(std::string address){
57
+ std::vector<int> parts = parseAddress(address);
58
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
59
+
60
+ if(width != 16){
61
+ throw std::invalid_argument("Invalid address type: " + address);
62
+ }
63
+ if(space == -1){
64
+ throw std::invalid_argument("Invalid address space: " + address);
65
+ }
66
+ if(index == -1){
67
+ throw std::invalid_argument("Invalid address index: " + address);
68
+ }
69
+ if(bit > -1){
70
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
71
+ }
72
+ return *getMemoryWord(space, index);
73
+ }
74
+ uint8_t readByte(std::string address){
75
+ std::vector<int> parts = parseAddress(address);
76
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
77
+
78
+ if(width != 8){
79
+ throw std::invalid_argument("Invalid address type: " + address);
80
+ }
81
+ if(space == -1){
82
+ throw std::invalid_argument("Invalid address space: " + address);
83
+ }
84
+ if(index == -1){
85
+ throw std::invalid_argument("Invalid address index: " + address);
86
+ }
87
+ if(bit > -1){
88
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
89
+ }
90
+ return *getMemoryByte(space, index);
91
+ }
92
+ bool readBit(std::string address){
93
+ std::vector<int> parts = parseAddress(address);
94
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
95
+
96
+ uint32_t val = 0;
97
+ if(space == -1){
98
+ throw std::invalid_argument("Invalid address space: " + address);
99
+ }
100
+ if(index == -1){
101
+ throw std::invalid_argument("Invalid address index: " + address);
102
+ }
103
+ if(bit == -1)
104
+ {
105
+ throw std::invalid_argument("Invalid address bit: " + address);
106
+ }
107
+ if(width == -1){
108
+ throw std::invalid_argument("Invalid address size: " + address);
109
+ }
110
+ bool ret = false;
111
+ switch(width){
112
+ case 8:
113
+ ret = getBit(getMemoryByte(space, index), bit);
114
+ break;
115
+ case 16:
116
+ ret = getBit(getMemoryWord(space, index), bit);
117
+ break;
118
+ case 32:
119
+ ret = getBit(getMemoryDWord(space, index), bit);
120
+ break;
121
+ }
122
+ return ret;
123
+ }
124
+ void writeDWord(std::string address, uint32_t value){
125
+ std::vector<int> parts = parseAddress(address);
126
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
127
+
128
+ if(width != 32){
129
+ throw std::invalid_argument("Invalid address type: " + address);
130
+ }
131
+ if(space == -1){
132
+ throw std::invalid_argument("Invalid address space: " + address);
133
+ }
134
+ if(index == -1){
135
+ throw std::invalid_argument("Invalid address index: " + address);
136
+ }
137
+ if(bit > -1){
138
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
139
+ }
140
+ uint32_t* temp = getMemoryDWord(space, index);
141
+ *temp = value;
142
+ }
143
+ void writeWord(std::string address, uint16_t value){
144
+ std::vector<int> parts = parseAddress(address);
145
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
146
+
147
+ if(width != 16){
148
+ throw std::invalid_argument("Invalid address type: " + address);
149
+ }
150
+ if(space == -1){
151
+ throw std::invalid_argument("Invalid address space: " + address);
152
+ }
153
+ if(index == -1){
154
+ throw std::invalid_argument("Invalid address index: " + address);
155
+ }
156
+ if(bit > -1){
157
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
158
+ }
159
+ uint16_t* temp = getMemoryWord(space, index);
160
+ *temp = value;
161
+ }
162
+ void writeByte(std::string address, uint8_t value){
163
+ std::vector<int> parts = parseAddress(address);
164
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
165
+
166
+ if(width != 8){
167
+ throw std::invalid_argument("Invalid address type: " + address);
168
+ }
169
+ if(space == -1){
170
+ throw std::invalid_argument("Invalid address space: " + address);
171
+ }
172
+ if(index == -1){
173
+ throw std::invalid_argument("Invalid address index: " + address);
174
+ }
175
+ if(bit > -1){
176
+ throw std::invalid_argument("Invalid address format. Reference specifies a bit: " + address);
177
+ }
178
+ uint8_t* temp = getMemoryByte(space, index);
179
+ *temp = value;
180
+ }
181
+ void writeBit(std::string address, bool value){
182
+ std::vector<int> parts = parseAddress(address);
183
+ int space = parts[0], width = parts[1], index = parts[2], bit = parts[3];
184
+
185
+ if(space == -1){
186
+ throw std::invalid_argument("Invalid address space: " + address);
187
+ }
188
+ if(index == -1){
189
+ throw std::invalid_argument("Invalid address index: " + address);
190
+ }
191
+
192
+ if(bit == -1)
193
+ {
194
+ throw std::invalid_argument("Invalid address bit: " + address);
195
+ }
196
+ if(width == -1){
197
+ throw std::invalid_argument("Invalid address size: " + address);
198
+ }
199
+ switch(width){
200
+ case 8:
201
+ setBit(getMemoryByte(space, index), bit, value);
202
+ break;
203
+ case 16:
204
+ setBit(getMemoryWord(space, index), bit, value);
205
+ break;
206
+ case 32:
207
+ setBit(getMemoryDWord(space, index), bit, value);
208
+ break;
209
+ }
210
+ }
211
+ bool getBit(void* var, int bit) {
212
+ // Advance to the byte containing the bit
213
+ uint8_t* bytePtr = static_cast<uint8_t*>(var) + (bit / 8);
214
+
215
+ // Mask out the bit within the byte
216
+ uint8_t mask = 1 << (bit % 8);
217
+
218
+ // Return true if the bit is set
219
+ return (*bytePtr & mask) != 0;
220
+ }
221
+
222
+ void setBit(void* var, int bit, bool value) {
223
+ uint8_t* bytePtr = static_cast<uint8_t*>(var) + (bit / 8);
224
+ uint8_t mask = 1 << (bit % 8);
225
+
226
+ if (value) {
227
+ *bytePtr |= mask; // Set the bit
228
+ } else {
229
+ *bytePtr &= ~mask; // Clear the bit
230
+ }
231
+ }
232
+
233
+ std::vector<std::unique_ptr<IOClient>> Clients;
234
+
235
+ IOMap::IOMap(std::string mapJson){
236
+ json j = json::parse(mapJson);
237
+ moduleID = j["ModuleID"];
238
+ modulePort = j["ModulePort"];
239
+ localAddress = j["InternalAddress"];
240
+ remoteAddress = j["RemoteAddress"];
241
+ width = std::atoi(j["RemoteSize"].get<std::string>().c_str());
242
+ interval = std::atoi(j["PollTime"].get<std::string>().c_str());
243
+ protocol = j["Protocol"];
244
+ additionalProperties = j["AdditionalProperties"];
245
+ if(localAddress.find("%Q") != std::string::npos){
246
+ direction = IOType::Output;
247
+ }
248
+ else{
249
+ direction = IOType::Input;
250
+ }
251
+ lastPoll = elapsed();
252
+ }
253
+
254
+ IOMap::IOMap(){
255
+
256
+ }
257
+
258
+ IOClient::IOClient(const std::string& protocol) : protocol(protocol) {
259
+ connected = false;
260
+ }
261
+
262
+ void IOClient::addMapping(const IOMap& map) {
263
+ if(!hasMapping(map.localAddress)){
264
+ if(mappings.size() == 0){
265
+ moduleID = map.moduleID;
266
+ }
267
+ std::cout << "Adding map for " << map.moduleID.c_str() << ":" << map.modulePort.c_str() << "->" << map.localAddress.c_str() << "\n";
268
+ mappings.push_back(map);
269
+ }
270
+ }
271
+
272
+ bool IOClient::hasMapping(std::string localAddress){
273
+ for (const auto& map : mappings) {
274
+ if(map.localAddress == localAddress){
275
+ return true;
276
+ }
277
+ }
278
+ return false;
279
+ }
280
+
281
+ const std::string& IOClient::getProtocol() const {
282
+ return protocol;
283
+ }
284
+
285
+ const std::string& IOClient::getModuleID() const {
286
+ return moduleID;
287
+ }
288
+
289
+ void IOClient::poll() {
290
+ if(connected){
291
+ for (auto& map : mappings) {
292
+ try {
293
+ if(elapsed() - map.lastPoll > map.interval){
294
+ map.lastPoll = elapsed();
295
+ if (map.direction == IOType::Output) {
296
+ switch (map.width) {
297
+ case 1: {
298
+ int bit = ::readBit(map.localAddress);
299
+ writeBit(map.remoteAddress, bit);
300
+ break;
301
+ }
302
+ case 8: {
303
+ uint8_t val = ::readByte(map.localAddress);
304
+ writeByte(map.remoteAddress, val);
305
+ break;
306
+ }
307
+ case 16: {
308
+ uint16_t val = ::readWord(map.localAddress);
309
+ writeWord(map.remoteAddress, val);
310
+ break;
311
+ }
312
+ case 32: {
313
+ uint32_t val = ::readDWord(map.localAddress);
314
+ writeDWord(map.remoteAddress, val);
315
+ break;
316
+ }
317
+ }
318
+ }
319
+ else if (map.direction == IOType::Input) {
320
+
321
+ switch (map.width) {
322
+ case 1: {
323
+ int bit = 0;
324
+ if (readBit(map.remoteAddress, bit)) {
325
+ ::writeBit(map.localAddress, bit > 0);
326
+ }
327
+ break;
328
+ }
329
+ case 8: {
330
+ uint8_t val = 0;
331
+ if (readByte(map.remoteAddress, val)) {
332
+ ::writeByte(map.localAddress, val);
333
+ }
334
+ break;
335
+ }
336
+ case 16: {
337
+ uint16_t val = 0;
338
+ if (readWord(map.remoteAddress, val)) {
339
+ ::writeWord(map.localAddress, val);
340
+ }
341
+ break;
342
+ }
343
+ case 32: {
344
+ uint32_t val = 0;
345
+ if (readDWord(map.remoteAddress, val)) {
346
+ ::writeDWord(map.localAddress, val);
347
+ }
348
+ break;
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ catch (const std::exception& e) {
355
+ // handle error or log it
356
+ }
357
+ }
358
+
359
+ }
360
+ else if(elapsed() - lastAttempt >= 15000){
361
+ lastAttempt = elapsed();
362
+ connect();
363
+ }
364
+ }
365
+
366
+
367
+
368
+ IOClient* findClient(IOMap map){
369
+ for(int x = 0; x < Clients.size(); x++){
370
+ if(Clients[x]->hasMapping(map.localAddress)){
371
+ return Clients[x].get();
372
+ }
373
+ else if(Clients[x]->getModuleID() == map.moduleID){
374
+ Clients[x]->addMapping(map);
375
+ return Clients[x].get();
376
+ }
377
+
378
+ }
379
+ return nullptr;
380
+ }
381
+ std::unique_ptr<IOClient> createClient(IOMap& map){
382
+
383
+ if(map.protocol == "MODBUS-TCP"){
384
+ auto ret = std::make_unique<ModbusClient>();
385
+ ret->addMapping(map);
386
+ return ret;
387
+ }
388
+ else if(map.protocol == "OPCUA"){
389
+ auto ret = std::make_unique<OPCUAClient>();
390
+ ret->addMapping(map);
391
+ return ret;
392
+ }
393
+ return nullptr;
394
+ }
395
+
396
+ void mapIO(std::string map){
397
+ try{
398
+ IOMap newMap(map);
399
+ IOClient* existing = findClient(newMap);
400
+ if(existing == nullptr){
401
+ auto client = createClient(newMap);
402
+ if(client) Clients.push_back(std::move(client));
403
+ }
404
+ }
405
+ catch(const std::exception& e){
406
+ std::cout << "Caught exception: " << e.what() << "\n";
407
+ }
408
+
409
+
410
+ }
411
+
412
+ void superviseIO(){
413
+ try{
414
+ for(int x = 0; x < Clients.size(); x++){
415
+ Clients[x]->poll();
416
+ }
417
+ }
418
+ catch(const std::exception& e){
419
+ std::cout << "Caught exception: " << e.what() << "\n";
420
+ }
421
+ }