nodalis-compiler 1.0.26 → 1.0.28

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 (39) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +1 -1
  3. package/src/compilers/ArduinoCompiler.js +45 -1
  4. package/src/compilers/support/arduino/gpio.cpp +1 -0
  5. package/src/compilers/support/arduino/modbus.cpp +96 -19
  6. package/src/compilers/support/arduino/modbus.h +1 -1
  7. package/src/compilers/support/arduino/network_config.cpp +502 -0
  8. package/src/compilers/support/arduino/network_config.h +20 -0
  9. package/src/compilers/support/arduino/nodalis.cpp +119 -15
  10. package/src/compilers/support/arduino/nodalis.h +10 -0
  11. package/src/compilers/support/generic/gpio.cpp +9 -0
  12. package/src/compilers/support/generic/gpio.h +1 -0
  13. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +0 -1
  14. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +1 -1
  15. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/NodalisEngine.1.0.10.nupkg +0 -0
  16. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.deps.json +24 -24
  17. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.dll +0 -0
  18. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.pdb +0 -0
  19. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.xml +0 -40
  20. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.dgspec.json +1 -1
  21. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.g.targets +1 -1
  22. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.10.nuspec +31 -0
  23. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfo.cs +3 -3
  24. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfoInputs.cache +1 -1
  25. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.assets.cache +0 -0
  26. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.AssemblyReference.cache +0 -0
  27. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.CoreCompileInputs.cache +1 -1
  28. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.dll +0 -0
  29. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.pdb +0 -0
  30. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.sourcelink.json +1 -1
  31. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.xml +0 -40
  32. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/ref/NodalisEngine.dll +0 -0
  33. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/refint/NodalisEngine.dll +0 -0
  34. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.assets.json +18 -18
  35. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.nuget.cache +4 -4
  36. package/src/compilers/support/nodejs/gpio.js +3 -0
  37. package/src/programmers/ArduinoProgrammer.js +37 -1
  38. package/src/programmers/SSHProgrammer.js +147 -10
  39. package/src/programmers/utils.js +27 -2
package/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
1
  # Changelog
2
+
3
+ ## [1.0.28] 2026-03-23
4
+ - Improved support for Arduino, fully tested Arduino compile -> program stack.
5
+ - Added a command line interface for nodalis on Arduino to use in setting IP, read/write of bits, and map info.
6
+
7
+ ## [1.0.27] 2026-03-20
8
+ - Corrected issues with SSH Programmer.
9
+
2
10
  ## [1.0.26] 2026-03-19
3
11
  - Improved error handling on programming.
4
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodalis-compiler",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Compiles IEC-61131-3/10 languages into code that can be used as a PLC on multiple platforms.",
5
5
  "icon": "nodalis.png",
6
6
  "main": "src/nodalis.js",
@@ -219,20 +219,62 @@ export class ArduinoCompiler extends Compiler {
219
219
 
220
220
  const inoCode = `#include "nodalis.h"
221
221
  #include <stdint.h>
222
+ #include <Ethernet.h>
222
223
  #include "modbus.h"
224
+ #include "network_config.h"
223
225
 
224
226
  NodalisModbusTcpServer modbusServer;
227
+ IPAddress localIp;
228
+
229
+ #if defined(LEDR)
230
+ const int NODALIS_HEARTBEAT_LED_PIN = LEDR;
231
+ #elif defined(LED_BUILTIN)
232
+ const int NODALIS_HEARTBEAT_LED_PIN = LED_BUILTIN;
233
+ #else
234
+ const int NODALIS_HEARTBEAT_LED_PIN = -1;
235
+ #endif
236
+
237
+ void nodalisHeartbeatTask() {
238
+ if (NODALIS_HEARTBEAT_LED_PIN < 0) {
239
+ return;
240
+ }
241
+
242
+ static uint64_t lastToggle = 0;
243
+ static bool ledState = false;
244
+ const uint64_t now = elapsed();
245
+ if (now - lastToggle < 500) {
246
+ return;
247
+ }
248
+
249
+ lastToggle = now;
250
+ ledState = !ledState;
251
+ digitalWrite(NODALIS_HEARTBEAT_LED_PIN, ledState ? HIGH : LOW);
252
+ }
225
253
  ${transpiledCode}
226
254
 
227
255
  void setup() {
256
+ Serial.begin(115200);
257
+ nodalisLogInfo("Setup starting");
258
+ if (NODALIS_HEARTBEAT_LED_PIN >= 0) {
259
+ pinMode(NODALIS_HEARTBEAT_LED_PIN, OUTPUT);
260
+ digitalWrite(NODALIS_HEARTBEAT_LED_PIN, LOW);
261
+ }
262
+ localIp = nodalisLoadIpAddress();
263
+ nodalisBeginEthernet(localIp);
264
+ nodalisLogInfo("Ethernet initialized");
228
265
  ${globals.join('\n')}
229
- modbusServer.start();
266
+ if (!modbusServer.start()) {
267
+ nodalisLogError("Modbus server start failed");
268
+ }
230
269
  ${mapCode}
270
+ nodalisLogInfo("Setup complete");
231
271
  }
232
272
 
233
273
  void loop() {
274
+ nodalisPollSerialIpConfig(localIp);
234
275
  modbusServer.poll();
235
276
  superviseIO();
277
+ nodalisHeartbeatTask();
236
278
  ${taskCode}
237
279
  delay(1);
238
280
  PROGRAM_COUNT++;
@@ -254,6 +296,8 @@ void loop() {
254
296
  fs.cpSync(path.join(supportDir, 'modbus.cpp'), path.join(outputPath, 'modbus.cpp'), { force: true });
255
297
  fs.cpSync(path.join(supportDir, 'gpio.h'), path.join(outputPath, 'gpio.h'), { force: true });
256
298
  fs.cpSync(path.join(supportDir, 'gpio.cpp'), path.join(outputPath, 'gpio.cpp'), { force: true });
299
+ fs.cpSync(path.join(supportDir, 'network_config.h'), path.join(outputPath, 'network_config.h'), { force: true });
300
+ fs.cpSync(path.join(supportDir, 'network_config.cpp'), path.join(outputPath, 'network_config.cpp'), { force: true });
257
301
  fs.cpSync(path.join(supportDir, 'json.hpp'), path.join(outputPath, 'json.hpp'), { force: true });
258
302
 
259
303
  if (this.isExecutableOutput()) {
@@ -19,6 +19,7 @@ void NodalisGPIOClient::onMappingAdded(const IOMap &map)
19
19
  uint8_t startPin = 0;
20
20
  if (!parsePin(map.remoteAddress, startPin))
21
21
  {
22
+ logErrorThrottled("Invalid GPIO pin mapping " + describeMapping(map));
22
23
  return;
23
24
  }
24
25
 
@@ -55,7 +55,7 @@ ModbusResponse NodalisModbusServer::handleRequest(const ModbusRequest &request)
55
55
  NodalisModbusClient::NodalisModbusClient(const std::string &ip, uint16_t port, uint8_t unitId)
56
56
  : IOClient("MODBUS-TCP"),
57
57
  ip(ip),
58
- port(port == 0 ? 502 : port),
58
+ port(port),
59
59
  modbusTcpClient(tcpClient),
60
60
  deviceAddress(unitId == 0 ? 1 : unitId),
61
61
  serverResolved(false)
@@ -63,6 +63,10 @@ NodalisModbusClient::NodalisModbusClient(const std::string &ip, uint16_t port, u
63
63
  if (!ip.empty())
64
64
  {
65
65
  serverResolved = parseIp(ip, serverIp);
66
+ if (!serverResolved)
67
+ {
68
+ logErrorThrottled("Invalid remote IP " + ip);
69
+ }
66
70
  }
67
71
  }
68
72
 
@@ -109,7 +113,7 @@ void NodalisModbusClient::onMappingAdded(const IOMap &map)
109
113
  {
110
114
  ip = map.moduleID;
111
115
  }
112
- if (port == 0 && !map.modulePort.empty())
116
+ if (!map.modulePort.empty())
113
117
  {
114
118
  port = static_cast<uint16_t>(std::atoi(map.modulePort.c_str()));
115
119
  }
@@ -126,6 +130,10 @@ void NodalisModbusClient::connect()
126
130
  if (!ip.empty() && !serverResolved)
127
131
  {
128
132
  serverResolved = parseIp(ip, serverIp);
133
+ if (!serverResolved)
134
+ {
135
+ logErrorThrottled("Could not parse target IP " + ip);
136
+ }
129
137
  }
130
138
  connected = ensureConnected();
131
139
  }
@@ -133,7 +141,7 @@ void NodalisModbusClient::connect()
133
141
  bool NodalisModbusClient::connectTCP(const std::string &newIp, uint16_t newPort)
134
142
  {
135
143
  ip = newIp;
136
- port = (newPort == 0) ? 502 : newPort;
144
+ port = newPort;
137
145
  serverResolved = parseIp(ip, serverIp);
138
146
  connected = ensureConnected();
139
147
  return connected;
@@ -143,13 +151,20 @@ bool NodalisModbusClient::ensureConnected()
143
151
  {
144
152
  if (!serverResolved)
145
153
  {
154
+ logErrorThrottled("Server IP is unresolved");
146
155
  return false;
147
156
  }
148
157
  if (modbusTcpClient.connected())
149
158
  {
150
159
  return true;
151
160
  }
152
- return modbusTcpClient.begin(serverIp, port) == 1;
161
+ const uint16_t effectivePort = (port == 0) ? 502 : port;
162
+ const bool started = modbusTcpClient.begin(serverIp, effectivePort) == 1;
163
+ if (!started)
164
+ {
165
+ logErrorThrottled("TCP connect failed to " + ip + ":" + std::to_string(effectivePort));
166
+ }
167
+ return started;
153
168
  }
154
169
 
155
170
  void NodalisModbusClient::disconnect()
@@ -183,15 +198,16 @@ bool NodalisModbusClient::sendRequest(const ModbusRequest &request, ModbusRespon
183
198
  return false;
184
199
  }
185
200
 
186
- uint16_t NodalisModbusClient::parseRemoteAddress(const std::string &remote) const
201
+ bool NodalisModbusClient::parseRemoteAddress(const std::string &remote, uint16_t &address) const
187
202
  {
188
203
  char *endPtr = nullptr;
189
204
  const long parsed = std::strtol(remote.c_str(), &endPtr, 10);
190
- if (endPtr == remote.c_str() || parsed < 0)
205
+ if (endPtr == remote.c_str() || *endPtr != '\0' || parsed < 0 || parsed > 65535)
191
206
  {
192
- return 0;
207
+ return false;
193
208
  }
194
- return static_cast<uint16_t>(parsed & 0xFFFF);
209
+ address = static_cast<uint16_t>(parsed & 0xFFFF);
210
+ return true;
195
211
  }
196
212
 
197
213
  bool NodalisModbusClient::readBit(const std::string &remote, int &result)
@@ -200,10 +216,17 @@ bool NodalisModbusClient::readBit(const std::string &remote, int &result)
200
216
  {
201
217
  return false;
202
218
  }
203
- const uint16_t addr = parseRemoteAddress(remote);
204
- const long val = modbusTcpClient.coilRead(deviceAddress, addr);
219
+ uint16_t addr = 0;
220
+ if (!parseRemoteAddress(remote, addr))
221
+ {
222
+ logErrorThrottled("Invalid remote bit address \"" + remote + "\"");
223
+ return false;
224
+ }
225
+ const long val = modbusTcpClient.discreteInputRead(deviceAddress, addr);
205
226
  if (val < 0)
206
227
  {
228
+ logErrorThrottled("Discrete input read failed remote=\"" + remote + "\" parsed=" + std::to_string(addr) +
229
+ " unit=" + std::to_string(deviceAddress) + ": " + modbusTcpClient.lastError());
207
230
  return false;
208
231
  }
209
232
  result = (val == 0) ? 0 : 1;
@@ -216,8 +239,20 @@ bool NodalisModbusClient::writeBit(const std::string &remote, int value)
216
239
  {
217
240
  return false;
218
241
  }
219
- const uint16_t addr = parseRemoteAddress(remote);
220
- return modbusTcpClient.coilWrite(deviceAddress, addr, value != 0) == 1;
242
+ uint16_t addr = 0;
243
+ if (!parseRemoteAddress(remote, addr))
244
+ {
245
+ logErrorThrottled("Invalid remote bit address \"" + remote + "\"");
246
+ return false;
247
+ }
248
+ const int rc = modbusTcpClient.coilWrite(deviceAddress, addr, value != 0);
249
+ if (rc != 1)
250
+ {
251
+ logErrorThrottled("Coil write failed remote=\"" + remote + "\" parsed=" + std::to_string(addr) +
252
+ " unit=" + std::to_string(deviceAddress) + ": " + modbusTcpClient.lastError());
253
+ return false;
254
+ }
255
+ return true;
221
256
  }
222
257
 
223
258
  bool NodalisModbusClient::readByte(const std::string &remote, uint8_t &result)
@@ -242,10 +277,17 @@ bool NodalisModbusClient::readWord(const std::string &remote, uint16_t &result)
242
277
  {
243
278
  return false;
244
279
  }
245
- const uint16_t addr = parseRemoteAddress(remote);
280
+ uint16_t addr = 0;
281
+ if (!parseRemoteAddress(remote, addr))
282
+ {
283
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
284
+ return false;
285
+ }
246
286
  const long val = modbusTcpClient.holdingRegisterRead(deviceAddress, addr);
247
287
  if (val < 0)
248
288
  {
289
+ logErrorThrottled("Holding register read failed remote=\"" + remote + "\" parsed=" + std::to_string(addr) +
290
+ " unit=" + std::to_string(deviceAddress) + ": " + modbusTcpClient.lastError());
249
291
  return false;
250
292
  }
251
293
  result = static_cast<uint16_t>(val & 0xFFFF);
@@ -258,15 +300,32 @@ bool NodalisModbusClient::writeWord(const std::string &remote, uint16_t value)
258
300
  {
259
301
  return false;
260
302
  }
261
- const uint16_t addr = parseRemoteAddress(remote);
262
- return modbusTcpClient.holdingRegisterWrite(deviceAddress, addr, value) == 1;
303
+ uint16_t addr = 0;
304
+ if (!parseRemoteAddress(remote, addr))
305
+ {
306
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
307
+ return false;
308
+ }
309
+ const int rc = modbusTcpClient.holdingRegisterWrite(deviceAddress, addr, value);
310
+ if (rc != 1)
311
+ {
312
+ logErrorThrottled("Holding register write failed remote=\"" + remote + "\" parsed=" + std::to_string(addr) +
313
+ " unit=" + std::to_string(deviceAddress) + ": " + modbusTcpClient.lastError());
314
+ return false;
315
+ }
316
+ return true;
263
317
  }
264
318
 
265
319
  bool NodalisModbusClient::readDWord(const std::string &remote, uint32_t &result)
266
320
  {
267
321
  uint16_t hi = 0;
268
322
  uint16_t lo = 0;
269
- const uint16_t addr = parseRemoteAddress(remote);
323
+ uint16_t addr = 0;
324
+ if (!parseRemoteAddress(remote, addr))
325
+ {
326
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
327
+ return false;
328
+ }
270
329
 
271
330
  if (!readWord(std::to_string(addr), hi))
272
331
  {
@@ -283,7 +342,12 @@ bool NodalisModbusClient::readDWord(const std::string &remote, uint32_t &result)
283
342
 
284
343
  bool NodalisModbusClient::writeDWord(const std::string &remote, uint32_t value)
285
344
  {
286
- const uint16_t addr = parseRemoteAddress(remote);
345
+ uint16_t addr = 0;
346
+ if (!parseRemoteAddress(remote, addr))
347
+ {
348
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
349
+ return false;
350
+ }
287
351
  const uint16_t hi = static_cast<uint16_t>((value >> 16) & 0xFFFF);
288
352
  const uint16_t lo = static_cast<uint16_t>(value & 0xFFFF);
289
353
 
@@ -296,7 +360,12 @@ bool NodalisModbusClient::writeDWord(const std::string &remote, uint32_t value)
296
360
 
297
361
  bool NodalisModbusClient::readLWord(const std::string &remote, uint64_t &result)
298
362
  {
299
- const uint16_t addr = parseRemoteAddress(remote);
363
+ uint16_t addr = 0;
364
+ if (!parseRemoteAddress(remote, addr))
365
+ {
366
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
367
+ return false;
368
+ }
300
369
  uint16_t regs[4] = {0, 0, 0, 0};
301
370
 
302
371
  for (uint16_t i = 0; i < 4; ++i)
@@ -316,7 +385,12 @@ bool NodalisModbusClient::readLWord(const std::string &remote, uint64_t &result)
316
385
 
317
386
  bool NodalisModbusClient::writeLWord(const std::string &remote, uint64_t value)
318
387
  {
319
- const uint16_t addr = parseRemoteAddress(remote);
388
+ uint16_t addr = 0;
389
+ if (!parseRemoteAddress(remote, addr))
390
+ {
391
+ logErrorThrottled("Invalid remote register address \"" + remote + "\"");
392
+ return false;
393
+ }
320
394
  const uint16_t regs[4] = {
321
395
  static_cast<uint16_t>((value >> 48) & 0xFFFF),
322
396
  static_cast<uint16_t>((value >> 32) & 0xFFFF),
@@ -404,6 +478,7 @@ bool NodalisModbusTcpServer::start(uint8_t serverId)
404
478
 
405
479
  if (modbusServer.begin(serverId) != 1)
406
480
  {
481
+ nodalisLogError("Failed to start Modbus TCP server");
407
482
  return false;
408
483
  }
409
484
 
@@ -418,6 +493,7 @@ bool NodalisModbusTcpServer::start(uint8_t serverId)
418
493
 
419
494
  tcpServer.begin();
420
495
  started = true;
496
+ nodalisLogInfo("Modbus TCP server started");
421
497
 
422
498
  for (size_t i = 0; i < globals.size(); ++i)
423
499
  {
@@ -441,6 +517,7 @@ void NodalisModbusTcpServer::poll()
441
517
  EthernetClient client = tcpServer.available();
442
518
  if (client)
443
519
  {
520
+ nodalisLogInfo("Accepted Modbus TCP client");
444
521
  modbusServer.accept(client);
445
522
  }
446
523
  modbusServer.poll();
@@ -108,7 +108,7 @@ private:
108
108
  uint8_t deviceAddress;
109
109
  bool serverResolved;
110
110
 
111
- uint16_t parseRemoteAddress(const std::string &remote) const;
111
+ bool parseRemoteAddress(const std::string &remote, uint16_t &address) const;
112
112
  uint8_t parseUnitId(const IOMap &map) const;
113
113
  bool ensureConnected();
114
114
  bool parseIp(const std::string &value, IPAddress &out) const;