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.
- package/README.md +134 -0
- package/package.json +59 -0
- package/src/compilers/CPPCompiler.js +272 -0
- package/src/compilers/Compiler.js +108 -0
- package/src/compilers/JSCompiler.js +293 -0
- package/src/compilers/iec-parser/parser.js +4254 -0
- package/src/compilers/st-parser/expressionConverter.js +155 -0
- package/src/compilers/st-parser/gcctranspiler.js +237 -0
- package/src/compilers/st-parser/jstranspiler.js +254 -0
- package/src/compilers/st-parser/parser.js +367 -0
- package/src/compilers/st-parser/tokenizer.js +78 -0
- package/src/compilers/support/generic/json.hpp +25526 -0
- package/src/compilers/support/generic/modbus.cpp +378 -0
- package/src/compilers/support/generic/modbus.h +124 -0
- package/src/compilers/support/generic/nodalis.cpp +421 -0
- package/src/compilers/support/generic/nodalis.h +798 -0
- package/src/compilers/support/generic/opcua.cpp +267 -0
- package/src/compilers/support/generic/opcua.h +50 -0
- package/src/compilers/support/generic/open62541.c +151897 -0
- package/src/compilers/support/generic/open62541.h +50357 -0
- package/src/compilers/support/jint/nodalis/Nodalis.sln +28 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/ModbusClient.cs +200 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +817 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +16 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +172 -0
- package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +275 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/NodalisPLC.csproj +19 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +197 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.bat +5 -0
- package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.sh +5 -0
- package/src/compilers/support/jint/nodalis/build.bat +25 -0
- package/src/compilers/support/jint/nodalis/build.sh +31 -0
- package/src/compilers/support/nodejs/IOClient.js +110 -0
- package/src/compilers/support/nodejs/modbus.js +115 -0
- package/src/compilers/support/nodejs/nodalis.js +662 -0
- package/src/compilers/support/nodejs/opcua.js +194 -0
- package/src/nodalis.js +174 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { IOClient } from './IOClient.js';
|
|
2
|
+
import {
|
|
3
|
+
OPCUAClient,
|
|
4
|
+
AttributeIds,
|
|
5
|
+
DataType,
|
|
6
|
+
StatusCodes,
|
|
7
|
+
Variant,
|
|
8
|
+
OPCUAServer
|
|
9
|
+
} from 'node-opcua';
|
|
10
|
+
|
|
11
|
+
export class OPCClient extends IOClient {
|
|
12
|
+
constructor(endpointUrl) {
|
|
13
|
+
super('OPCUA');
|
|
14
|
+
this.endpointUrl = endpointUrl;
|
|
15
|
+
this.client = OPCUAClient.create({ endpoint_must_exist: false });
|
|
16
|
+
this.session = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async connect() {
|
|
20
|
+
try {
|
|
21
|
+
if(this.moduleID.length > 0){
|
|
22
|
+
this.endpointUrl = this.moduleID;
|
|
23
|
+
}
|
|
24
|
+
console.log(`Connecting to OPC UA server at ${this.endpointUrl}`);
|
|
25
|
+
await this.client.connect(this.endpointUrl);
|
|
26
|
+
this.session = await this.client.createSession();
|
|
27
|
+
this.connected = true;
|
|
28
|
+
console.log("OPC UA connected.");
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error("OPC UA connection error:", err);
|
|
31
|
+
this.connected = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getNodeId(remote) {
|
|
36
|
+
// Expects node format: "SW2" -> "ns=1;s=SW2"
|
|
37
|
+
return `ns=1;s=${remote}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async readValue(remote, dataType, callback) {
|
|
41
|
+
if (!this.connected) return callback(null);
|
|
42
|
+
try {
|
|
43
|
+
const nodeId = this.getNodeId(remote);
|
|
44
|
+
const result = await this.session.read({
|
|
45
|
+
nodeId,
|
|
46
|
+
attributeId: AttributeIds.Value
|
|
47
|
+
});
|
|
48
|
+
callback(result.value.value);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(`Read error [${remote}]`, err);
|
|
51
|
+
callback(null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async writeValue(remote, value, dataType) {
|
|
56
|
+
if (!this.connected) return;
|
|
57
|
+
try {
|
|
58
|
+
const nodeId = this.getNodeId(remote);
|
|
59
|
+
const variant = new Variant({ dataType, value });
|
|
60
|
+
const statusCode = await this.session.write({
|
|
61
|
+
nodeId,
|
|
62
|
+
attributeId: AttributeIds.Value,
|
|
63
|
+
value: { value: variant }
|
|
64
|
+
});
|
|
65
|
+
if (statusCode !== StatusCodes.Good) {
|
|
66
|
+
console.warn(`Write to ${remote} failed: ${statusCode.toString()}`);
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`Write error [${remote}]`, err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
readBit(remote, callback) {
|
|
74
|
+
this.readValue(remote, DataType.Boolean, callback);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeBit(remote, value) {
|
|
78
|
+
this.writeValue(remote, !!value, DataType.Boolean);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
readByte(remote, callback) {
|
|
82
|
+
this.readValue(remote, DataType.Byte, callback);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
writeByte(remote, value) {
|
|
86
|
+
this.writeValue(remote, value, DataType.Byte);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
readWord(remote, callback) {
|
|
90
|
+
this.readValue(remote, DataType.UInt16, callback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeWord(remote, value) {
|
|
94
|
+
this.writeValue(remote, value, DataType.UInt16);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
readDWord(remote, callback) {
|
|
98
|
+
this.readValue(remote, DataType.UInt32, callback);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeDWord(remote, value) {
|
|
102
|
+
this.writeValue(remote, value, DataType.UInt32);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export class OPCServer {
|
|
108
|
+
constructor() {
|
|
109
|
+
this.server = new OPCUAServer({
|
|
110
|
+
port: 4840,
|
|
111
|
+
resourcePath: "/UA/Nodalis",
|
|
112
|
+
buildInfo: {
|
|
113
|
+
productName: "NodalisServer",
|
|
114
|
+
buildNumber: "1",
|
|
115
|
+
buildDate: new Date()
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.addressMap = new Map(); // maps nodeId -> address string
|
|
120
|
+
this.readCallback = null;
|
|
121
|
+
this.writeCallback = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setReadWriteHandlers(readHandler, writeHandler) {
|
|
125
|
+
this.readCallback = readHandler;
|
|
126
|
+
this.writeCallback = writeHandler;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async initialize() {
|
|
130
|
+
await this.server.initialize();
|
|
131
|
+
const addressSpace = this.server.engine.addressSpace;
|
|
132
|
+
this.namespace = addressSpace.getOwnNamespace();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async start() {
|
|
136
|
+
await this.initialize();
|
|
137
|
+
await this.server.start();
|
|
138
|
+
console.log("OPCUA Server is now listening at", this.server.endpoints[0].endpointDescriptions()[0].endpointUrl);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async stop() {
|
|
142
|
+
await this.server.shutdown(1000);
|
|
143
|
+
console.log("OPCUA Server has shut down.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
mapVariable(varname, addr) {
|
|
147
|
+
const nodeId = `ns=1;s=${varname}`;
|
|
148
|
+
this.addressMap.set(nodeId, addr);
|
|
149
|
+
|
|
150
|
+
this.namespace.addVariable({
|
|
151
|
+
organizedBy: this.server.engine.addressSpace.rootFolder.objects,
|
|
152
|
+
nodeId: nodeId,
|
|
153
|
+
browseName: varname,
|
|
154
|
+
dataType: this._guessDataType(addr),
|
|
155
|
+
value: {
|
|
156
|
+
get: () => this._read(addr),
|
|
157
|
+
set: (variant) => this._write(addr, variant)
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_read(addr) {
|
|
163
|
+
if (!this.readCallback) return new Variant({ dataType: DataType.Boolean, value: false });
|
|
164
|
+
const val = this.readCallback(addr);
|
|
165
|
+
return new Variant(this._valueToVariant(val));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_write(addr, variant) {
|
|
169
|
+
if (!this.writeCallback) return StatusCodes.BadNotWritable;
|
|
170
|
+
this.writeCallback(addr, variant.value);
|
|
171
|
+
return StatusCodes.Good;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_guessDataType(addr) {
|
|
175
|
+
if (addr.includes(".")) return DataType.Boolean;
|
|
176
|
+
switch (addr[2]) {
|
|
177
|
+
case 'X': return DataType.Byte;
|
|
178
|
+
case 'W': return DataType.UInt16;
|
|
179
|
+
case 'D': return DataType.UInt32;
|
|
180
|
+
default: return DataType.Boolean;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_valueToVariant(value) {
|
|
185
|
+
const type = typeof value;
|
|
186
|
+
if (type === "boolean") return { dataType: DataType.Boolean, value };
|
|
187
|
+
if (type === "number") {
|
|
188
|
+
if (value <= 255) return { dataType: DataType.Byte, value };
|
|
189
|
+
if (value <= 65535) return { dataType: DataType.UInt16, value };
|
|
190
|
+
return { dataType: DataType.UInt32, value };
|
|
191
|
+
}
|
|
192
|
+
return { dataType: DataType.Null, value: null };
|
|
193
|
+
}
|
|
194
|
+
}
|
package/src/nodalis.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable curly */
|
|
3
|
+
/* eslint-disable eqeqeq */
|
|
4
|
+
// Copyright [2025] Nathan Skipper
|
|
5
|
+
//
|
|
6
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
// you may not use this file except in compliance with the License.
|
|
8
|
+
// You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
// See the License for the specific language governing permissions and
|
|
16
|
+
// limitations under the License.
|
|
17
|
+
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
|
|
22
|
+
// Updated compiler imports
|
|
23
|
+
import { CPPCompiler } from './compilers/CPPCompiler.js';
|
|
24
|
+
import { JSCompiler } from './compilers/JSCompiler.js';
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = path.dirname(__filename);
|
|
28
|
+
|
|
29
|
+
const availableCompilers = [
|
|
30
|
+
new CPPCompiler(),
|
|
31
|
+
new JSCompiler(),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function validateFileExtension(language, sourcePath) {
|
|
35
|
+
const ext = path.extname(sourcePath).toLowerCase();
|
|
36
|
+
if (language === 'st') {
|
|
37
|
+
if (ext !== '.st' && ext !== '.iec') {
|
|
38
|
+
throw new Error(`Invalid file extension for language 'st'. Expected '.st' or '.iec', got '${ext}'`);
|
|
39
|
+
}
|
|
40
|
+
} else if (language === 'ld') {
|
|
41
|
+
if (ext !== '.iec') {
|
|
42
|
+
throw new Error(`Invalid file extension for language 'ld'. Expected '.iec', got '${ext}'`);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(`Unknown language: ${language}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class Nodalis {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.compilers = availableCompilers;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
listCompilers() {
|
|
55
|
+
return this.compilers.map(c => ({
|
|
56
|
+
name: c.constructor.name,
|
|
57
|
+
supportedTargets: c.supportedTargetDevices,
|
|
58
|
+
supportedOutputTypes: c.supportedOutputTypes,
|
|
59
|
+
supportedLanguages: c.supportedLanguages,
|
|
60
|
+
supportedProtocols: c.supportedProtocols,
|
|
61
|
+
compilerVersion: c.compilerVersion,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getCompiler(target, outputType, language) {
|
|
66
|
+
return this.compilers.find(c =>
|
|
67
|
+
c.supportedTargetDevices.includes(target) &&
|
|
68
|
+
c.supportedOutputTypes.includes(outputType) &&
|
|
69
|
+
c.supportedLanguages.includes(language.toUpperCase())
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async compile({ target, outputType, outputPath, resourceName, sourcePath, language }) {
|
|
74
|
+
validateFileExtension(language, sourcePath);
|
|
75
|
+
|
|
76
|
+
const compiler = this.getCompiler(target, outputType, language);
|
|
77
|
+
if (!compiler) {
|
|
78
|
+
throw new Error(`No compiler found for target "${target}", outputType "${outputType}", and language "${language}"`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
compiler.options = {
|
|
82
|
+
sourcePath,
|
|
83
|
+
outputPath,
|
|
84
|
+
resourceName,
|
|
85
|
+
target,
|
|
86
|
+
outputType,
|
|
87
|
+
language,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await compiler.compile();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// === CLI Entry Point ===
|
|
95
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
96
|
+
|
|
97
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
98
|
+
console.log(`
|
|
99
|
+
Usage:
|
|
100
|
+
node nodalis.js --action <action> [options]
|
|
101
|
+
|
|
102
|
+
Actions:
|
|
103
|
+
--action list-compilers
|
|
104
|
+
Lists all available compilers and their supported targets, languages, protocols, and versions.
|
|
105
|
+
|
|
106
|
+
--action compile
|
|
107
|
+
Required options:
|
|
108
|
+
--target Target platform (e.g. nodejs, generic-cpp)
|
|
109
|
+
--outputType Output type (e.g. code, executable)
|
|
110
|
+
--outputPath Directory to write the result
|
|
111
|
+
--resourceName Resource name (used for .iec projects)
|
|
112
|
+
--sourcePath Path to source file (.st or .iec)
|
|
113
|
+
--language st (Structured Text) or ld (Ladder Diagram)
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
node nodalis.js --action list-compilers
|
|
117
|
+
|
|
118
|
+
node nodalis.js --action compile \\
|
|
119
|
+
--target nodejs \\
|
|
120
|
+
--outputType code \\
|
|
121
|
+
--outputPath ./out \\
|
|
122
|
+
--resourceName MyPLC \\
|
|
123
|
+
--sourcePath ./examples/pump.iec \\
|
|
124
|
+
--language st
|
|
125
|
+
`);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const args = process.argv.slice(2);
|
|
130
|
+
const argMap = {};
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
133
|
+
const key = args[i].replace(/^--/, '');
|
|
134
|
+
const value = args[i + 1];
|
|
135
|
+
argMap[key] = value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const app = new Nodalis();
|
|
139
|
+
|
|
140
|
+
switch (argMap.action) {
|
|
141
|
+
case 'list-compilers': {
|
|
142
|
+
const list = app.listCompilers();
|
|
143
|
+
console.log(JSON.stringify(list, null, 2));
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case 'compile': {
|
|
148
|
+
app.compile({
|
|
149
|
+
target: argMap.target,
|
|
150
|
+
outputType: argMap.outputType,
|
|
151
|
+
outputPath: argMap.outputPath,
|
|
152
|
+
resourceName: argMap.resourceName,
|
|
153
|
+
sourcePath: argMap.sourcePath,
|
|
154
|
+
language: argMap.language,
|
|
155
|
+
}).then(() => {
|
|
156
|
+
console.log('Compilation completed.');
|
|
157
|
+
}).catch(err => {
|
|
158
|
+
console.error(`Compilation failed: ${err.message}`);
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'deploy': {
|
|
164
|
+
console.log(`Deploy action is not yet implemented.`);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
default: {
|
|
169
|
+
console.error(`Unknown or missing action: ${argMap.action}`);
|
|
170
|
+
console.error(`Valid actions: list-compilers, compile, deploy`);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|