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.
- package/CHANGELOG.md +7 -0
- package/package.json +4 -1
- package/src/compilers/CPPCompiler.js +176 -2
- package/src/compilers/JSCompiler.js +15 -0
- package/src/compilers/st-parser/expressionConverter.js +8 -0
- package/src/compilers/support/generic/modbus.cpp +57 -18
- package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +2 -2
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +63 -10
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +69 -9
- package/src/compilers/support/jint/nodalis/NodalisEngine/README.md +5 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.deps.json +263 -162
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Debug/net8.0/NodalisEngine.xml +2 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/NodalisEngine.1.0.10.nupkg +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/NodalisEngine.1.0.11.nupkg +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/NodalisEngine.1.0.12.nupkg +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.deps.json +263 -162
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/bin/Release/net8.0/NodalisEngine.xml +2 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.AssemblyInfo.cs +3 -3
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.AssemblyInfoInputs.cache +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.assets.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.csproj.AssemblyReference.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.csproj.CoreCompileInputs.cache +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.sourcelink.json +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/NodalisEngine.xml +2 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/ref/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Debug/net8.0/refint/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.dgspec.json +2 -2
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/NodalisEngine.csproj.nuget.g.targets +3 -2
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.10.nuspec +2 -2
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.11.nuspec +31 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/NodalisEngine.1.0.12.nuspec +31 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfo.cs +3 -3
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.AssemblyInfoInputs.cache +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.assets.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.AssemblyReference.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.csproj.CoreCompileInputs.cache +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.sourcelink.json +1 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/NodalisEngine.xml +2 -1
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/ref/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/Release/net8.0/refint/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.assets.json +495 -224
- package/src/compilers/support/jint/nodalis/NodalisEngine/obj/project.nuget.cache +24 -19
- package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +14 -5
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/BouncyCastle.Cryptography.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Esprima.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Jint.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Microsoft.Extensions.Logging.Abstractions.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Newtonsoft.Json.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisEngine.xml +0 -693
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.deps.json +0 -358
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/NodalisPLC.runtimeconfig.json +0 -12
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Client.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Configuration.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Core.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Gds.Client.Common.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Gds.Server.Common.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Security.Certificates.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/Opc.Ua.Server.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.IO.Pipelines.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.Text.Encodings.Web.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/System.Text.Json.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bin/Debug/net8.0/runtimes/browser/lib/net8.0/System.Text.Encodings.Web.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +0 -4
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.AssemblyInfo.cs +0 -22
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.AssemblyInfoInputs.cache +0 -1
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.GeneratedMSBuildEditorConfig.editorconfig +0 -15
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.GlobalUsings.g.cs +0 -8
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.assets.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.AssemblyReference.cache +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.CoreCompileInputs.cache +0 -1
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.FileListAbsolute.txt +0 -37
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.csproj.Up2Date +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.genruntimeconfig.cache +0 -1
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.pdb +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/NodalisPLC.sourcelink.json +0 -1
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/apphost +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/ref/NodalisPLC.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/Debug/net8.0/refint/NodalisPLC.dll +0 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.dgspec.json +0 -282
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.g.props +0 -15
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/NodalisPLC.csproj.nuget.g.targets +0 -7
- package/src/compilers/support/jint/nodalis/NodalisPLC/obj/project.assets.json +0 -3181
- 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.
|
|
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&
|
|
200
|
+
bool ModbusClient::sendRequest(const ModbusRequest &req, ModbusResponse &resp)
|
|
201
|
+
{
|
|
201
202
|
std::vector<uint8_t> pdu;
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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()
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
219
|
-
//
|
|
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))
|
|
267
|
+
if (!sendRaw(pdu, response))
|
|
268
|
+
return false;
|
|
232
269
|
|
|
233
|
-
if (response.size() < 2)
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
68
|
-
var endpointConfig = EndpointConfiguration.Create(_config);
|
|
69
|
-
var endpointDesc = new ConfiguredEndpoint(null, endpoint, endpointConfig);
|
|
65
|
+
await app.CheckApplicationInstanceCertificatesAsync(false, 0);
|
|
70
66
|
|
|
71
|
-
|
|
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)
|