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,378 @@
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 Modbus
17
+ * @author Nathan Skipper, MTI
18
+ * @version 1.0.2
19
+ * @copyright Apache 2.0
20
+ */
21
+ #include "modbus.h"
22
+ #include <cstring>
23
+ #include <iostream>
24
+ #ifdef _WIN32
25
+ #include <winsock2.h>
26
+ #include <ws2tcpip.h>
27
+ #pragma comment(lib, "ws2_32.lib")
28
+ #else
29
+ #include <errno.h>
30
+ #endif
31
+
32
+ // ========== Server Implementation ==========
33
+
34
+ ModbusServer::ModbusServer() {}
35
+
36
+ void ModbusServer::setCoil(uint16_t address, bool value) {
37
+ coils[address] = value;
38
+ }
39
+
40
+ bool ModbusServer::getCoil(uint16_t address) {
41
+ return coils[address];
42
+ }
43
+
44
+ void ModbusServer::setDiscreteInput(uint16_t address, bool value) {
45
+ discreteInputs[address] = value;
46
+ }
47
+
48
+ bool ModbusServer::getDiscreteInput(uint16_t address) {
49
+ return discreteInputs[address];
50
+ }
51
+
52
+ void ModbusServer::setRegister(uint16_t address, uint16_t value) {
53
+ holdingRegisters[address] = value;
54
+ }
55
+
56
+ uint16_t ModbusServer::getRegister(uint16_t address) {
57
+ return holdingRegisters[address];
58
+ }
59
+
60
+ ModbusResponse ModbusServer::handleRequest(const ModbusRequest& request) {
61
+ ModbusResponse res;
62
+ res.address = request.address;
63
+ res.function = request.function;
64
+ res.exceptionCode = 0;
65
+
66
+ switch (request.function) {
67
+ case READ_COILS:
68
+ case READ_DISCRETE_INPUTS: {
69
+ for (uint16_t i = 0; i < request.quantity; ++i) {
70
+ bool bit = (request.function == READ_COILS)
71
+ ? getCoil(request.startAddress + i)
72
+ : getDiscreteInput(request.startAddress + i);
73
+ if (i % 8 == 0)
74
+ res.data.push_back(0);
75
+ if (bit)
76
+ res.data[i / 8] |= (1 << (i % 8));
77
+ }
78
+ break;
79
+ }
80
+
81
+ case READ_HOLDING_REGISTERS:
82
+ case READ_INPUT_REGISTERS: {
83
+ for (uint16_t i = 0; i < request.quantity; ++i) {
84
+ uint16_t val = getRegister(request.startAddress + i);
85
+ res.data.push_back(val >> 8);
86
+ res.data.push_back(val & 0xFF);
87
+ }
88
+ break;
89
+ }
90
+
91
+ case WRITE_SINGLE_COIL: {
92
+ if (request.data.size() < 2) { res.exceptionCode = 0x03; break; }
93
+ bool value = (request.data[0] == 0xFF);
94
+ setCoil(request.startAddress, value);
95
+ res.data = request.data;
96
+ break;
97
+ }
98
+
99
+ case WRITE_SINGLE_REGISTER: {
100
+ if (request.data.size() < 2) { res.exceptionCode = 0x03; break; }
101
+ uint16_t value = (request.data[0] << 8) | request.data[1];
102
+ setRegister(request.startAddress, value);
103
+ res.data = request.data;
104
+ break;
105
+ }
106
+
107
+ default:
108
+ res.exceptionCode = 0x01;
109
+ break;
110
+ }
111
+
112
+ return res;
113
+ }
114
+
115
+ // ========== Client Implementation ==========
116
+
117
+ ModbusClient::ModbusClient(const std::string& ip, uint16_t port, uint8_t unitId)
118
+ : IOClient("MODBUS-TCP"), deviceAddress(unitId), sockfd(-1), ip(ip), port(port) {
119
+ #ifdef _WIN32
120
+ WSADATA wsa;
121
+ WSAStartup(MAKEWORD(2,2), &wsa);
122
+ #endif
123
+ if(ip != "") connectTCP(ip, port);
124
+ }
125
+
126
+ ModbusClient::~ModbusClient() {
127
+ disconnect();
128
+ }
129
+
130
+ void ModbusClient::connect(){
131
+ if(connected){
132
+ disconnect();
133
+ }
134
+ if(ip == "" || port == 0){
135
+ if(mappings.size() > 0){
136
+ IOMap& map = mappings[0];
137
+ ip = map.moduleID;
138
+ port = std::atoi(map.modulePort.c_str());
139
+ }
140
+ }
141
+ if(ip != "" && port > 0){
142
+ moduleID = ip;
143
+ connectTCP(ip, port);
144
+ }
145
+ }
146
+
147
+ bool ModbusClient::connectTCP(const std::string& ip, uint16_t port) {
148
+ std::cout << "Modbus-TCP attempting to connect to " << ip.c_str() << ":" << port << "\n";
149
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
150
+ if (sockfd < 0) return false;
151
+
152
+ sockaddr_in serverAddr;
153
+ memset(&serverAddr, 0, sizeof(serverAddr));
154
+ serverAddr.sin_family = AF_INET;
155
+ serverAddr.sin_port = htons(port);
156
+ inet_pton(AF_INET, ip.c_str(), &serverAddr.sin_addr);
157
+
158
+ int err = ::connect(sockfd, (sockaddr*)&serverAddr, sizeof(serverAddr));
159
+ if (err < 0) {
160
+ disconnect();
161
+ #ifdef _WIN32
162
+ int wsaErr = WSAGetLastError();
163
+ std::cerr << "Connect failed: WSA Error " << wsaErr << "\n";
164
+ #else
165
+ std::cerr << "Connect failed: " << strerror(errno)
166
+ << " (errno = " << errno << ")\n";
167
+ #endif
168
+ return false;
169
+ }
170
+ std::cout << "Modbus-TCP connected to " << ip.c_str() << ":" << port << "\n";
171
+ connected = true;
172
+ return true;
173
+ }
174
+
175
+ void ModbusClient::disconnect() {
176
+ if (connected) {
177
+ #ifdef _WIN32
178
+ closesocket(sockfd);
179
+ #else
180
+ close(sockfd);
181
+ #endif
182
+ connected = false;
183
+ }
184
+ }
185
+
186
+ ModbusRequest ModbusClient::createReadRequest(uint8_t function, uint16_t startAddress, uint16_t quantity) {
187
+ return { deviceAddress, function, startAddress, quantity, {} };
188
+ }
189
+
190
+ ModbusRequest ModbusClient::createWriteSingleCoil(uint16_t address, bool value) {
191
+ std::vector<uint8_t> data = value ? std::vector<uint8_t>{0xFF, 0x00} : std::vector<uint8_t>{0x00, 0x00};
192
+ return { deviceAddress, WRITE_SINGLE_COIL, address, 0, data };
193
+ }
194
+
195
+ ModbusRequest ModbusClient::createWriteSingleRegister(uint16_t address, uint16_t value) {
196
+ std::vector<uint8_t> data = { static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value & 0xFF) };
197
+ return { deviceAddress, WRITE_SINGLE_REGISTER, address, 1, data };
198
+ }
199
+
200
+ bool ModbusClient::sendRequest(const ModbusRequest& req, ModbusResponse& resp) {
201
+ std::vector<uint8_t> pdu;
202
+
203
+ // Handle function-specific encoding
204
+ if (req.function == WRITE_SINGLE_COIL) {
205
+ // Function 0x05: Write Single Coil
206
+ pdu = {
207
+ req.function,
208
+ static_cast<uint8_t>(req.startAddress >> 8),
209
+ static_cast<uint8_t>(req.startAddress & 0xFF)
210
+ };
211
+ if (req.data.size() == 2) {
212
+ pdu.push_back(req.data[0]); // Hi byte (0xFF or 0x00)
213
+ pdu.push_back(req.data[1]); // Lo byte (always 0x00)
214
+ } else {
215
+ std::cerr << "Invalid data size for Write Single Coil (expected 2 bytes).\n";
216
+ return false;
217
+ }
218
+ } else {
219
+ // Default encoding for most function codes
220
+ pdu = {
221
+ req.function,
222
+ static_cast<uint8_t>(req.startAddress >> 8),
223
+ static_cast<uint8_t>(req.startAddress & 0xFF),
224
+ static_cast<uint8_t>(req.quantity >> 8),
225
+ static_cast<uint8_t>(req.quantity & 0xFF)
226
+ };
227
+ pdu.insert(pdu.end(), req.data.begin(), req.data.end());
228
+ }
229
+
230
+ std::vector<uint8_t> response;
231
+ if (!sendRaw(pdu, response)) return false;
232
+
233
+ if (response.size() < 2) return false;
234
+
235
+ resp.address = req.address;
236
+ resp.function = response[0];
237
+ resp.data.assign(response.begin() + 1, response.end());
238
+ resp.exceptionCode = (resp.function & 0x80) ? resp.data[0] : 0;
239
+
240
+ if (resp.exceptionCode > 0) {
241
+ std::cerr << "MODBUS exception code: " << static_cast<int>(resp.exceptionCode) << "\n";
242
+ return false;
243
+ }
244
+
245
+ return true;
246
+ }
247
+
248
+ bool ModbusClient::sendRaw(const std::vector<uint8_t>& pdu, std::vector<uint8_t>& response) {
249
+ if (!connected) return false;
250
+
251
+ // MBAP header (7 bytes): Transaction ID, Protocol ID, Length, Unit ID
252
+ uint8_t mbap[7] = {
253
+ 0x00, 0x01, 0x00, 0x00,
254
+ static_cast<uint8_t>((pdu.size() + 1) >> 8),
255
+ static_cast<uint8_t>((pdu.size() + 1) & 0xFF),
256
+ deviceAddress
257
+ };
258
+
259
+ std::vector<uint8_t> packet(mbap, mbap + 7);
260
+ packet.insert(packet.end(), pdu.begin(), pdu.end());
261
+
262
+ // Send the packet
263
+ ssize_t bytesSent = send(sockfd, reinterpret_cast<const char*>(packet.data()), packet.size(), 0);
264
+ if (bytesSent < 0) {
265
+ #ifdef _WIN32
266
+ int err = WSAGetLastError();
267
+ std::cerr << "Send failed: WSA error " << err << "\n";
268
+ #else
269
+ std::cerr << "Send failed: " << strerror(errno) << " (errno = " << errno << ")\n";
270
+ #endif
271
+ disconnect();
272
+ connected = false;
273
+ return false;
274
+ }
275
+
276
+ // Receive response
277
+ uint8_t buf[260] = {0};
278
+ ssize_t len = recv(sockfd, reinterpret_cast<char*>(buf), sizeof(buf), 0);
279
+ if (len < 0) {
280
+ #ifdef _WIN32
281
+ int err = WSAGetLastError();
282
+ std::cerr << "Receive failed: WSA error " << err << "\n";
283
+ #else
284
+ std::cerr << "Receive failed: " << strerror(errno) << " (errno = " << errno << ")\n";
285
+ #endif
286
+ disconnect();
287
+ connected = false;
288
+ return false;
289
+ }
290
+
291
+ if (len < 9) {
292
+ std::cerr << "Incomplete MODBUS response (len = " << len << ")\n";
293
+ disconnect();
294
+ connected = false;
295
+ return false;
296
+ }
297
+
298
+ response.assign(buf + 7, buf + len);
299
+ return true;
300
+ }
301
+
302
+ bool ModbusClient::readBit(const std::string& remote, int& result) {
303
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
304
+ ModbusRequest req = createReadRequest(READ_DISCRETE_INPUTS, addr, 1);
305
+ ModbusResponse res;
306
+ if (!sendRequest(req, res)) return false;
307
+
308
+ result = (res.data[1] & 0x01) ? 1 : 0;
309
+ return true;
310
+ }
311
+
312
+ bool ModbusClient::writeBit(const std::string& remote, int value) {
313
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
314
+ ModbusRequest req = createWriteSingleCoil(addr, value != 0);
315
+ ModbusResponse res;
316
+ return sendRequest(req, res);
317
+ }
318
+
319
+ bool ModbusClient::readByte(const std::string& remote, uint8_t& result) {
320
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
321
+ ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 1);
322
+ ModbusResponse res;
323
+ if (!sendRequest(req, res)) return false;
324
+
325
+ // Read only low byte
326
+ result = res.data.size() >= 2 ? res.data[1] : 0;
327
+ return true;
328
+ }
329
+
330
+ bool ModbusClient::writeByte(const std::string& remote, uint8_t value) {
331
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
332
+ // Store value in lower byte; high byte = 0
333
+ std::vector<uint8_t> data = { 0x00, value };
334
+ ModbusRequest req = { deviceAddress, WRITE_SINGLE_REGISTER, addr, 1, data };
335
+ ModbusResponse res;
336
+ return sendRequest(req, res);
337
+ }
338
+
339
+ bool ModbusClient::readWord(const std::string& remote, uint16_t& result) {
340
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
341
+ ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 1);
342
+ ModbusResponse res;
343
+ if (!sendRequest(req, res)) return false;
344
+
345
+ result = (res.data[0] << 8) | res.data[1];
346
+ return true;
347
+ }
348
+
349
+ bool ModbusClient::writeWord(const std::string& remote, uint16_t value) {
350
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
351
+ ModbusRequest req = createWriteSingleRegister(addr, value);
352
+ ModbusResponse res;
353
+ return sendRequest(req, res);
354
+ }
355
+
356
+ bool ModbusClient::readDWord(const std::string& remote, uint32_t& result) {
357
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
358
+ ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 2);
359
+ ModbusResponse res;
360
+ if (!sendRequest(req, res)) return false;
361
+
362
+ result = (res.data[0] << 24) | (res.data[1] << 16) | (res.data[2] << 8) | res.data[3];
363
+ return true;
364
+ }
365
+
366
+ bool ModbusClient::writeDWord(const std::string& remote, uint32_t value) {
367
+ uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
368
+ std::vector<uint8_t> data = {
369
+ static_cast<uint8_t>(value >> 24),
370
+ static_cast<uint8_t>((value >> 16) & 0xFF),
371
+ static_cast<uint8_t>((value >> 8) & 0xFF),
372
+ static_cast<uint8_t>(value & 0xFF)
373
+ };
374
+
375
+ ModbusRequest req = { deviceAddress, WRITE_MULTIPLE_REGISTERS, addr, 2, data };
376
+ ModbusResponse res;
377
+ return sendRequest(req, res);
378
+ }
@@ -0,0 +1,124 @@
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 Modbus
17
+ * @author Nathan Skipper, MTI
18
+ * @version 1.0.2
19
+ * @copyright Apache 2.0
20
+ */
21
+ #pragma once
22
+ #ifndef MODBUS_H
23
+ #define MODBUS_H
24
+
25
+ #include <cstdint>
26
+ #include <vector>
27
+ #include <map>
28
+ #include <string>
29
+ #include "nodalis.h"
30
+
31
+ #ifdef _WIN32
32
+ #include <winsock2.h>
33
+ typedef int socklen_t;
34
+ #else
35
+ #include <sys/socket.h>
36
+ #include <arpa/inet.h>
37
+ #include <unistd.h>
38
+ #endif
39
+
40
+ // MODBUS function codes
41
+ enum ModbusFunctionCode {
42
+ READ_COILS = 0x01,
43
+ READ_DISCRETE_INPUTS = 0x02,
44
+ READ_HOLDING_REGISTERS = 0x03,
45
+ READ_INPUT_REGISTERS = 0x04,
46
+ WRITE_SINGLE_COIL = 0x05,
47
+ WRITE_SINGLE_REGISTER = 0x06,
48
+ WRITE_MULTIPLE_COILS = 0x0F,
49
+ WRITE_MULTIPLE_REGISTERS = 0x10
50
+ };
51
+
52
+ struct ModbusRequest {
53
+ uint8_t address;
54
+ uint8_t function;
55
+ uint16_t startAddress;
56
+ uint16_t quantity;
57
+ std::vector<uint8_t> data;
58
+ };
59
+
60
+ struct ModbusResponse {
61
+ uint8_t address;
62
+ uint8_t function;
63
+ std::vector<uint8_t> data;
64
+ uint8_t exceptionCode;
65
+ };
66
+
67
+ // Server implementation
68
+ class ModbusServer {
69
+ public:
70
+ ModbusServer();
71
+
72
+ void setCoil(uint16_t address, bool value);
73
+ bool getCoil(uint16_t address);
74
+
75
+ void setDiscreteInput(uint16_t address, bool value);
76
+ bool getDiscreteInput(uint16_t address);
77
+
78
+ void setRegister(uint16_t address, uint16_t value);
79
+ uint16_t getRegister(uint16_t address);
80
+
81
+ ModbusResponse handleRequest(const ModbusRequest& request);
82
+
83
+ private:
84
+ std::map<uint16_t, bool> coils;
85
+ std::map<uint16_t, bool> discreteInputs;
86
+ std::map<uint16_t, uint16_t> holdingRegisters;
87
+ };
88
+
89
+ // Client implementation
90
+ class ModbusClient : public IOClient{
91
+ public:
92
+ ModbusClient(const std::string& ip = "", uint16_t port = 502, uint8_t unitId = 1);
93
+ ~ModbusClient();
94
+
95
+ bool connectTCP(const std::string& ip, uint16_t port);
96
+ void disconnect();
97
+
98
+ ModbusRequest createReadRequest(uint8_t function, uint16_t startAddress, uint16_t quantity);
99
+ ModbusRequest createWriteSingleCoil(uint16_t address, bool value);
100
+ ModbusRequest createWriteSingleRegister(uint16_t address, uint16_t value);
101
+
102
+ bool sendRequest(const ModbusRequest& request, ModbusResponse& response);
103
+
104
+ protected:
105
+ std::string ip;
106
+ uint16_t port;
107
+ bool readBit(const std::string& remote, int& result) override;
108
+ bool writeBit(const std::string& remote, int value) override;
109
+ bool readByte(const std::string& remote, uint8_t& result) override;
110
+ bool writeByte(const std::string& remote, uint8_t value) override;
111
+ bool readWord(const std::string& remote, uint16_t& result) override;
112
+ bool writeWord(const std::string& remote, uint16_t value) override;
113
+ bool readDWord(const std::string& remote, uint32_t& result) override;
114
+ bool writeDWord(const std::string& remote, uint32_t value) override;
115
+ void connect() override;
116
+
117
+ private:
118
+ int sockfd;
119
+ uint8_t deviceAddress;
120
+
121
+ bool sendRaw(const std::vector<uint8_t>& requestPDU, std::vector<uint8_t>& responsePDU);
122
+ };
123
+
124
+ #endif // MODBUS_H