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,798 @@
|
|
|
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
|
+
/**
|
|
16
|
+
* @description Nodalis PLC Header
|
|
17
|
+
* @author Nathan Skipper, MTI
|
|
18
|
+
* @version 1.0.2
|
|
19
|
+
* @copyright Apache 2.0
|
|
20
|
+
*/
|
|
21
|
+
#pragma once
|
|
22
|
+
#include <iostream>
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <string>
|
|
25
|
+
#include <cctype>
|
|
26
|
+
#include <chrono>
|
|
27
|
+
#include <type_traits> // for std::is_same
|
|
28
|
+
#include <math.h>
|
|
29
|
+
#include <vector>
|
|
30
|
+
#include <regex>
|
|
31
|
+
#include <stdexcept>
|
|
32
|
+
#define JSON_USE_IMPLICIT_CONVERSIONS 1
|
|
33
|
+
#define JSON_USE_WIDE_STRING 1
|
|
34
|
+
#include "json.hpp"
|
|
35
|
+
using json = nlohmann::json;
|
|
36
|
+
|
|
37
|
+
#pragma region "Program Timing"
|
|
38
|
+
extern uint64_t PROGRAM_COUNT;
|
|
39
|
+
extern std::chrono::steady_clock::time_point PROGRAM_START;
|
|
40
|
+
/**
|
|
41
|
+
* Provides the number of milliseconds since the program started.
|
|
42
|
+
* @returns Returns a ulong of the elapsed time, in milliseconds.
|
|
43
|
+
*/
|
|
44
|
+
uint64_t elapsed();
|
|
45
|
+
#pragma endregion
|
|
46
|
+
#pragma region "Memory Handling"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Defines the total memory block for this PLC. This memory is a grid of 64x16 "sheets" or pages of memory.
|
|
50
|
+
* The first column is the row of 64, the second column is 16 registers for that sheet.
|
|
51
|
+
* Addresses are reserved based on MTI's standard registers, AI (physical inputs), AO (physical outputs),
|
|
52
|
+
* SW (switch inputs from HMIs), LD (LED outputs to HMIs), BI, BO, CI, CO (all free memory locations for logical operations)
|
|
53
|
+
* PROTECT, BREACH, TROUBLE, STAT1, STAT2, MISC1, MISC2, MISC3.
|
|
54
|
+
* Standard IEC address references break down in the following ways:
|
|
55
|
+
* %I - corresponds to the AI register MEMORY[x][0]. If requesting %IX0, the sheet row would be calculated by r = floor((0*8)/64) - which would yield 0.
|
|
56
|
+
* To reference each individual byte, we would get uint8_t* bytes = &MEMORY[r][0]; We then can reference the byte within this row by getting b = 0 % 8;
|
|
57
|
+
* We can then get the value of that byte by referencing bytes[b];
|
|
58
|
+
* %Q - Same as %I, except uint8_t* bytes = &MEMORY[r][1];
|
|
59
|
+
* %M - Virtual memory used for program interface. This takes up the other 14 columns in a row. A reference to %MX[a], where a is a numerical byte address would be used to calculate
|
|
60
|
+
* r = floor((a*8)/(64*14)). If a is 0, this would yield row 0. If a is 112, it would yield 1. The column would be c = floor(a/(8*14)) + 2, so 0 would yield 2 and 112 would also yield 2.
|
|
61
|
+
* The byte would be obtained by b = a % 8, so 0 would yield 0, and 112 would yield 0.
|
|
62
|
+
*/
|
|
63
|
+
extern uint64_t MEMORY[64][16];
|
|
64
|
+
|
|
65
|
+
inline std::string toLowerCase(const std::string& input) {
|
|
66
|
+
std::string result = input;
|
|
67
|
+
for (size_t i = 0; i < result.size(); ++i) {
|
|
68
|
+
result[i] = std::tolower(result[i]);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Defines the memory space designations for use in getting memory addresses.
|
|
75
|
+
*/
|
|
76
|
+
enum MEMORY_SPACE : int {
|
|
77
|
+
I, //input memory space
|
|
78
|
+
Q, //output memory space
|
|
79
|
+
M, //Virtual memory space
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses a ST address reference into a vector with the memory space, type, byte index, and bit broken out.
|
|
84
|
+
* @param address A string representing the ST address.
|
|
85
|
+
* @returns Returns a vector with four elements: the memory space (Input, Output, or Virtual), the width in bits, the address index, and the bit.
|
|
86
|
+
*/
|
|
87
|
+
inline std::vector<int> parseAddress(const std::string& address) {
|
|
88
|
+
std::regex pattern(R"(%([IQM])([XBWDL])(\d+)(?:\.(\d+))?)", std::regex::icase);
|
|
89
|
+
std::smatch match;
|
|
90
|
+
|
|
91
|
+
if (std::regex_match(address, match, pattern)) {
|
|
92
|
+
std::string space = match[1].str(); // I, Q, M
|
|
93
|
+
std::string type = match[2].str(); // X, W, D, etc.
|
|
94
|
+
std::string index = match[3].str(); // 0, 1, ...
|
|
95
|
+
std::string bit = match[4].matched ? match[4].str() : ""; // bit if present
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
int ispace = -1;
|
|
99
|
+
int addr = -1;
|
|
100
|
+
int ibit = -1;
|
|
101
|
+
int width = -1;
|
|
102
|
+
if(toLowerCase(space) == "m"){
|
|
103
|
+
ispace = MEMORY_SPACE::M;
|
|
104
|
+
}
|
|
105
|
+
else if(toLowerCase(space) == "q"){
|
|
106
|
+
ispace = MEMORY_SPACE::Q;
|
|
107
|
+
}
|
|
108
|
+
else if(toLowerCase(space) == "i"){
|
|
109
|
+
ispace = MEMORY_SPACE::I;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if(toLowerCase(type) == "x"){
|
|
113
|
+
width = 8;
|
|
114
|
+
}
|
|
115
|
+
else if(toLowerCase(type) == "w"){
|
|
116
|
+
width = 16;
|
|
117
|
+
}
|
|
118
|
+
else if(toLowerCase(type) == "d"){
|
|
119
|
+
width = 32;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if(bit != ""){
|
|
123
|
+
ibit = std::stoi(bit);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
addr = std::stoi(index);
|
|
128
|
+
return {ispace, width, addr, ibit};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw std::invalid_argument("Invalid address format: " + address);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Gets a byte pointer to a memory address in a certain memory space.
|
|
136
|
+
* @param space The memory space from which to get the address
|
|
137
|
+
* @param addr The byte index to pull from.
|
|
138
|
+
* @returns Returns a byte pointer to the memory address, or 0 if there is no memory at the given address.
|
|
139
|
+
*/
|
|
140
|
+
inline uint8_t* getMemoryByte(int space, int addr){
|
|
141
|
+
uint8_t* ret = 0;
|
|
142
|
+
int r = -1, c = 0, b = 0;
|
|
143
|
+
switch(space){
|
|
144
|
+
case MEMORY_SPACE::Q:
|
|
145
|
+
r = floor((addr*8)/64);
|
|
146
|
+
c = 1;
|
|
147
|
+
b = addr % 8;
|
|
148
|
+
break;
|
|
149
|
+
case MEMORY_SPACE::I:
|
|
150
|
+
r = floor((addr*8)/64);
|
|
151
|
+
c = 0;
|
|
152
|
+
b = addr % 8;
|
|
153
|
+
break;
|
|
154
|
+
case MEMORY_SPACE::M:
|
|
155
|
+
r = floor((addr*8)/(64*14));
|
|
156
|
+
c = floor(addr/112) + 2;
|
|
157
|
+
b = addr % 8;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
if(r >= 0){
|
|
161
|
+
ret = (uint8_t*)(&MEMORY[r][c]) + b;
|
|
162
|
+
}
|
|
163
|
+
return ret;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Gets a word pointer to a memory address in a certain memory space.
|
|
167
|
+
* @param space The memory space from which to get the address.
|
|
168
|
+
* @param addr The word index to pull from.
|
|
169
|
+
* @returns Returns a word pointer to a memory address, or 0 if there is no memory at the given address.
|
|
170
|
+
*/
|
|
171
|
+
inline uint16_t* getMemoryWord(int space, int addr){
|
|
172
|
+
return (uint16_t*)getMemoryByte(space, addr * 2);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Gets a double word pointer to a memory address in a certain memory space.
|
|
176
|
+
* @param space The memory space from which to get the address
|
|
177
|
+
* @param addr The double word index to pull from.
|
|
178
|
+
* @return Returns a double word pointer to a memory address, or 0 if there is no memory at the given address.
|
|
179
|
+
*/
|
|
180
|
+
inline uint32_t* getMemoryDWord(int space, int addr){
|
|
181
|
+
return (uint32_t*) getMemoryByte(space, addr*4);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reads the 32 bit value at a given address.
|
|
186
|
+
* @param address The address of the memory to get the 32 bit value from.
|
|
187
|
+
* @returns Returns a 32 bit value
|
|
188
|
+
*/
|
|
189
|
+
uint32_t readDWord(std::string address);
|
|
190
|
+
/**
|
|
191
|
+
* Reads the 16 bit value at a given address.
|
|
192
|
+
* @param address The address of the memory to get the 16 bit value from.
|
|
193
|
+
* @returns Returns a 16 bit value
|
|
194
|
+
*/
|
|
195
|
+
uint16_t readWord(std::string address);
|
|
196
|
+
/**
|
|
197
|
+
* Reads the 8 bit value at a given address.
|
|
198
|
+
* @param address The address of the memory to get the 8 bit value from.
|
|
199
|
+
* @returns Returns a 8 bit value
|
|
200
|
+
*/
|
|
201
|
+
uint8_t readByte(std::string address);
|
|
202
|
+
/**
|
|
203
|
+
* Reads the bit value at a given address.
|
|
204
|
+
* @param address The address of the memory to get the bit value from.
|
|
205
|
+
* @returns Returns a boolean value indicating the status of the bit.
|
|
206
|
+
*/
|
|
207
|
+
bool readBit(std::string address);
|
|
208
|
+
/**
|
|
209
|
+
* Writes a 32 bit value to an address in memory.
|
|
210
|
+
* @param address The address of memory to write to.
|
|
211
|
+
* @param value The 32 bit value to write to memory.
|
|
212
|
+
*/
|
|
213
|
+
void writeDWord(std::string address, uint32_t value);
|
|
214
|
+
/**
|
|
215
|
+
* Writes a 16 bit value to an address in memory.
|
|
216
|
+
* @param address The address of memory to write to.
|
|
217
|
+
* @param value The 16 bit value to write to memory.
|
|
218
|
+
*/
|
|
219
|
+
void writeWord(std::string address, uint16_t value);
|
|
220
|
+
/**
|
|
221
|
+
* Writes a 8 bit value to an address in memory.
|
|
222
|
+
* @param address The address of memory to write to.
|
|
223
|
+
* @param value The 8 bit value to write to memory.
|
|
224
|
+
*/
|
|
225
|
+
void writeByte(std::string address, uint8_t value);
|
|
226
|
+
/**
|
|
227
|
+
* Writes a bit value to an address in memory.
|
|
228
|
+
* @param address The address of memory to write to.
|
|
229
|
+
* @param value The bit value to write to memory.
|
|
230
|
+
*/
|
|
231
|
+
void writeBit(std::string address, bool value);
|
|
232
|
+
/**
|
|
233
|
+
* Gets the bit value from a variable
|
|
234
|
+
* @param var A pointer to the variable from which to get the bit.
|
|
235
|
+
* @param bit The number of the bit to get
|
|
236
|
+
* @returns Returns the state of the bit.
|
|
237
|
+
*/
|
|
238
|
+
bool getBit(void* var, int bit);
|
|
239
|
+
/**
|
|
240
|
+
* Sets the bit in a variable.
|
|
241
|
+
* @param var A pointer to the variable to which to set the bit.
|
|
242
|
+
* @param bit The bit to set.
|
|
243
|
+
* @param value The state to set the bit to.
|
|
244
|
+
*/
|
|
245
|
+
void setBit(void* var, int bit, bool value);
|
|
246
|
+
#pragma endregion
|
|
247
|
+
#pragma region "Reference Handling"
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The RefVar class provides a means of declaring a variable with a reference to memory, similar to a pointer.
|
|
251
|
+
*/
|
|
252
|
+
template<typename T>
|
|
253
|
+
class RefVar {
|
|
254
|
+
private:
|
|
255
|
+
/**
|
|
256
|
+
* The address of the memory.
|
|
257
|
+
*/
|
|
258
|
+
std::string address;
|
|
259
|
+
/**
|
|
260
|
+
* The cached value of the address
|
|
261
|
+
*/
|
|
262
|
+
T cache;
|
|
263
|
+
|
|
264
|
+
public:
|
|
265
|
+
/**
|
|
266
|
+
* Constructs a new RefVar object based on a given address
|
|
267
|
+
* @param addr The address to reference.
|
|
268
|
+
*/
|
|
269
|
+
RefVar(const std::string& addr) : address(addr) {
|
|
270
|
+
cache = read();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
virtual ~RefVar() = default;
|
|
274
|
+
/**
|
|
275
|
+
* Provides an assignment operator for RefVar so that it acts just like a primitive variable.
|
|
276
|
+
* @param value The value to assign.
|
|
277
|
+
*/
|
|
278
|
+
RefVar<T>& operator=(T value) {
|
|
279
|
+
cache = value;
|
|
280
|
+
write(value);
|
|
281
|
+
return *this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Provides a reference operator to provide a reference to this RefVar.
|
|
286
|
+
*/
|
|
287
|
+
RefVar<T>& operator&(){
|
|
288
|
+
return *this;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Provides an expression operator so that a RefVar object can be used in a statement like any other variable and return its memory value.
|
|
293
|
+
*/
|
|
294
|
+
operator T() const {
|
|
295
|
+
return read();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private:
|
|
299
|
+
/**
|
|
300
|
+
* Reads the value of the reference from memory.
|
|
301
|
+
*/
|
|
302
|
+
T read() const {
|
|
303
|
+
if constexpr (std::is_same_v<T, bool>) {
|
|
304
|
+
return readBit(address);
|
|
305
|
+
} else if constexpr (std::is_same_v<T, uint8_t>) {
|
|
306
|
+
return readByte(address);
|
|
307
|
+
} else if constexpr (std::is_same_v<T, uint16_t>) {
|
|
308
|
+
return readWord(address);
|
|
309
|
+
} else if constexpr (std::is_same_v<T, uint32_t>) {
|
|
310
|
+
return readDWord(address);
|
|
311
|
+
} else {
|
|
312
|
+
static_assert(!std::is_same_v<T, T>, "Unsupported type for RefVar");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Writes the value to the memory referenced.
|
|
317
|
+
* @param value The value to assign to the memory.
|
|
318
|
+
*/
|
|
319
|
+
void write(T value) const {
|
|
320
|
+
if constexpr (std::is_same_v<T, bool>) {
|
|
321
|
+
writeBit(address, value);
|
|
322
|
+
} else if constexpr (std::is_same_v<T, uint8_t>) {
|
|
323
|
+
writeByte(address, value);
|
|
324
|
+
} else if constexpr (std::is_same_v<T, uint16_t>) {
|
|
325
|
+
writeWord(address, value);
|
|
326
|
+
} else if constexpr (std::is_same_v<T, uint32_t>) {
|
|
327
|
+
writeDWord(address, value);
|
|
328
|
+
} else {
|
|
329
|
+
static_assert(!std::is_same_v<T, T>, "Unsupported type for RefVar");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Gets a bit from a RefVar object.
|
|
336
|
+
* @param var a reference to the RefVar object
|
|
337
|
+
* @param bit The bit to read.
|
|
338
|
+
* @returns Returns the state of the bit.
|
|
339
|
+
*/
|
|
340
|
+
template<typename T>
|
|
341
|
+
bool getBit(RefVar<T>& var, int bit){
|
|
342
|
+
T ref = var;
|
|
343
|
+
return getBit(&ref, bit);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Sets a bit in a RefVar object
|
|
347
|
+
* @param var A reference to the RefVar object.
|
|
348
|
+
* @param bit The bit to set.
|
|
349
|
+
* @param value The state to set the bit to.
|
|
350
|
+
*/
|
|
351
|
+
template<typename T>
|
|
352
|
+
void setBit(RefVar<T>& var, int bit, bool value){
|
|
353
|
+
T ref = var;
|
|
354
|
+
setBit(&ref, bit, value);
|
|
355
|
+
var = ref;
|
|
356
|
+
}
|
|
357
|
+
#pragma endregion
|
|
358
|
+
#pragma region "IO Handling"
|
|
359
|
+
/**
|
|
360
|
+
* Handles the aquisition of IO inputs and the application of IO outputs.
|
|
361
|
+
*/
|
|
362
|
+
void superviseIO();
|
|
363
|
+
|
|
364
|
+
// Identifies direction of I/O mapping
|
|
365
|
+
enum class IOType {
|
|
366
|
+
Input,
|
|
367
|
+
Output
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Defines a single mapping between a remote IO module address and an internal address in the PLC.
|
|
372
|
+
*/
|
|
373
|
+
class IOMap {
|
|
374
|
+
public:
|
|
375
|
+
/**
|
|
376
|
+
* The direction of the IO interface.
|
|
377
|
+
*/
|
|
378
|
+
IOType direction;
|
|
379
|
+
/**
|
|
380
|
+
* The unique identifier of the module. This can be the IP address or a unit ID.
|
|
381
|
+
*/
|
|
382
|
+
std::string moduleID;
|
|
383
|
+
/**
|
|
384
|
+
* The port for the module communications port. In TCP/IP coms, this is the TCP port. In serial, this is the serial port.
|
|
385
|
+
*/
|
|
386
|
+
std::string modulePort;
|
|
387
|
+
/**
|
|
388
|
+
* The name of the protocol for this map.
|
|
389
|
+
*/
|
|
390
|
+
std::string protocol;
|
|
391
|
+
/**
|
|
392
|
+
* Additional properties, as defined by the protocol.
|
|
393
|
+
*/
|
|
394
|
+
json additionalProperties;
|
|
395
|
+
/**
|
|
396
|
+
* The remote address, as it is understood by the protocol.
|
|
397
|
+
*/
|
|
398
|
+
std::string remoteAddress; // e.g. "40001"
|
|
399
|
+
/**
|
|
400
|
+
* The local address, which is a memory address reference.
|
|
401
|
+
*/
|
|
402
|
+
std::string localAddress; // e.g. "%MW1"
|
|
403
|
+
/**
|
|
404
|
+
* The bit of the address.
|
|
405
|
+
*/
|
|
406
|
+
int bit = -1; // Optional bit index
|
|
407
|
+
/**
|
|
408
|
+
* The width of the input to read from the remote and store in the address.
|
|
409
|
+
*/
|
|
410
|
+
int width = 16; // 8, 16, or 32
|
|
411
|
+
/**
|
|
412
|
+
* The interval at which the module should be polled for this address.
|
|
413
|
+
*/
|
|
414
|
+
int interval = 500;
|
|
415
|
+
/**
|
|
416
|
+
* The last time the module was polled, in Milliseconds.
|
|
417
|
+
*/
|
|
418
|
+
uint64_t lastPoll = 0;
|
|
419
|
+
/**
|
|
420
|
+
* Constructs a new IOMap object based on a string of JSON.
|
|
421
|
+
* @param A string of JSON properties.
|
|
422
|
+
*/
|
|
423
|
+
IOMap(std::string mapJson);
|
|
424
|
+
IOMap();
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* The IOClient is an abstract class implemented by all protocol clients that will be used in Nodalis.
|
|
429
|
+
*/
|
|
430
|
+
class IOClient {
|
|
431
|
+
public:
|
|
432
|
+
/**
|
|
433
|
+
* Indicates whether the IOClient is connected to its remote module.
|
|
434
|
+
*/
|
|
435
|
+
bool connected;
|
|
436
|
+
IOClient(const std::string& protocol);
|
|
437
|
+
virtual ~IOClient() = default;
|
|
438
|
+
|
|
439
|
+
void addMapping(const IOMap& map);
|
|
440
|
+
bool hasMapping(std::string localAddress);
|
|
441
|
+
|
|
442
|
+
void poll(); // Reads and writes mapped I/O
|
|
443
|
+
|
|
444
|
+
const std::string& getProtocol() const;
|
|
445
|
+
const std::string& getModuleID() const;
|
|
446
|
+
protected:
|
|
447
|
+
std::string protocol;
|
|
448
|
+
std::string moduleID;
|
|
449
|
+
std::vector<IOMap> mappings;
|
|
450
|
+
uint64_t lastAttempt = 0;
|
|
451
|
+
|
|
452
|
+
// Must be implemented by derived classes
|
|
453
|
+
virtual bool readBit(const std::string& remote, int& result) = 0;
|
|
454
|
+
virtual bool writeBit(const std::string& remote, int value) = 0;
|
|
455
|
+
virtual bool readByte(const std::string& remote, uint8_t& result) = 0;
|
|
456
|
+
virtual bool writeByte(const std::string& remote, uint8_t value) = 0;
|
|
457
|
+
virtual bool readWord(const std::string& remote, uint16_t& result) = 0;
|
|
458
|
+
virtual bool writeWord(const std::string& remote, uint16_t value) = 0;
|
|
459
|
+
virtual bool readDWord(const std::string& remote, uint32_t& result) = 0;
|
|
460
|
+
virtual bool writeDWord(const std::string& remote, uint32_t value) = 0;
|
|
461
|
+
virtual void connect() = 0;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
extern std::vector<std::unique_ptr<IOClient>> Clients;
|
|
465
|
+
|
|
466
|
+
IOClient* findClient(IOMap map);
|
|
467
|
+
std::unique_ptr<IOClient> createClient(IOMap& map);
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
void mapIO(std::string map);
|
|
471
|
+
|
|
472
|
+
#pragma endregion
|
|
473
|
+
|
|
474
|
+
#pragma region "Standard Function Blocks"
|
|
475
|
+
|
|
476
|
+
class TP{
|
|
477
|
+
public:
|
|
478
|
+
bool Q;
|
|
479
|
+
bool IN;
|
|
480
|
+
uint64_t PT;
|
|
481
|
+
uint64_t ET;
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
void operator()(){
|
|
485
|
+
Q = false;
|
|
486
|
+
if(!lastIN && IN){
|
|
487
|
+
lastIN = IN;
|
|
488
|
+
ET = 0;
|
|
489
|
+
startTime = 0;
|
|
490
|
+
}
|
|
491
|
+
if(IN){
|
|
492
|
+
Q = true;
|
|
493
|
+
}
|
|
494
|
+
else if(lastIN && !IN){
|
|
495
|
+
if(startTime == 0){
|
|
496
|
+
startTime = elapsed();
|
|
497
|
+
}
|
|
498
|
+
ET = elapsed() - startTime;
|
|
499
|
+
if(PT >= ET){
|
|
500
|
+
Q = true;
|
|
501
|
+
}
|
|
502
|
+
else{
|
|
503
|
+
lastIN = false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
private:
|
|
508
|
+
bool lastIN = false;
|
|
509
|
+
uint64_t startTime = 0;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// TON: On-delay timer
|
|
513
|
+
class TON {
|
|
514
|
+
public:
|
|
515
|
+
bool IN;
|
|
516
|
+
uint64_t PT;
|
|
517
|
+
bool Q = false;
|
|
518
|
+
uint64_t ET = 0;
|
|
519
|
+
|
|
520
|
+
void operator()() {
|
|
521
|
+
if (IN) {
|
|
522
|
+
if (startTime == 0) {
|
|
523
|
+
startTime = elapsed();
|
|
524
|
+
}
|
|
525
|
+
ET = elapsed() - startTime;
|
|
526
|
+
Q = ET >= PT;
|
|
527
|
+
} else {
|
|
528
|
+
startTime = 0;
|
|
529
|
+
ET = 0;
|
|
530
|
+
Q = false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private:
|
|
535
|
+
uint64_t startTime = 0;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// TOF: Off-delay timer
|
|
539
|
+
class TOF {
|
|
540
|
+
public:
|
|
541
|
+
bool IN;
|
|
542
|
+
uint64_t PT;
|
|
543
|
+
bool Q = false;
|
|
544
|
+
uint64_t ET = 0;
|
|
545
|
+
|
|
546
|
+
void operator()() {
|
|
547
|
+
if (IN) {
|
|
548
|
+
Q = true;
|
|
549
|
+
startTime = 0;
|
|
550
|
+
ET = 0;
|
|
551
|
+
} else if (Q) {
|
|
552
|
+
if (startTime == 0) {
|
|
553
|
+
startTime = elapsed();
|
|
554
|
+
}
|
|
555
|
+
ET = elapsed() - startTime;
|
|
556
|
+
if (ET >= PT) {
|
|
557
|
+
Q = false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private:
|
|
563
|
+
uint64_t startTime = 0;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// Boolean Logic Gates
|
|
567
|
+
#define BOOL_GATE(NAME, EXPR) \
|
|
568
|
+
class NAME { \
|
|
569
|
+
public: \
|
|
570
|
+
bool IN1 = false; \
|
|
571
|
+
bool IN2 = false; \
|
|
572
|
+
bool OUT = false; \
|
|
573
|
+
void operator()() { OUT = (EXPR); } \
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
BOOL_GATE(AND, IN1 && IN2)
|
|
577
|
+
BOOL_GATE(OR, IN1 || IN2)
|
|
578
|
+
BOOL_GATE(XOR, IN1 != IN2)
|
|
579
|
+
BOOL_GATE(NOR, !(IN1 || IN2))
|
|
580
|
+
BOOL_GATE(NAND, !(IN1 && IN2))
|
|
581
|
+
#undef BOOL_GATE
|
|
582
|
+
|
|
583
|
+
class NOT {
|
|
584
|
+
public:
|
|
585
|
+
bool IN = false;
|
|
586
|
+
bool OUT = false;
|
|
587
|
+
void operator()() { OUT = !IN; }
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
class ASSIGNMENT {
|
|
591
|
+
public:
|
|
592
|
+
bool IN = false;
|
|
593
|
+
bool OUT = false;
|
|
594
|
+
void operator()() { OUT = IN; }
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// Set/Reset flip-flops
|
|
598
|
+
class SR {
|
|
599
|
+
public:
|
|
600
|
+
bool S1 = false;
|
|
601
|
+
bool R = false;
|
|
602
|
+
bool Q1 = false;
|
|
603
|
+
|
|
604
|
+
void operator()() {
|
|
605
|
+
if (R) Q1 = false;
|
|
606
|
+
if (S1) Q1 = true;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
class RS {
|
|
611
|
+
public:
|
|
612
|
+
bool S = false;
|
|
613
|
+
bool R1 = false;
|
|
614
|
+
bool Q1 = false;
|
|
615
|
+
|
|
616
|
+
void operator()() {
|
|
617
|
+
if (S) Q1 = true;
|
|
618
|
+
if (R1) Q1 = false;
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// Rising-edge Trigger
|
|
623
|
+
class R_TRIG {
|
|
624
|
+
public:
|
|
625
|
+
bool CLK = false;
|
|
626
|
+
bool OUT = false;
|
|
627
|
+
|
|
628
|
+
void operator()() {
|
|
629
|
+
OUT = CLK && !lastCLK;
|
|
630
|
+
lastCLK = CLK;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private:
|
|
634
|
+
bool lastCLK = false;
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// Falling-edge Trigger
|
|
638
|
+
class F_TRIG {
|
|
639
|
+
public:
|
|
640
|
+
bool CLK = false;
|
|
641
|
+
bool OUT = false;
|
|
642
|
+
|
|
643
|
+
void operator()() {
|
|
644
|
+
OUT = !CLK && lastCLK;
|
|
645
|
+
lastCLK = CLK;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private:
|
|
649
|
+
bool lastCLK = false;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// Up Counter
|
|
653
|
+
class CTU {
|
|
654
|
+
public:
|
|
655
|
+
bool CU = false;
|
|
656
|
+
bool R = false;
|
|
657
|
+
uint16_t PV = 0;
|
|
658
|
+
uint16_t CV = 0;
|
|
659
|
+
bool Q = false;
|
|
660
|
+
|
|
661
|
+
void operator()() {
|
|
662
|
+
if (R) {
|
|
663
|
+
CV = 0;
|
|
664
|
+
} else if (CU && !lastCU) {
|
|
665
|
+
CV++;
|
|
666
|
+
}
|
|
667
|
+
Q = CV >= PV;
|
|
668
|
+
lastCU = CU;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
private:
|
|
672
|
+
bool lastCU = false;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// Down Counter
|
|
676
|
+
class CTD {
|
|
677
|
+
public:
|
|
678
|
+
bool CD = false;
|
|
679
|
+
bool LD = false;
|
|
680
|
+
uint16_t PV = 0;
|
|
681
|
+
uint16_t CV = 0;
|
|
682
|
+
bool Q = false;
|
|
683
|
+
|
|
684
|
+
void operator()() {
|
|
685
|
+
if (LD) {
|
|
686
|
+
CV = PV;
|
|
687
|
+
} else if (CD && !lastCD && CV > 0) {
|
|
688
|
+
CV--;
|
|
689
|
+
}
|
|
690
|
+
Q = CV == 0;
|
|
691
|
+
lastCD = CD;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
private:
|
|
695
|
+
bool lastCD = false;
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// Up/Down Counter
|
|
699
|
+
class CTUD {
|
|
700
|
+
public:
|
|
701
|
+
bool CU = false;
|
|
702
|
+
bool CD = false;
|
|
703
|
+
bool R = false;
|
|
704
|
+
bool LD = false;
|
|
705
|
+
uint16_t PV = 0;
|
|
706
|
+
uint16_t CV = 0;
|
|
707
|
+
bool QU = false;
|
|
708
|
+
bool QD = false;
|
|
709
|
+
|
|
710
|
+
void operator()() {
|
|
711
|
+
if (R) {
|
|
712
|
+
CV = 0;
|
|
713
|
+
} else if (LD) {
|
|
714
|
+
CV = PV;
|
|
715
|
+
} else {
|
|
716
|
+
if (CU && !lastCU) CV++;
|
|
717
|
+
if (CD && !lastCD && CV > 0) CV--;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
QU = CV >= PV;
|
|
721
|
+
QD = CV == 0;
|
|
722
|
+
|
|
723
|
+
lastCU = CU;
|
|
724
|
+
lastCD = CD;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private:
|
|
728
|
+
bool lastCU = false;
|
|
729
|
+
bool lastCD = false;
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Comparison blocks
|
|
733
|
+
#define COMP_BLOCK(NAME, EXPR) \
|
|
734
|
+
class NAME { \
|
|
735
|
+
public: \
|
|
736
|
+
uint32_t IN1 = 0, IN2 = 0; \
|
|
737
|
+
bool OUT = false; \
|
|
738
|
+
void operator()() { OUT = (EXPR); } \
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
COMP_BLOCK(EQ, IN1 == IN2)
|
|
742
|
+
COMP_BLOCK(NE, IN1 != IN2)
|
|
743
|
+
COMP_BLOCK(LT, IN1 < IN2)
|
|
744
|
+
COMP_BLOCK(GT, IN1 > IN2)
|
|
745
|
+
COMP_BLOCK(GE, IN1 >= IN2)
|
|
746
|
+
COMP_BLOCK(LE, IN1 <= IN2)
|
|
747
|
+
#undef COMP_BLOCK
|
|
748
|
+
|
|
749
|
+
class MOVE {
|
|
750
|
+
public:
|
|
751
|
+
uint32_t IN = 0;
|
|
752
|
+
uint32_t OUT = 0;
|
|
753
|
+
void operator()() { OUT = IN; }
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
class SEL {
|
|
757
|
+
public:
|
|
758
|
+
bool G = false;
|
|
759
|
+
uint32_t IN0 = 0, IN1 = 0;
|
|
760
|
+
uint32_t OUT = 0;
|
|
761
|
+
void operator()() { OUT = G ? IN1 : IN0; }
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
class MUX {
|
|
765
|
+
public:
|
|
766
|
+
bool K = false;
|
|
767
|
+
uint32_t IN0 = 0, IN1 = 0;
|
|
768
|
+
uint32_t OUT = 0;
|
|
769
|
+
void operator()() { OUT = K ? IN1 : IN0; }
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
class MIN {
|
|
773
|
+
public:
|
|
774
|
+
uint32_t IN1 = 0, IN2 = 0;
|
|
775
|
+
uint32_t OUT = 0;
|
|
776
|
+
void operator()() { OUT = std::min(IN1, IN2); }
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
class MAX {
|
|
780
|
+
public:
|
|
781
|
+
uint32_t IN1 = 0, IN2 = 0;
|
|
782
|
+
uint32_t OUT = 0;
|
|
783
|
+
void operator()() { OUT = std::max(IN1, IN2); }
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
class LIMIT {
|
|
787
|
+
public:
|
|
788
|
+
uint32_t MN = 0, IN = 0, MX = 0;
|
|
789
|
+
uint32_t OUT = 0;
|
|
790
|
+
void operator()() {
|
|
791
|
+
if (IN < MN) OUT = MN;
|
|
792
|
+
else if (IN > MX) OUT = MX;
|
|
793
|
+
else OUT = IN;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
#pragma endregion
|