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,16 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<TargetFramework>netstandard2.1</TargetFramework>
|
|
5
|
+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
6
|
+
</PropertyGroup>
|
|
7
|
+
<PropertyGroup>
|
|
8
|
+
<LangVersion>latest</LangVersion>
|
|
9
|
+
</PropertyGroup>
|
|
10
|
+
<ItemGroup>
|
|
11
|
+
<PackageReference Include="Jint" Version="3.0.0-beta-2031" />
|
|
12
|
+
<PackageReference Include="System.Text.Json" Version="8.0.3" />
|
|
13
|
+
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.368.49" />
|
|
14
|
+
</ItemGroup>
|
|
15
|
+
|
|
16
|
+
</Project>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Copyright [2025] Nathan Skipper
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// OPC client implementation for .NET
|
|
17
|
+
/// </summary>
|
|
18
|
+
|
|
19
|
+
using Opc.Ua;
|
|
20
|
+
using Opc.Ua.Client;
|
|
21
|
+
using Opc.Ua.Configuration;
|
|
22
|
+
using System;
|
|
23
|
+
using System.Collections.Generic;
|
|
24
|
+
using System.Threading.Tasks;
|
|
25
|
+
|
|
26
|
+
namespace Nodalis
|
|
27
|
+
{
|
|
28
|
+
public class OPCClient : IOClient
|
|
29
|
+
{
|
|
30
|
+
private Session? _session;
|
|
31
|
+
private ApplicationConfiguration? _config;
|
|
32
|
+
|
|
33
|
+
public OPCClient() : base("OPCUA") { }
|
|
34
|
+
|
|
35
|
+
public override void Connect()
|
|
36
|
+
{
|
|
37
|
+
Task.Run(async () =>
|
|
38
|
+
{
|
|
39
|
+
try
|
|
40
|
+
{
|
|
41
|
+
var map = mappings[0];
|
|
42
|
+
string endpointUrl = map.moduleID;
|
|
43
|
+
|
|
44
|
+
_config = new ApplicationConfiguration
|
|
45
|
+
{
|
|
46
|
+
ApplicationName = "NodalisOPCUAClient",
|
|
47
|
+
ApplicationType = ApplicationType.Client,
|
|
48
|
+
SecurityConfiguration = new SecurityConfiguration
|
|
49
|
+
{
|
|
50
|
+
ApplicationCertificate = new CertificateIdentifier(),
|
|
51
|
+
AutoAcceptUntrustedCertificates = true,
|
|
52
|
+
},
|
|
53
|
+
TransportConfigurations = new TransportConfigurationCollection(),
|
|
54
|
+
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
|
|
55
|
+
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 }
|
|
56
|
+
};
|
|
57
|
+
await _config.Validate(ApplicationType.Client);
|
|
58
|
+
|
|
59
|
+
var app = new ApplicationInstance { ApplicationName = "NodalisOPCUAClient", ApplicationType = ApplicationType.Client, ApplicationConfiguration = _config };
|
|
60
|
+
var endpoint = CoreClientUtils.SelectEndpoint(endpointUrl, false, 15000);
|
|
61
|
+
var endpointConfig = EndpointConfiguration.Create(_config);
|
|
62
|
+
var endpointDesc = new ConfiguredEndpoint(null, endpoint, endpointConfig);
|
|
63
|
+
|
|
64
|
+
_session = await Session.Create(_config, endpointDesc, false, "", 60000, null, null);
|
|
65
|
+
connected = true;
|
|
66
|
+
Console.WriteLine("OPC UA connected.");
|
|
67
|
+
}
|
|
68
|
+
catch (Exception ex)
|
|
69
|
+
{
|
|
70
|
+
Console.WriteLine("OPC UA connection error: " + ex.Message);
|
|
71
|
+
connected = false;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private NodeId GetNodeId(string remote) => new NodeId($"s={remote}", 1);
|
|
77
|
+
|
|
78
|
+
public override bool ReadBit(string address, out int result)
|
|
79
|
+
{
|
|
80
|
+
return ReadValue(address, out result, BuiltInType.Boolean);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public override bool WriteBit(string address, int value)
|
|
84
|
+
{
|
|
85
|
+
return WriteVal(address, value != 0, BuiltInType.Boolean);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public override bool ReadByte(string address, out byte result)
|
|
89
|
+
{
|
|
90
|
+
return ReadValue(address, out result, BuiltInType.Byte);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public override bool WriteByte(string address, byte value)
|
|
94
|
+
{
|
|
95
|
+
return WriteVal(address, value, BuiltInType.Byte);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public override bool ReadWord(string address, out ushort result)
|
|
99
|
+
{
|
|
100
|
+
return ReadValue(address, out result, BuiltInType.UInt16);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public override bool WriteWord(string address, ushort value)
|
|
104
|
+
{
|
|
105
|
+
return WriteVal(address, value, BuiltInType.UInt16);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public override bool ReadDWord(string address, out uint result)
|
|
109
|
+
{
|
|
110
|
+
return ReadValue(address, out result, BuiltInType.UInt32);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public override bool WriteDWord(string address, uint value)
|
|
114
|
+
{
|
|
115
|
+
return WriteVal(address, value, BuiltInType.UInt32);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private bool ReadValue<T>(string address, out T result, BuiltInType type)
|
|
119
|
+
{
|
|
120
|
+
result = default!;
|
|
121
|
+
if (!connected || _session == null) return false;
|
|
122
|
+
|
|
123
|
+
try
|
|
124
|
+
{
|
|
125
|
+
var nodeId = GetNodeId(address);
|
|
126
|
+
var value = _session.ReadValue(nodeId);
|
|
127
|
+
if (value.Value is T cast)
|
|
128
|
+
{
|
|
129
|
+
result = cast;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (Exception ex)
|
|
134
|
+
{
|
|
135
|
+
Console.WriteLine($"Read error [{address}]: {ex.Message}");
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private bool WriteVal<T>(string address, T value, BuiltInType type)
|
|
141
|
+
{
|
|
142
|
+
if (!connected || this._session == null) return false;
|
|
143
|
+
|
|
144
|
+
try
|
|
145
|
+
{
|
|
146
|
+
var writeValue = new WriteValue
|
|
147
|
+
{
|
|
148
|
+
NodeId = GetNodeId(address),
|
|
149
|
+
AttributeId = Attributes.Value,
|
|
150
|
+
Value = new DataValue(new Variant(value))
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
writeValue.Value.StatusCode = StatusCodes.Good;
|
|
154
|
+
writeValue.Value.ServerTimestamp = DateTime.MinValue;
|
|
155
|
+
writeValue.Value.SourceTimestamp = DateTime.MinValue;
|
|
156
|
+
|
|
157
|
+
var writeResults = _session.Write(null, new WriteValueCollection { writeValue }, out StatusCodeCollection statusCodes, out DiagnosticInfoCollection diag);
|
|
158
|
+
if (StatusCode.IsBad(statusCodes[0]))
|
|
159
|
+
{
|
|
160
|
+
Console.WriteLine($"Write failed for {address}: {statusCodes[0]}");
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
catch (Exception ex)
|
|
166
|
+
{
|
|
167
|
+
Console.WriteLine($"Write error [{address}]: {ex.Message}");
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Copyright [2025] Nathan Skipper
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// OPC Server implementation for .NET
|
|
17
|
+
/// </summary>
|
|
18
|
+
///
|
|
19
|
+
using Opc.Ua;
|
|
20
|
+
using Opc.Ua.Configuration;
|
|
21
|
+
using Opc.Ua.Server;
|
|
22
|
+
using System;
|
|
23
|
+
using System.Collections.Generic;
|
|
24
|
+
using System.Threading.Tasks;
|
|
25
|
+
|
|
26
|
+
namespace Nodalis
|
|
27
|
+
{
|
|
28
|
+
public class OPCServer
|
|
29
|
+
{
|
|
30
|
+
private ApplicationInstance _application;
|
|
31
|
+
private NodalisEngine _engine;
|
|
32
|
+
private Dictionary<string, string> _addressMap = new();
|
|
33
|
+
private NodalisNodeManager? _nodeManager;
|
|
34
|
+
private StandardServer? _server;
|
|
35
|
+
|
|
36
|
+
public OPCServer(NodalisEngine engine)
|
|
37
|
+
{
|
|
38
|
+
_engine = engine;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public void MapVariable(string varName, string address)
|
|
42
|
+
{
|
|
43
|
+
_addressMap[varName] = address;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async Task StartAsync()
|
|
47
|
+
{
|
|
48
|
+
_application = new ApplicationInstance
|
|
49
|
+
{
|
|
50
|
+
ApplicationName = "NodalisServer",
|
|
51
|
+
ApplicationType = ApplicationType.Server,
|
|
52
|
+
ConfigSectionName = "NodalisServer"
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
var config = new ApplicationConfiguration
|
|
56
|
+
{
|
|
57
|
+
ApplicationName = "NodalisServer",
|
|
58
|
+
ApplicationType = ApplicationType.Server,
|
|
59
|
+
ApplicationUri = "urn:localhost:NodalisServer",
|
|
60
|
+
ServerConfiguration = new ServerConfiguration
|
|
61
|
+
{
|
|
62
|
+
BaseAddresses = { "opc.tcp://localhost:4840/UA/Nodalis" }
|
|
63
|
+
},
|
|
64
|
+
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
|
|
65
|
+
SecurityConfiguration = new SecurityConfiguration
|
|
66
|
+
{
|
|
67
|
+
ApplicationCertificate = new CertificateIdentifier(),
|
|
68
|
+
AutoAcceptUntrustedCertificates = true
|
|
69
|
+
},
|
|
70
|
+
CertificateValidator = new CertificateValidator(),
|
|
71
|
+
//DiagnosticsConfiguration = new DiagnosticsConfiguration { Enabled = true },
|
|
72
|
+
Extensions = new XmlElementCollection()
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
await config.Validate(ApplicationType.Server);
|
|
76
|
+
|
|
77
|
+
_application.ApplicationConfiguration = config;
|
|
78
|
+
|
|
79
|
+
_server = new NodalisServer(_engine, _addressMap);
|
|
80
|
+
_application.Start(_server);
|
|
81
|
+
|
|
82
|
+
Console.WriteLine("OPC UA Server started at: opc.tcp://localhost:4840/UA/Nodalis");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async Task StopAsync()
|
|
86
|
+
{
|
|
87
|
+
if (_server != null)
|
|
88
|
+
{
|
|
89
|
+
_server.Stop();
|
|
90
|
+
Console.WriteLine("OPC UA Server stopped.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private class NodalisServer : StandardServer
|
|
95
|
+
{
|
|
96
|
+
private readonly NodalisEngine _engine;
|
|
97
|
+
private readonly Dictionary<string, string> _map;
|
|
98
|
+
|
|
99
|
+
public NodalisServer(NodalisEngine engine, Dictionary<string, string> map)
|
|
100
|
+
{
|
|
101
|
+
_engine = engine;
|
|
102
|
+
_map = map;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration config)
|
|
106
|
+
{
|
|
107
|
+
var nodeManagers = new List<INodeManager>
|
|
108
|
+
{
|
|
109
|
+
new NodalisNodeManager(server, config, _engine, _map)
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return new MasterNodeManager(server, config, null, nodeManagers.ToArray());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private class NodalisNodeManager : CustomNodeManager2
|
|
117
|
+
{
|
|
118
|
+
private readonly NodalisEngine _engine;
|
|
119
|
+
private readonly Dictionary<string, string> _addressMap;
|
|
120
|
+
|
|
121
|
+
public NodalisNodeManager(IServerInternal server, ApplicationConfiguration config, NodalisEngine engine, Dictionary<string, string> map)
|
|
122
|
+
: base(server, config, "http://nodalis.local/UA/")
|
|
123
|
+
{
|
|
124
|
+
_engine = engine;
|
|
125
|
+
_addressMap = map;
|
|
126
|
+
SystemContext.NodeIdFactory = this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private FolderState CreateFolder(
|
|
130
|
+
NodeState parent,
|
|
131
|
+
string path,
|
|
132
|
+
string name,
|
|
133
|
+
IDictionary<NodeId, IList<IReference>> externalReferences,
|
|
134
|
+
ushort namespaceIndex)
|
|
135
|
+
{
|
|
136
|
+
var folder = new FolderState(parent)
|
|
137
|
+
{
|
|
138
|
+
SymbolicName = name,
|
|
139
|
+
ReferenceTypeId = ReferenceTypeIds.Organizes,
|
|
140
|
+
TypeDefinitionId = ObjectTypeIds.FolderType,
|
|
141
|
+
NodeId = new NodeId(path, namespaceIndex),
|
|
142
|
+
BrowseName = new QualifiedName(name, namespaceIndex),
|
|
143
|
+
DisplayName = name,
|
|
144
|
+
EventNotifier = EventNotifiers.None
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out var references))
|
|
148
|
+
references.Add(new NodeStateReference(ReferenceTypeIds.Organizes, false, folder.NodeId));
|
|
149
|
+
else
|
|
150
|
+
externalReferences[ObjectIds.ObjectsFolder] = new List<IReference>
|
|
151
|
+
{
|
|
152
|
+
new NodeStateReference(ReferenceTypeIds.Organizes, false, folder.NodeId)
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
AddPredefinedNode(SystemContext, folder);
|
|
156
|
+
return folder;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private ServiceResult ReadValueHandler(
|
|
160
|
+
ISystemContext context,
|
|
161
|
+
NodeState node,
|
|
162
|
+
NumericRange indexRange,
|
|
163
|
+
QualifiedName name,
|
|
164
|
+
ref object value,
|
|
165
|
+
ref StatusCode statusCode,
|
|
166
|
+
ref DateTime timestamp)
|
|
167
|
+
{
|
|
168
|
+
if (node is BaseDataVariableState variable &&
|
|
169
|
+
_addressMap.TryGetValue(variable.SymbolicName, out var addr))
|
|
170
|
+
{
|
|
171
|
+
try
|
|
172
|
+
{
|
|
173
|
+
value = ReadFromEngine(addr);
|
|
174
|
+
statusCode = StatusCodes.Good;
|
|
175
|
+
timestamp = DateTime.UtcNow;
|
|
176
|
+
return ServiceResult.Good;
|
|
177
|
+
}
|
|
178
|
+
catch (Exception ex)
|
|
179
|
+
{
|
|
180
|
+
Console.WriteLine($"Read error for {addr}: {ex.Message}");
|
|
181
|
+
return StatusCodes.BadUnexpectedError;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return StatusCodes.BadNodeIdUnknown;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
|
|
189
|
+
{
|
|
190
|
+
var folder = CreateFolder(
|
|
191
|
+
null,
|
|
192
|
+
"Nodalis",
|
|
193
|
+
"Nodalis",
|
|
194
|
+
externalReferences,
|
|
195
|
+
NamespaceIndex
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
foreach (var entry in _addressMap)
|
|
199
|
+
{
|
|
200
|
+
string name = entry.Key;
|
|
201
|
+
string addr = entry.Value;
|
|
202
|
+
var dataType = GetDataType(addr);
|
|
203
|
+
|
|
204
|
+
var variable = new BaseDataVariableState(folder)
|
|
205
|
+
{
|
|
206
|
+
SymbolicName = name,
|
|
207
|
+
NodeId = new NodeId(name, NamespaceIndex),
|
|
208
|
+
BrowseName = new QualifiedName(name, NamespaceIndex),
|
|
209
|
+
DisplayName = name,
|
|
210
|
+
DataType = dataType,
|
|
211
|
+
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
|
|
212
|
+
ValueRank = ValueRanks.Scalar,
|
|
213
|
+
AccessLevel = AccessLevels.CurrentReadOrWrite,
|
|
214
|
+
UserAccessLevel = AccessLevels.CurrentReadOrWrite
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Define read delegate
|
|
218
|
+
variable.OnReadValue = ReadValueHandler;
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
// Define write delegate
|
|
222
|
+
variable.OnSimpleWriteValue = (ISystemContext context, NodeState node, ref object val) =>
|
|
223
|
+
{
|
|
224
|
+
try
|
|
225
|
+
{
|
|
226
|
+
WriteToEngine(addr, val);
|
|
227
|
+
return ServiceResult.Good;
|
|
228
|
+
}
|
|
229
|
+
catch (Exception ex)
|
|
230
|
+
{
|
|
231
|
+
Console.WriteLine($"Write error for {addr}: {ex.Message}");
|
|
232
|
+
return StatusCodes.BadUnexpectedError;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
AddPredefinedNode(SystemContext, variable);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private object ReadFromEngine(string address)
|
|
241
|
+
{
|
|
242
|
+
if (address.Contains(".")) return _engine.ReadBit(address);
|
|
243
|
+
else if (address.Contains("X")) return _engine.ReadByte(address);
|
|
244
|
+
else if (address.Contains("W")) return _engine.ReadWord(address);
|
|
245
|
+
else if (address.Contains("D")) return _engine.ReadDWord(address);
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private void WriteToEngine(string address, object value)
|
|
250
|
+
{
|
|
251
|
+
switch (value)
|
|
252
|
+
{
|
|
253
|
+
case bool b: _engine.WriteBit(address, b); break;
|
|
254
|
+
case byte bt: _engine.WriteByte(address, bt); break;
|
|
255
|
+
case ushort us: _engine.WriteWord(address, us); break;
|
|
256
|
+
case uint ui: _engine.WriteDWord(address, ui); break;
|
|
257
|
+
default: throw new InvalidCastException($"Unsupported value type: {value?.GetType()?.Name}");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
private NodeId GetDataType(string address)
|
|
263
|
+
{
|
|
264
|
+
if (address.Contains(".")) return DataTypeIds.Boolean;
|
|
265
|
+
else if (address.Contains("X")) return DataTypeIds.Byte;
|
|
266
|
+
else if (address.Contains("W")) return DataTypeIds.UInt16;
|
|
267
|
+
else if (address.Contains("D")) return DataTypeIds.UInt32;
|
|
268
|
+
return DataTypeIds.Boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<OutputType>Exe</OutputType>
|
|
5
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
6
|
+
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
|
7
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
8
|
+
<Nullable>enable</Nullable>
|
|
9
|
+
</PropertyGroup>
|
|
10
|
+
|
|
11
|
+
<ItemGroup>
|
|
12
|
+
<PackageReference Include="Jint" Version="3.0.0-beta-2031" />
|
|
13
|
+
<PackageReference Include="System.Text.Json" Version="8.0.3" />
|
|
14
|
+
</ItemGroup>
|
|
15
|
+
<ItemGroup>
|
|
16
|
+
<ProjectReference Include="..\\NodalisEngine\\NodalisEngine.csproj" />
|
|
17
|
+
</ItemGroup>
|
|
18
|
+
|
|
19
|
+
</Project>
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Copyright [2025] Nathan Skipper
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Nodalis PLC .NET Console app
|
|
17
|
+
/// </summary>
|
|
18
|
+
|
|
19
|
+
using System;
|
|
20
|
+
using System.IO;
|
|
21
|
+
using System.Threading;
|
|
22
|
+
using Jint;
|
|
23
|
+
using Jint.Runtime;
|
|
24
|
+
using Nodalis;
|
|
25
|
+
using System.Text.RegularExpressions;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProgramEngine : NodalisEngine
|
|
29
|
+
{
|
|
30
|
+
private static readonly ulong[,] MEMORY = new ulong[64, 16];
|
|
31
|
+
|
|
32
|
+
public override bool ReadBit(string address)
|
|
33
|
+
{
|
|
34
|
+
var mem = ParseAddress(address);
|
|
35
|
+
return (GetMemoryCell(mem[0], mem[2]) & (1UL << (mem[3] % 64))) != 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public override byte ReadByte(string address)
|
|
39
|
+
{
|
|
40
|
+
var mem = ParseAddress(address);
|
|
41
|
+
return (byte)((GetMemoryCell(mem[0], mem[2]) >> (mem[3] % 64)) & 0xFF);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public override ushort ReadWord(string address)
|
|
45
|
+
{
|
|
46
|
+
var mem = ParseAddress(address);
|
|
47
|
+
return (ushort)((GetMemoryCell(mem[0], mem[2]) >> (mem[3] % 64)) & 0xFFFF);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public override uint ReadDWord(string address)
|
|
51
|
+
{
|
|
52
|
+
var mem = ParseAddress(address);
|
|
53
|
+
return (uint)((GetMemoryCell(mem[0], mem[2]) >> (mem[3] % 64)) & 0xFFFFFFFF);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public override void WriteBit(string address, bool value)
|
|
57
|
+
{
|
|
58
|
+
var mem = ParseAddress(address);
|
|
59
|
+
if (value)
|
|
60
|
+
GetMemoryCell(mem[0], mem[2]) |= (1UL << (mem[3] % 64));
|
|
61
|
+
else
|
|
62
|
+
GetMemoryCell(mem[0], mem[2]) &= ~(1UL << (mem[3] % 64));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public override void WriteByte(string address, byte value)
|
|
66
|
+
{
|
|
67
|
+
WriteGeneric(address, value, 8);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public override void WriteWord(string address, ushort value)
|
|
71
|
+
{
|
|
72
|
+
WriteGeneric(address, value, 16);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public override void WriteDWord(string address, uint value)
|
|
76
|
+
{
|
|
77
|
+
WriteGeneric(address, value, 32);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public enum MemorySpace
|
|
81
|
+
{
|
|
82
|
+
I = 0,
|
|
83
|
+
Q = 1,
|
|
84
|
+
M = 2
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public static ref ulong GetMemoryCell(int space, int addr)
|
|
88
|
+
{
|
|
89
|
+
int r = -1, c = 0, b = 0;
|
|
90
|
+
switch (space)
|
|
91
|
+
{
|
|
92
|
+
case 1: // Q
|
|
93
|
+
r = (addr * 8) / 64;
|
|
94
|
+
c = 1;
|
|
95
|
+
b = addr % 8;
|
|
96
|
+
break;
|
|
97
|
+
case 0: // I
|
|
98
|
+
r = (addr * 8) / 64;
|
|
99
|
+
c = 0;
|
|
100
|
+
b = addr % 8;
|
|
101
|
+
break;
|
|
102
|
+
case 2: // M
|
|
103
|
+
r = (addr * 8) / (64 * 14);
|
|
104
|
+
c = (addr / 112) + 2;
|
|
105
|
+
b = addr % 8;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (r >= 0 && r < 64 && c >= 0 && c < 16)
|
|
110
|
+
{
|
|
111
|
+
return ref MEMORY[r, c];
|
|
112
|
+
}
|
|
113
|
+
throw new ArgumentOutOfRangeException();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public static List<int> ParseAddress(string address)
|
|
117
|
+
{
|
|
118
|
+
var pattern = new Regex(@"%([IQM])([XBWDL])(\d+)(?:\.(\d+))?", RegexOptions.IgnoreCase);
|
|
119
|
+
var match = pattern.Match(address);
|
|
120
|
+
|
|
121
|
+
if (!match.Success)
|
|
122
|
+
throw new ArgumentException($"Invalid address format: {address}");
|
|
123
|
+
|
|
124
|
+
string space = match.Groups[1].Value.ToUpperInvariant(); // I, Q, M
|
|
125
|
+
string type = match.Groups[2].Value.ToUpperInvariant(); // X, W, D, etc.
|
|
126
|
+
string index = match.Groups[3].Value; // 0, 1, ...
|
|
127
|
+
string bit = match.Groups[4].Success ? match.Groups[4].Value : null;
|
|
128
|
+
|
|
129
|
+
int ispace = space switch
|
|
130
|
+
{
|
|
131
|
+
"M" => (int)MemorySpace.M,
|
|
132
|
+
"Q" => (int)MemorySpace.Q,
|
|
133
|
+
"I" => (int)MemorySpace.I,
|
|
134
|
+
_ => throw new ArgumentException($"Unknown memory space: {space}")
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
int width = type switch
|
|
138
|
+
{
|
|
139
|
+
"X" => 8,
|
|
140
|
+
"W" => 16,
|
|
141
|
+
"D" => 32,
|
|
142
|
+
_ => throw new ArgumentException($"Unknown type: {type}")
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
int addr = int.Parse(index);
|
|
146
|
+
int ibit = bit != null ? int.Parse(bit) : -1;
|
|
147
|
+
|
|
148
|
+
return new List<int> { ispace, width, addr, ibit };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private void WriteGeneric(string address, ulong value, int width)
|
|
152
|
+
{
|
|
153
|
+
var mem = ParseAddress(address);
|
|
154
|
+
ulong mask = (1UL << width) - 1;
|
|
155
|
+
ulong shiftedMask = mask << (mem[3] % 64);
|
|
156
|
+
ulong location = GetMemoryCell(mem[0], mem[2]);
|
|
157
|
+
location &= ~shiftedMask;
|
|
158
|
+
location |= (value & mask) << (mem[3] % 64);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class Program
|
|
163
|
+
{
|
|
164
|
+
static void Main(string[] args)
|
|
165
|
+
{
|
|
166
|
+
if (args.Length < 1)
|
|
167
|
+
{
|
|
168
|
+
Console.WriteLine("Usage: NodalisRuntime <jsfile>");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
var engine = new ProgramEngine();
|
|
173
|
+
long lastExec = engine.ElapsedMilliseconds;
|
|
174
|
+
string jsCode = File.ReadAllText(args[0]);
|
|
175
|
+
try
|
|
176
|
+
{
|
|
177
|
+
engine.Load(jsCode);
|
|
178
|
+
engine.Setup();
|
|
179
|
+
while (true)
|
|
180
|
+
{
|
|
181
|
+
engine.SuperviseIO();
|
|
182
|
+
if (engine.ElapsedMilliseconds - lastExec >= 100)
|
|
183
|
+
{
|
|
184
|
+
lastExec = engine.ElapsedMilliseconds;
|
|
185
|
+
engine.Execute();
|
|
186
|
+
}
|
|
187
|
+
Thread.Sleep(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (Exception ex)
|
|
191
|
+
{
|
|
192
|
+
Console.WriteLine(ex.ToString());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|