nodalis-compiler 1.0.30 → 1.0.32

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 (98) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +4 -1
  3. package/src/compilers/CPPCompiler.js +176 -2
  4. package/src/compilers/JSCompiler.js +15 -0
  5. package/src/compilers/st-parser/expressionConverter.js +8 -0
  6. package/src/compilers/support/generic/modbus.cpp +57 -18
  7. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +2 -2
  8. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +63 -10
  9. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +69 -9
  10. package/src/compilers/support/jint/nodalis/NodalisEngine/README.md +5 -0
  11. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.deps.json +263 -162
  12. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.dll +0 -0
  13. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.pdb +0 -0
  14. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.xml +2 -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/NodalisEngine.1.0.11.nupkg +0 -0
  17. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/NodalisEngine.1.0.12.nupkg +0 -0
  18. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.deps.json +263 -162
  19. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.dll +0 -0
  20. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.pdb +0 -0
  21. package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.xml +2 -1
  22. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.AssemblyInfo.cs +3 -3
  23. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.AssemblyInfoInputs.cache +1 -1
  24. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.assets.cache +0 -0
  25. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.csproj.AssemblyReference.cache +0 -0
  26. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.csproj.CoreCompileInputs.cache +1 -1
  27. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.dll +0 -0
  28. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.pdb +0 -0
  29. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.sourcelink.json +1 -1
  30. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.xml +2 -1
  31. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/ref/NodalisEngine.dll +0 -0
  32. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/refint/NodalisEngine.dll +0 -0
  33. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.dgspec.json +2 -2
  34. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.g.targets +3 -2
  35. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.10.nuspec +2 -2
  36. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.11.nuspec +31 -0
  37. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.12.nuspec +31 -0
  38. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfo.cs +3 -3
  39. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfoInputs.cache +1 -1
  40. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.assets.cache +0 -0
  41. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.AssemblyReference.cache +0 -0
  42. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.CoreCompileInputs.cache +1 -1
  43. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.dll +0 -0
  44. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.pdb +0 -0
  45. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.sourcelink.json +1 -1
  46. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.xml +2 -1
  47. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/ref/NodalisEngine.dll +0 -0
  48. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/refint/NodalisEngine.dll +0 -0
  49. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.assets.json +495 -224
  50. package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.nuget.cache +24 -19
  51. package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +14 -5
  52. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/BouncyCastle.Cryptography.dll +0 -0
  53. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Esprima.dll +0 -0
  54. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Jint.dll +0 -0
  55. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll +0 -0
  56. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Microsoft.Extensions.Logging.Abstractions.dll +0 -0
  57. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Newtonsoft.Json.dll +0 -0
  58. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.dll +0 -0
  59. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.pdb +0 -0
  60. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.xml +0 -693
  61. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC +0 -0
  62. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.deps.json +0 -358
  63. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.dll +0 -0
  64. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.pdb +0 -0
  65. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.runtimeconfig.json +0 -12
  66. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Client.dll +0 -0
  67. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Configuration.dll +0 -0
  68. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Core.dll +0 -0
  69. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Gds.Client.Common.dll +0 -0
  70. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Gds.Server.Common.dll +0 -0
  71. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Security.Certificates.dll +0 -0
  72. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Server.dll +0 -0
  73. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.IO.Pipelines.dll +0 -0
  74. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.Text.Encodings.Web.dll +0 -0
  75. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.Text.Json.dll +0 -0
  76. package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll +0 -0
  77. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +0 -4
  78. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.AssemblyInfo.cs +0 -22
  79. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.AssemblyInfoInputs.cache +0 -1
  80. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.GeneratedMSBuildEditorConfig.editorconfig +0 -15
  81. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.GlobalUsings.g.cs +0 -8
  82. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.assets.cache +0 -0
  83. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.AssemblyReference.cache +0 -0
  84. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.CoreCompileInputs.cache +0 -1
  85. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.FileListAbsolute.txt +0 -37
  86. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.Up2Date +0 -0
  87. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.dll +0 -0
  88. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.genruntimeconfig.cache +0 -1
  89. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.pdb +0 -0
  90. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.sourcelink.json +0 -1
  91. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/apphost +0 -0
  92. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/ref/NodalisPLC.dll +0 -0
  93. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/refint/NodalisPLC.dll +0 -0
  94. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.dgspec.json +0 -282
  95. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.g.props +0 -15
  96. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.g.targets +0 -7
  97. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/project.assets.json +0 -3181
  98. package/src/compilers/support/jint/nodalis/NodalisPLC/obj/project.nuget.cache +0 -51
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.32] 2026-06-05
4
+ - Fixed problems with writing registers to Modbus.
5
+
6
+ ## [1.0.31] 2026-04-28
7
+ - Added an "exports.json" file that can be used to designate exported local variables in
8
+ programs when compiling a folder of ST files.
9
+
3
10
  ## [1.0.30] 2026-03-30
4
11
  - Fixed issue with boolean assignments in C++
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodalis-compiler",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
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",
@@ -16,6 +16,9 @@
16
16
  },
17
17
  "files": [
18
18
  "src/",
19
+ "!src/compilers/support/jint/nodalis/NodalisPLC/bin/",
20
+ "!src/compilers/support/jint/nodalis/NodalisPLC/obj/",
21
+ "!src/compilers/support/jint/nodalis/publish/",
19
22
  "README.md",
20
23
  "LICENSE",
21
24
  "package.json",
@@ -45,6 +45,163 @@ export class CPPCompiler extends Compiler {
45
45
  this.name = 'CPPCompiler';
46
46
  }
47
47
 
48
+ loadStructuredTextBundle(sourcePath, resourceName) {
49
+ const exports = this.loadStructuredTextBundleExports(sourcePath, resourceName);
50
+ const stFiles = this.listStructuredTextBundleFiles(sourcePath);
51
+
52
+ if (stFiles.length === 0) {
53
+ throw new Error(`No .st files found in source directory "${sourcePath}".`);
54
+ }
55
+
56
+ const normalizedResource = String(resourceName || '').trim();
57
+ const candidateNames = new Set([
58
+ normalizedResource,
59
+ normalizedResource.toLowerCase(),
60
+ normalizedResource.toLowerCase().endsWith('.st') ? normalizedResource.toLowerCase() : `${normalizedResource.toLowerCase()}.st`
61
+ ]);
62
+
63
+ const entryFile = stFiles.find((file) => {
64
+ const lower = file.toLowerCase();
65
+ return candidateNames.has(file) || candidateNames.has(lower);
66
+ });
67
+
68
+ if (!entryFile) {
69
+ throw new Error(`resourceName "${resourceName}" is not an .st file in "${sourcePath}".`);
70
+ }
71
+
72
+ const orderedFiles = [
73
+ ...stFiles.filter((file) => file !== entryFile).sort((a, b) => a.localeCompare(b)),
74
+ entryFile
75
+ ];
76
+
77
+ let combinedSource = this.buildStructuredTextExportGlobals(exports);
78
+ let currentLine = combinedSource.length > 0 ? combinedSource.split('\n').length + 2 : 1;
79
+ const fileLineMappings = [];
80
+
81
+ for (const file of orderedFiles) {
82
+ const originalSource = fs.readFileSync(path.join(sourcePath, file), 'utf-8');
83
+ const fileSource = (file === entryFile
84
+ ? this.addStructuredTextExportAssignments(originalSource, exports)
85
+ : originalSource).trim();
86
+ if (fileSource.length === 0) continue;
87
+
88
+ if (combinedSource.length > 0) {
89
+ combinedSource += '\n\n';
90
+ }
91
+
92
+ const lineCount = fileSource.split('\n').length;
93
+ fileLineMappings.push({
94
+ file,
95
+ startLine: currentLine,
96
+ endLine: currentLine + lineCount - 1
97
+ });
98
+ combinedSource += fileSource;
99
+ currentLine += lineCount + 1;
100
+ }
101
+
102
+ const entrySource = fs.readFileSync(path.join(sourcePath, entryFile), 'utf-8');
103
+ const entryProgramName = this.extractFirstProgramName(entrySource) || path.basename(entryFile, path.extname(entryFile));
104
+
105
+ return { combinedSource, entryProgramName, fileLineMappings };
106
+ }
107
+
108
+ loadStructuredTextBundleExports(sourcePath, resourceName) {
109
+ const exportsPath = path.join(sourcePath, 'exports.json');
110
+ const resourceKey = this.getStructuredTextExportResourceKey(resourceName);
111
+ const defaultExports = {
112
+ T_MAIN: [{ global: 'Result', type: 'BOOL', address: '%MX0.0', variable: 'bResult' }]
113
+ };
114
+
115
+ if (!fs.existsSync(exportsPath)) {
116
+ fs.writeFileSync(exportsPath, JSON.stringify(defaultExports, null, 4));
117
+ return resourceKey === this.getStructuredTextExportResourceKey('T_MAIN') ? defaultExports.T_MAIN : [];
118
+ }
119
+
120
+ let exportConfig;
121
+ try {
122
+ exportConfig = JSON.parse(fs.readFileSync(exportsPath, 'utf-8'));
123
+ } catch (err) {
124
+ throw new Error(`Failed to load exports.json from ${exportsPath}: ${err.message}`);
125
+ }
126
+
127
+ if (typeof exportConfig !== 'object' || exportConfig === null || Array.isArray(exportConfig)) {
128
+ throw new Error(`exports.json in ${sourcePath} must be an object keyed by resource name.`);
129
+ }
130
+
131
+ const matchedKey = Object.keys(exportConfig).find((key) => this.getStructuredTextExportResourceKey(key) === resourceKey);
132
+ if (!matchedKey) return [];
133
+
134
+ const exports = exportConfig[matchedKey];
135
+ if (!Array.isArray(exports)) {
136
+ throw new Error(`exports.json entry for resource "${matchedKey}" must be an array.`);
137
+ }
138
+ return exports.map((entry, index) => this.validateStructuredTextBundleExport(entry, index, exportsPath));
139
+ }
140
+
141
+ getStructuredTextExportResourceKey(resourceName) {
142
+ return this.getStructuredTextExportResourceName(resourceName).toLowerCase();
143
+ }
144
+
145
+ getStructuredTextExportResourceName(resourceName) {
146
+ const normalized = String(resourceName || '').trim();
147
+ return path.basename(normalized, path.extname(normalized));
148
+ }
149
+
150
+ validateStructuredTextBundleExport(entry, index, exportsPath) {
151
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
152
+ throw new Error(`Invalid export at ${exportsPath}[${index}]: expected an object.`);
153
+ }
154
+
155
+ const global = String(entry.global || '').trim();
156
+ const type = String(entry.type || 'BOOL').trim();
157
+ const address = String(entry.address || '').trim();
158
+ const variable = String(entry.variable || '').trim();
159
+ const identifierPattern = /^[A-Za-z_]\w*$/;
160
+ const variablePattern = /^[A-Za-z_]\w*(?:\.\w+)?$/;
161
+ const addressPattern = /^%[IQM][A-Z]*\d+(?:\.\d+)?$/i;
162
+
163
+ if (!identifierPattern.test(global)) {
164
+ throw new Error(`Invalid export global at ${exportsPath}[${index}]: "${entry.global}" is not a valid ST identifier.`);
165
+ }
166
+ if (!identifierPattern.test(type)) {
167
+ throw new Error(`Invalid export type at ${exportsPath}[${index}]: "${entry.type}" is not a valid ST type identifier.`);
168
+ }
169
+ if (!addressPattern.test(address)) {
170
+ throw new Error(`Invalid export address at ${exportsPath}[${index}]: "${entry.address}" is not a valid ST address.`);
171
+ }
172
+ if (!variablePattern.test(variable)) {
173
+ throw new Error(`Invalid export variable at ${exportsPath}[${index}]: "${entry.variable}" is not a valid ST variable reference.`);
174
+ }
175
+
176
+ return { global, type, address, variable };
177
+ }
178
+
179
+ buildStructuredTextExportGlobals(exports) {
180
+ if (!Array.isArray(exports) || exports.length === 0) return '';
181
+
182
+ return [
183
+ 'VAR_GLOBAL',
184
+ ...exports.flatMap((entry) => [
185
+ ` ${entry.global} AT ${entry.address} : ${entry.type};`,
186
+ `//Global=${JSON.stringify({ Name: entry.global, Address: entry.address })}`
187
+ ]),
188
+ 'END_VAR'
189
+ ].join('\n');
190
+ }
191
+
192
+ addStructuredTextExportAssignments(entrySource, exports) {
193
+ if (!Array.isArray(exports) || exports.length === 0) return entrySource;
194
+
195
+ const assignmentBlock = exports.map((entry) => `${entry.global} := ${entry.variable};`).join('\n');
196
+ const endProgramPattern = /^(\s*END_PROGRAM\b[^\r\n]*)/im;
197
+
198
+ if (!endProgramPattern.test(entrySource)) {
199
+ throw new Error('Entry program does not contain END_PROGRAM; cannot add exports.json assignments.');
200
+ }
201
+
202
+ return entrySource.replace(endProgramPattern, `${assignmentBlock}\n$1`);
203
+ }
204
+
48
205
  get supportedLanguages() {
49
206
  return [IECLanguage.STRUCTURED_TEXT, IECLanguage.LADDER_DIAGRAM];
50
207
  }
@@ -301,18 +458,22 @@ int main() {
301
458
 
302
459
  inputs.push(`"${open62541o}"`);
303
460
  inputs.push(`"${bacneta}"`);
461
+ const debugFlags = this.getDebugCompileFlags(compiler);
304
462
 
305
463
  if (compiler === 'cl.exe') {
306
464
  const cppFlagSegment = formatFlags(archFlags.cpp);
307
- cppCompileCmd = `cl.exe /I${bacneti} /I${bacneti}/ports/${isWindowsTarget ? "win32" : "linux"} ${cppFlagSegment}/EHsc /std:c++17 /Fe:"${exeFile}" ` +
465
+ cppCompileCmd = `cl.exe /I${bacneti} /I${bacneti}/ports/${isWindowsTarget ? "win32" : "linux"} ${cppFlagSegment}${debugFlags} /EHsc /std:c++17 /Fe:"${exeFile}" ` +
308
466
  `"${cppFile}" "${pathTo('nodalis.cpp')}" "${pathTo('modbus.cpp')}" "${pathTo('opcua.cpp')}" "${pathTo('bacnet.cpp')}" "${pathTo('gpio.cpp')}"`; //"${pathTo('open62541.obj')}"`;
309
467
  } else {
310
468
  const cppFlagSegment = formatFlags(archFlags.cpp);
311
- cppCompileCmd = `${compiler} ${cppFlagSegment}-std=c++17 -I${bacneti} -I${bacneti}/ports/${isWindowsTarget ? "win32" : "linux"} -o "${exeFile}" ${inputs.join(' ')} ${archFlags.linker}`;
469
+ cppCompileCmd = `${compiler} ${cppFlagSegment}${debugFlags} -std=c++17 -I${bacneti} -I${bacneti}/ports/${isWindowsTarget ? "win32" : "linux"} -o "${exeFile}" ${inputs.join(' ')} ${archFlags.linker}`;
312
470
  }
313
471
 
314
472
  try {
315
473
  execSync(cppCompileCmd, { stdio: 'pipe' });
474
+ if (targetInfo.os === 'macos') {
475
+ this.generateMacOSDebugSymbols(exeFile);
476
+ }
316
477
  if (isWindowsTarget) {
317
478
  this.copyWindowsRuntimeDependencies(compiler, binDir);
318
479
  }
@@ -326,6 +487,19 @@ int main() {
326
487
  }
327
488
  }
328
489
 
490
+ getDebugCompileFlags(compiler) {
491
+ if (compiler === 'cl.exe') return '/Zi /Od';
492
+ return '-g -O0';
493
+ }
494
+
495
+ generateMacOSDebugSymbols(exeFile) {
496
+ try {
497
+ execSync(`dsymutil "${exeFile}"`, { stdio: 'pipe' });
498
+ } catch {
499
+ // Debug symbols are best-effort; the executable is still usable without a dSYM.
500
+ }
501
+ }
502
+
329
503
  copyWindowsRuntimeDependencies(compiler, binDir) {
330
504
  const compilerName = String(compiler || '').toLowerCase();
331
505
  const isMingwToolchain = compilerName.includes('mingw') || compilerName.includes('w64');
@@ -114,6 +114,7 @@ export class JSCompiler extends Compiler {
114
114
  let tasks = [];
115
115
  let programs = [];
116
116
  let globals = [];
117
+ let csharpGlobals = [];
117
118
  let taskCode = "";
118
119
  let mapCode = "";
119
120
  let plcname = "NodalisPLC";
@@ -143,6 +144,7 @@ export class JSCompiler extends Compiler {
143
144
  else if(line.indexOf("//Global=") > -1){
144
145
  let global = JSON.parse(line.substring(line.indexOf("=") + 1).trim());
145
146
  globals.push(`opcServer.mapVariable("${global.Name}", "${global.Address}");`)
147
+ csharpGlobals.push(`opcServer.MapVariable(${toCSharpStringLiteral(global.Name)}, ${toCSharpStringLiteral(global.Address)});`)
146
148
 
147
149
  }
148
150
  else if(line.trim().startsWith("PROGRAM")){
@@ -273,6 +275,7 @@ export function run(){
273
275
 
274
276
  // Keep the Jint project sources in the main output directory.
275
277
  fs.cpSync(supportDir, projectOutputDir, { recursive: true, force: true });
278
+ patchJintProgramOpcMappings(projectOutputDir, csharpGlobals);
276
279
 
277
280
  // Build from the main output directory, then move publish artifacts under bin.
278
281
  const buildPath = path.resolve(path.join(projectOutputDir, buildScript));
@@ -347,3 +350,15 @@ function installDependencies(outputDir) {
347
350
  shell: true
348
351
  });
349
352
  }
353
+
354
+ function patchJintProgramOpcMappings(projectOutputDir, csharpGlobals) {
355
+ const programPath = path.join(projectOutputDir, "NodalisPLC", "Program.cs");
356
+ let programSource = fs.readFileSync(programPath, "utf-8");
357
+ const mappingCode = csharpGlobals.map((line) => ` ${line}`).join("\n");
358
+ programSource = programSource.replace(" // {opcServerMappings}", mappingCode);
359
+ fs.writeFileSync(programPath, programSource, "utf-8");
360
+ }
361
+
362
+ function toCSharpStringLiteral(value) {
363
+ return `"${String(value).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
364
+ }
@@ -102,6 +102,7 @@ export function convertExpression(expr, isjsfb = false, jsfbVars = [], isjs=fals
102
102
  .replace(/\b(?<![><!])=(?!=)/g, '=='); // ✅ fix assignment/comparison
103
103
 
104
104
  results = rewriteExponentOperator(results, isjs);
105
+ results = rewriteNumericNotOperator(results);
105
106
 
106
107
  // JS accepts 0o..., but C++ requires legacy octal form 0...
107
108
  if (!isjs) {
@@ -150,6 +151,13 @@ export function convertExpression(expr, isjsfb = false, jsfbVars = [], isjs=fals
150
151
  return results;
151
152
  }
152
153
 
154
+ function rewriteNumericNotOperator(expression) {
155
+ return String(expression || '').replace(
156
+ /!\s+([+-]?(?:0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|\d+))/g,
157
+ '~$1'
158
+ );
159
+ }
160
+
153
161
  function rewriteExponentOperator(expression, isjs) {
154
162
  let result = String(expression || '');
155
163
  const operand = String.raw`(?:\([^()]*\)|[A-Za-z_]\w*(?:\.\d+)?|[+-]?(?:0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]?\d+)?))`;
@@ -197,47 +197,86 @@ ModbusRequest ModbusClient::createWriteSingleRegister(uint16_t address, uint16_t
197
197
  return { deviceAddress, WRITE_SINGLE_REGISTER, address, 1, data };
198
198
  }
199
199
 
200
- bool ModbusClient::sendRequest(const ModbusRequest& req, ModbusResponse& resp) {
200
+ bool ModbusClient::sendRequest(const ModbusRequest &req, ModbusResponse &resp)
201
+ {
201
202
  std::vector<uint8_t> pdu;
202
203
 
203
- // Handle function-specific encoding
204
- if (req.function == WRITE_SINGLE_COIL) {
205
- // Function 0x05: Write Single Coil
204
+ if (req.function == WRITE_SINGLE_COIL)
205
+ {
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
+ {
213
+ std::cerr << "Invalid data size for Write Single Coil.\n";
214
+ return false;
215
+ }
216
+
217
+ pdu.push_back(req.data[0]);
218
+ pdu.push_back(req.data[1]);
219
+ }
220
+ else if (req.function == WRITE_SINGLE_REGISTER)
221
+ {
206
222
  pdu = {
207
223
  req.function,
208
224
  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";
225
+ static_cast<uint8_t>(req.startAddress & 0xFF)};
226
+
227
+ if (req.data.size() != 2)
228
+ {
229
+ std::cerr << "Invalid data size for Write Single Register.\n";
216
230
  return false;
217
231
  }
218
- } else {
219
- // Default encoding for most function codes
232
+
233
+ pdu.push_back(req.data[0]); // value high byte
234
+ pdu.push_back(req.data[1]); // value low byte
235
+ }
236
+ else if (req.function == READ_HOLDING_REGISTERS ||
237
+ req.function == READ_INPUT_REGISTERS ||
238
+ req.function == READ_COILS ||
239
+ req.function == READ_DISCRETE_INPUTS)
240
+ {
220
241
  pdu = {
221
242
  req.function,
222
243
  static_cast<uint8_t>(req.startAddress >> 8),
223
244
  static_cast<uint8_t>(req.startAddress & 0xFF),
224
245
  static_cast<uint8_t>(req.quantity >> 8),
225
- static_cast<uint8_t>(req.quantity & 0xFF)
226
- };
246
+ static_cast<uint8_t>(req.quantity & 0xFF)};
247
+ }
248
+ else if (req.function == WRITE_MULTIPLE_REGISTERS)
249
+ {
250
+ pdu = {
251
+ req.function,
252
+ static_cast<uint8_t>(req.startAddress >> 8),
253
+ static_cast<uint8_t>(req.startAddress & 0xFF),
254
+ static_cast<uint8_t>(req.quantity >> 8),
255
+ static_cast<uint8_t>(req.quantity & 0xFF),
256
+ static_cast<uint8_t>(req.data.size())};
257
+
227
258
  pdu.insert(pdu.end(), req.data.begin(), req.data.end());
228
259
  }
260
+ else
261
+ {
262
+ std::cerr << "Unsupported Modbus function: " << static_cast<int>(req.function) << "\n";
263
+ return false;
264
+ }
229
265
 
230
266
  std::vector<uint8_t> response;
231
- if (!sendRaw(pdu, response)) return false;
267
+ if (!sendRaw(pdu, response))
268
+ return false;
232
269
 
233
- if (response.size() < 2) return false;
270
+ if (response.size() < 2)
271
+ return false;
234
272
 
235
273
  resp.address = req.address;
236
274
  resp.function = response[0];
237
275
  resp.data.assign(response.begin() + 1, response.end());
238
276
  resp.exceptionCode = (resp.function & 0x80) ? resp.data[0] : 0;
239
277
 
240
- if (resp.exceptionCode > 0) {
278
+ if (resp.exceptionCode > 0)
279
+ {
241
280
  std::cerr << "MODBUS exception code: " << static_cast<int>(resp.exceptionCode) << "\n";
242
281
  return false;
243
282
  }
@@ -12,7 +12,7 @@
12
12
  <PackageReference Include="BACnet" Version="3.0.2" />
13
13
  <PackageReference Include="Jint" Version="4.4.2" />
14
14
  <PackageReference Include="System.Text.Json" Version="*" />
15
- <PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.5.374.118" />
15
+ <PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.5.378.134" />
16
16
  </ItemGroup>
17
17
  <ItemGroup>
18
18
  <None Include="README.md" Pack="true" PackagePath="\" />
@@ -24,7 +24,7 @@
24
24
  <PropertyGroup>
25
25
  <TargetFrameworks>net8.0</TargetFrameworks>
26
26
  <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- optional -->
27
- <Version>1.0.10</Version>
27
+ <Version>1.0.12</Version>
28
28
  <Authors>Nathan Skipper</Authors>
29
29
  <Company>Montgomery Technology, Inc.</Company>
30
30
  <PackageId>NodalisEngine</PackageId>
@@ -21,6 +21,7 @@ using Opc.Ua.Client;
21
21
  using Opc.Ua.Configuration;
22
22
  using System;
23
23
  using System.Collections.Generic;
24
+ using System.IO;
24
25
  using System.Threading.Tasks;
25
26
 
26
27
  namespace Nodalis
@@ -30,7 +31,7 @@ namespace Nodalis
30
31
  /// </summary>
31
32
  public class OPCClient : IOClient
32
33
  {
33
- private Session? _session;
34
+ private ISession? _session;
34
35
  private ApplicationConfiguration? _config;
35
36
  /// <summary>
36
37
  /// Instantiates a new OPCClient.
@@ -52,23 +53,35 @@ namespace Nodalis
52
53
  {
53
54
  ApplicationName = "NodalisOPCUAClient",
54
55
  ApplicationType = ApplicationType.Client,
55
- SecurityConfiguration = new SecurityConfiguration
56
- {
57
- ApplicationCertificate = new CertificateIdentifier(),
58
- AutoAcceptUntrustedCertificates = true,
59
- },
56
+ SecurityConfiguration = CreateSecurityConfiguration(),
60
57
  TransportConfigurations = new TransportConfigurationCollection(),
61
58
  TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
62
59
  ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
63
60
  };
64
61
  await _config.Validate(ApplicationType.Client);
62
+ await _config.CertificateValidator.UpdateAsync(_config, default);
65
63
 
66
64
  var app = new ApplicationInstance { ApplicationName = "NodalisOPCUAClient", ApplicationType = ApplicationType.Client, ApplicationConfiguration = _config };
67
- var endpoint = CoreClientUtils.SelectEndpoint(endpointUrl, false, 15000);
68
- var endpointConfig = EndpointConfiguration.Create(_config);
69
- var endpointDesc = new ConfiguredEndpoint(null, endpoint, endpointConfig);
65
+ await app.CheckApplicationInstanceCertificatesAsync(false, 0);
70
66
 
71
- _session = await Session.Create(_config, endpointDesc, false, "", 60000, null, null);
67
+ var endpoint = CoreClientUtils.SelectEndpoint(_config, endpointUrl, false, 15000);
68
+ var endpointConfig = EndpointConfiguration.Create(_config);
69
+ var configuredEndpoint = new ConfiguredEndpoint(null, endpoint, endpointConfig);
70
+
71
+ var sessionFactory = new DefaultSessionFactory();
72
+
73
+ _session = await sessionFactory.CreateAsync(
74
+ _config,
75
+ (ITransportWaitingConnection?)null,
76
+ configuredEndpoint,
77
+ true,
78
+ false,
79
+ _config.ApplicationName,
80
+ 60000,
81
+ new UserIdentity(),
82
+ null,
83
+ default
84
+ );
72
85
  connected = true;
73
86
  Console.WriteLine("OPC UA connected.");
74
87
  }
@@ -80,6 +93,46 @@ namespace Nodalis
80
93
  });
81
94
  }
82
95
 
96
+ private static SecurityConfiguration CreateSecurityConfiguration()
97
+ {
98
+ string pkiRoot = Path.Combine(AppContext.BaseDirectory, "pki");
99
+ string ownStore = Path.Combine(pkiRoot, "own");
100
+ string trustedPeerStore = Path.Combine(pkiRoot, "trusted");
101
+ string trustedIssuerStore = Path.Combine(pkiRoot, "issuer");
102
+ string rejectedStore = Path.Combine(pkiRoot, "rejected");
103
+
104
+ Directory.CreateDirectory(ownStore);
105
+ Directory.CreateDirectory(trustedPeerStore);
106
+ Directory.CreateDirectory(trustedIssuerStore);
107
+ Directory.CreateDirectory(rejectedStore);
108
+
109
+ return new SecurityConfiguration
110
+ {
111
+ ApplicationCertificate = new CertificateIdentifier
112
+ {
113
+ StoreType = CertificateStoreType.Directory,
114
+ StorePath = ownStore,
115
+ SubjectName = "CN=NodalisOPCUAClient"
116
+ },
117
+ TrustedPeerCertificates = new CertificateTrustList
118
+ {
119
+ StoreType = CertificateStoreType.Directory,
120
+ StorePath = trustedPeerStore
121
+ },
122
+ TrustedIssuerCertificates = new CertificateTrustList
123
+ {
124
+ StoreType = CertificateStoreType.Directory,
125
+ StorePath = trustedIssuerStore
126
+ },
127
+ RejectedCertificateStore = new CertificateTrustList
128
+ {
129
+ StoreType = CertificateStoreType.Directory,
130
+ StorePath = rejectedStore
131
+ },
132
+ AutoAcceptUntrustedCertificates = true
133
+ };
134
+ }
135
+
83
136
  private NodeId GetNodeId(string remote) => new NodeId($"s={remote}", 1);
84
137
 
85
138
  public override bool ReadBit(string address, out int result)