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,378 @@
|
|
|
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 Modbus
|
|
17
|
+
* @author Nathan Skipper, MTI
|
|
18
|
+
* @version 1.0.2
|
|
19
|
+
* @copyright Apache 2.0
|
|
20
|
+
*/
|
|
21
|
+
#include "modbus.h"
|
|
22
|
+
#include <cstring>
|
|
23
|
+
#include <iostream>
|
|
24
|
+
#ifdef _WIN32
|
|
25
|
+
#include <winsock2.h>
|
|
26
|
+
#include <ws2tcpip.h>
|
|
27
|
+
#pragma comment(lib, "ws2_32.lib")
|
|
28
|
+
#else
|
|
29
|
+
#include <errno.h>
|
|
30
|
+
#endif
|
|
31
|
+
|
|
32
|
+
// ========== Server Implementation ==========
|
|
33
|
+
|
|
34
|
+
ModbusServer::ModbusServer() {}
|
|
35
|
+
|
|
36
|
+
void ModbusServer::setCoil(uint16_t address, bool value) {
|
|
37
|
+
coils[address] = value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool ModbusServer::getCoil(uint16_t address) {
|
|
41
|
+
return coils[address];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void ModbusServer::setDiscreteInput(uint16_t address, bool value) {
|
|
45
|
+
discreteInputs[address] = value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
bool ModbusServer::getDiscreteInput(uint16_t address) {
|
|
49
|
+
return discreteInputs[address];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void ModbusServer::setRegister(uint16_t address, uint16_t value) {
|
|
53
|
+
holdingRegisters[address] = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
uint16_t ModbusServer::getRegister(uint16_t address) {
|
|
57
|
+
return holdingRegisters[address];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ModbusResponse ModbusServer::handleRequest(const ModbusRequest& request) {
|
|
61
|
+
ModbusResponse res;
|
|
62
|
+
res.address = request.address;
|
|
63
|
+
res.function = request.function;
|
|
64
|
+
res.exceptionCode = 0;
|
|
65
|
+
|
|
66
|
+
switch (request.function) {
|
|
67
|
+
case READ_COILS:
|
|
68
|
+
case READ_DISCRETE_INPUTS: {
|
|
69
|
+
for (uint16_t i = 0; i < request.quantity; ++i) {
|
|
70
|
+
bool bit = (request.function == READ_COILS)
|
|
71
|
+
? getCoil(request.startAddress + i)
|
|
72
|
+
: getDiscreteInput(request.startAddress + i);
|
|
73
|
+
if (i % 8 == 0)
|
|
74
|
+
res.data.push_back(0);
|
|
75
|
+
if (bit)
|
|
76
|
+
res.data[i / 8] |= (1 << (i % 8));
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case READ_HOLDING_REGISTERS:
|
|
82
|
+
case READ_INPUT_REGISTERS: {
|
|
83
|
+
for (uint16_t i = 0; i < request.quantity; ++i) {
|
|
84
|
+
uint16_t val = getRegister(request.startAddress + i);
|
|
85
|
+
res.data.push_back(val >> 8);
|
|
86
|
+
res.data.push_back(val & 0xFF);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case WRITE_SINGLE_COIL: {
|
|
92
|
+
if (request.data.size() < 2) { res.exceptionCode = 0x03; break; }
|
|
93
|
+
bool value = (request.data[0] == 0xFF);
|
|
94
|
+
setCoil(request.startAddress, value);
|
|
95
|
+
res.data = request.data;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case WRITE_SINGLE_REGISTER: {
|
|
100
|
+
if (request.data.size() < 2) { res.exceptionCode = 0x03; break; }
|
|
101
|
+
uint16_t value = (request.data[0] << 8) | request.data[1];
|
|
102
|
+
setRegister(request.startAddress, value);
|
|
103
|
+
res.data = request.data;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
res.exceptionCode = 0x01;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return res;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ========== Client Implementation ==========
|
|
116
|
+
|
|
117
|
+
ModbusClient::ModbusClient(const std::string& ip, uint16_t port, uint8_t unitId)
|
|
118
|
+
: IOClient("MODBUS-TCP"), deviceAddress(unitId), sockfd(-1), ip(ip), port(port) {
|
|
119
|
+
#ifdef _WIN32
|
|
120
|
+
WSADATA wsa;
|
|
121
|
+
WSAStartup(MAKEWORD(2,2), &wsa);
|
|
122
|
+
#endif
|
|
123
|
+
if(ip != "") connectTCP(ip, port);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ModbusClient::~ModbusClient() {
|
|
127
|
+
disconnect();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
void ModbusClient::connect(){
|
|
131
|
+
if(connected){
|
|
132
|
+
disconnect();
|
|
133
|
+
}
|
|
134
|
+
if(ip == "" || port == 0){
|
|
135
|
+
if(mappings.size() > 0){
|
|
136
|
+
IOMap& map = mappings[0];
|
|
137
|
+
ip = map.moduleID;
|
|
138
|
+
port = std::atoi(map.modulePort.c_str());
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if(ip != "" && port > 0){
|
|
142
|
+
moduleID = ip;
|
|
143
|
+
connectTCP(ip, port);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
bool ModbusClient::connectTCP(const std::string& ip, uint16_t port) {
|
|
148
|
+
std::cout << "Modbus-TCP attempting to connect to " << ip.c_str() << ":" << port << "\n";
|
|
149
|
+
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
150
|
+
if (sockfd < 0) return false;
|
|
151
|
+
|
|
152
|
+
sockaddr_in serverAddr;
|
|
153
|
+
memset(&serverAddr, 0, sizeof(serverAddr));
|
|
154
|
+
serverAddr.sin_family = AF_INET;
|
|
155
|
+
serverAddr.sin_port = htons(port);
|
|
156
|
+
inet_pton(AF_INET, ip.c_str(), &serverAddr.sin_addr);
|
|
157
|
+
|
|
158
|
+
int err = ::connect(sockfd, (sockaddr*)&serverAddr, sizeof(serverAddr));
|
|
159
|
+
if (err < 0) {
|
|
160
|
+
disconnect();
|
|
161
|
+
#ifdef _WIN32
|
|
162
|
+
int wsaErr = WSAGetLastError();
|
|
163
|
+
std::cerr << "Connect failed: WSA Error " << wsaErr << "\n";
|
|
164
|
+
#else
|
|
165
|
+
std::cerr << "Connect failed: " << strerror(errno)
|
|
166
|
+
<< " (errno = " << errno << ")\n";
|
|
167
|
+
#endif
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
std::cout << "Modbus-TCP connected to " << ip.c_str() << ":" << port << "\n";
|
|
171
|
+
connected = true;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void ModbusClient::disconnect() {
|
|
176
|
+
if (connected) {
|
|
177
|
+
#ifdef _WIN32
|
|
178
|
+
closesocket(sockfd);
|
|
179
|
+
#else
|
|
180
|
+
close(sockfd);
|
|
181
|
+
#endif
|
|
182
|
+
connected = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
ModbusRequest ModbusClient::createReadRequest(uint8_t function, uint16_t startAddress, uint16_t quantity) {
|
|
187
|
+
return { deviceAddress, function, startAddress, quantity, {} };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
ModbusRequest ModbusClient::createWriteSingleCoil(uint16_t address, bool value) {
|
|
191
|
+
std::vector<uint8_t> data = value ? std::vector<uint8_t>{0xFF, 0x00} : std::vector<uint8_t>{0x00, 0x00};
|
|
192
|
+
return { deviceAddress, WRITE_SINGLE_COIL, address, 0, data };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
ModbusRequest ModbusClient::createWriteSingleRegister(uint16_t address, uint16_t value) {
|
|
196
|
+
std::vector<uint8_t> data = { static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value & 0xFF) };
|
|
197
|
+
return { deviceAddress, WRITE_SINGLE_REGISTER, address, 1, data };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
bool ModbusClient::sendRequest(const ModbusRequest& req, ModbusResponse& resp) {
|
|
201
|
+
std::vector<uint8_t> pdu;
|
|
202
|
+
|
|
203
|
+
// Handle function-specific encoding
|
|
204
|
+
if (req.function == WRITE_SINGLE_COIL) {
|
|
205
|
+
// Function 0x05: Write Single Coil
|
|
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
|
+
pdu.push_back(req.data[0]); // Hi byte (0xFF or 0x00)
|
|
213
|
+
pdu.push_back(req.data[1]); // Lo byte (always 0x00)
|
|
214
|
+
} else {
|
|
215
|
+
std::cerr << "Invalid data size for Write Single Coil (expected 2 bytes).\n";
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// Default encoding for most function codes
|
|
220
|
+
pdu = {
|
|
221
|
+
req.function,
|
|
222
|
+
static_cast<uint8_t>(req.startAddress >> 8),
|
|
223
|
+
static_cast<uint8_t>(req.startAddress & 0xFF),
|
|
224
|
+
static_cast<uint8_t>(req.quantity >> 8),
|
|
225
|
+
static_cast<uint8_t>(req.quantity & 0xFF)
|
|
226
|
+
};
|
|
227
|
+
pdu.insert(pdu.end(), req.data.begin(), req.data.end());
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
std::vector<uint8_t> response;
|
|
231
|
+
if (!sendRaw(pdu, response)) return false;
|
|
232
|
+
|
|
233
|
+
if (response.size() < 2) return false;
|
|
234
|
+
|
|
235
|
+
resp.address = req.address;
|
|
236
|
+
resp.function = response[0];
|
|
237
|
+
resp.data.assign(response.begin() + 1, response.end());
|
|
238
|
+
resp.exceptionCode = (resp.function & 0x80) ? resp.data[0] : 0;
|
|
239
|
+
|
|
240
|
+
if (resp.exceptionCode > 0) {
|
|
241
|
+
std::cerr << "MODBUS exception code: " << static_cast<int>(resp.exceptionCode) << "\n";
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bool ModbusClient::sendRaw(const std::vector<uint8_t>& pdu, std::vector<uint8_t>& response) {
|
|
249
|
+
if (!connected) return false;
|
|
250
|
+
|
|
251
|
+
// MBAP header (7 bytes): Transaction ID, Protocol ID, Length, Unit ID
|
|
252
|
+
uint8_t mbap[7] = {
|
|
253
|
+
0x00, 0x01, 0x00, 0x00,
|
|
254
|
+
static_cast<uint8_t>((pdu.size() + 1) >> 8),
|
|
255
|
+
static_cast<uint8_t>((pdu.size() + 1) & 0xFF),
|
|
256
|
+
deviceAddress
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
std::vector<uint8_t> packet(mbap, mbap + 7);
|
|
260
|
+
packet.insert(packet.end(), pdu.begin(), pdu.end());
|
|
261
|
+
|
|
262
|
+
// Send the packet
|
|
263
|
+
ssize_t bytesSent = send(sockfd, reinterpret_cast<const char*>(packet.data()), packet.size(), 0);
|
|
264
|
+
if (bytesSent < 0) {
|
|
265
|
+
#ifdef _WIN32
|
|
266
|
+
int err = WSAGetLastError();
|
|
267
|
+
std::cerr << "Send failed: WSA error " << err << "\n";
|
|
268
|
+
#else
|
|
269
|
+
std::cerr << "Send failed: " << strerror(errno) << " (errno = " << errno << ")\n";
|
|
270
|
+
#endif
|
|
271
|
+
disconnect();
|
|
272
|
+
connected = false;
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Receive response
|
|
277
|
+
uint8_t buf[260] = {0};
|
|
278
|
+
ssize_t len = recv(sockfd, reinterpret_cast<char*>(buf), sizeof(buf), 0);
|
|
279
|
+
if (len < 0) {
|
|
280
|
+
#ifdef _WIN32
|
|
281
|
+
int err = WSAGetLastError();
|
|
282
|
+
std::cerr << "Receive failed: WSA error " << err << "\n";
|
|
283
|
+
#else
|
|
284
|
+
std::cerr << "Receive failed: " << strerror(errno) << " (errno = " << errno << ")\n";
|
|
285
|
+
#endif
|
|
286
|
+
disconnect();
|
|
287
|
+
connected = false;
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (len < 9) {
|
|
292
|
+
std::cerr << "Incomplete MODBUS response (len = " << len << ")\n";
|
|
293
|
+
disconnect();
|
|
294
|
+
connected = false;
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
response.assign(buf + 7, buf + len);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
bool ModbusClient::readBit(const std::string& remote, int& result) {
|
|
303
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
304
|
+
ModbusRequest req = createReadRequest(READ_DISCRETE_INPUTS, addr, 1);
|
|
305
|
+
ModbusResponse res;
|
|
306
|
+
if (!sendRequest(req, res)) return false;
|
|
307
|
+
|
|
308
|
+
result = (res.data[1] & 0x01) ? 1 : 0;
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
bool ModbusClient::writeBit(const std::string& remote, int value) {
|
|
313
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
314
|
+
ModbusRequest req = createWriteSingleCoil(addr, value != 0);
|
|
315
|
+
ModbusResponse res;
|
|
316
|
+
return sendRequest(req, res);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
bool ModbusClient::readByte(const std::string& remote, uint8_t& result) {
|
|
320
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
321
|
+
ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 1);
|
|
322
|
+
ModbusResponse res;
|
|
323
|
+
if (!sendRequest(req, res)) return false;
|
|
324
|
+
|
|
325
|
+
// Read only low byte
|
|
326
|
+
result = res.data.size() >= 2 ? res.data[1] : 0;
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
bool ModbusClient::writeByte(const std::string& remote, uint8_t value) {
|
|
331
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
332
|
+
// Store value in lower byte; high byte = 0
|
|
333
|
+
std::vector<uint8_t> data = { 0x00, value };
|
|
334
|
+
ModbusRequest req = { deviceAddress, WRITE_SINGLE_REGISTER, addr, 1, data };
|
|
335
|
+
ModbusResponse res;
|
|
336
|
+
return sendRequest(req, res);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
bool ModbusClient::readWord(const std::string& remote, uint16_t& result) {
|
|
340
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
341
|
+
ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 1);
|
|
342
|
+
ModbusResponse res;
|
|
343
|
+
if (!sendRequest(req, res)) return false;
|
|
344
|
+
|
|
345
|
+
result = (res.data[0] << 8) | res.data[1];
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
bool ModbusClient::writeWord(const std::string& remote, uint16_t value) {
|
|
350
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
351
|
+
ModbusRequest req = createWriteSingleRegister(addr, value);
|
|
352
|
+
ModbusResponse res;
|
|
353
|
+
return sendRequest(req, res);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
bool ModbusClient::readDWord(const std::string& remote, uint32_t& result) {
|
|
357
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
358
|
+
ModbusRequest req = createReadRequest(READ_HOLDING_REGISTERS, addr, 2);
|
|
359
|
+
ModbusResponse res;
|
|
360
|
+
if (!sendRequest(req, res)) return false;
|
|
361
|
+
|
|
362
|
+
result = (res.data[0] << 24) | (res.data[1] << 16) | (res.data[2] << 8) | res.data[3];
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
bool ModbusClient::writeDWord(const std::string& remote, uint32_t value) {
|
|
367
|
+
uint16_t addr = static_cast<uint16_t>(std::stoi(remote));
|
|
368
|
+
std::vector<uint8_t> data = {
|
|
369
|
+
static_cast<uint8_t>(value >> 24),
|
|
370
|
+
static_cast<uint8_t>((value >> 16) & 0xFF),
|
|
371
|
+
static_cast<uint8_t>((value >> 8) & 0xFF),
|
|
372
|
+
static_cast<uint8_t>(value & 0xFF)
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
ModbusRequest req = { deviceAddress, WRITE_MULTIPLE_REGISTERS, addr, 2, data };
|
|
376
|
+
ModbusResponse res;
|
|
377
|
+
return sendRequest(req, res);
|
|
378
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 Modbus
|
|
17
|
+
* @author Nathan Skipper, MTI
|
|
18
|
+
* @version 1.0.2
|
|
19
|
+
* @copyright Apache 2.0
|
|
20
|
+
*/
|
|
21
|
+
#pragma once
|
|
22
|
+
#ifndef MODBUS_H
|
|
23
|
+
#define MODBUS_H
|
|
24
|
+
|
|
25
|
+
#include <cstdint>
|
|
26
|
+
#include <vector>
|
|
27
|
+
#include <map>
|
|
28
|
+
#include <string>
|
|
29
|
+
#include "nodalis.h"
|
|
30
|
+
|
|
31
|
+
#ifdef _WIN32
|
|
32
|
+
#include <winsock2.h>
|
|
33
|
+
typedef int socklen_t;
|
|
34
|
+
#else
|
|
35
|
+
#include <sys/socket.h>
|
|
36
|
+
#include <arpa/inet.h>
|
|
37
|
+
#include <unistd.h>
|
|
38
|
+
#endif
|
|
39
|
+
|
|
40
|
+
// MODBUS function codes
|
|
41
|
+
enum ModbusFunctionCode {
|
|
42
|
+
READ_COILS = 0x01,
|
|
43
|
+
READ_DISCRETE_INPUTS = 0x02,
|
|
44
|
+
READ_HOLDING_REGISTERS = 0x03,
|
|
45
|
+
READ_INPUT_REGISTERS = 0x04,
|
|
46
|
+
WRITE_SINGLE_COIL = 0x05,
|
|
47
|
+
WRITE_SINGLE_REGISTER = 0x06,
|
|
48
|
+
WRITE_MULTIPLE_COILS = 0x0F,
|
|
49
|
+
WRITE_MULTIPLE_REGISTERS = 0x10
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
struct ModbusRequest {
|
|
53
|
+
uint8_t address;
|
|
54
|
+
uint8_t function;
|
|
55
|
+
uint16_t startAddress;
|
|
56
|
+
uint16_t quantity;
|
|
57
|
+
std::vector<uint8_t> data;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
struct ModbusResponse {
|
|
61
|
+
uint8_t address;
|
|
62
|
+
uint8_t function;
|
|
63
|
+
std::vector<uint8_t> data;
|
|
64
|
+
uint8_t exceptionCode;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Server implementation
|
|
68
|
+
class ModbusServer {
|
|
69
|
+
public:
|
|
70
|
+
ModbusServer();
|
|
71
|
+
|
|
72
|
+
void setCoil(uint16_t address, bool value);
|
|
73
|
+
bool getCoil(uint16_t address);
|
|
74
|
+
|
|
75
|
+
void setDiscreteInput(uint16_t address, bool value);
|
|
76
|
+
bool getDiscreteInput(uint16_t address);
|
|
77
|
+
|
|
78
|
+
void setRegister(uint16_t address, uint16_t value);
|
|
79
|
+
uint16_t getRegister(uint16_t address);
|
|
80
|
+
|
|
81
|
+
ModbusResponse handleRequest(const ModbusRequest& request);
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
std::map<uint16_t, bool> coils;
|
|
85
|
+
std::map<uint16_t, bool> discreteInputs;
|
|
86
|
+
std::map<uint16_t, uint16_t> holdingRegisters;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Client implementation
|
|
90
|
+
class ModbusClient : public IOClient{
|
|
91
|
+
public:
|
|
92
|
+
ModbusClient(const std::string& ip = "", uint16_t port = 502, uint8_t unitId = 1);
|
|
93
|
+
~ModbusClient();
|
|
94
|
+
|
|
95
|
+
bool connectTCP(const std::string& ip, uint16_t port);
|
|
96
|
+
void disconnect();
|
|
97
|
+
|
|
98
|
+
ModbusRequest createReadRequest(uint8_t function, uint16_t startAddress, uint16_t quantity);
|
|
99
|
+
ModbusRequest createWriteSingleCoil(uint16_t address, bool value);
|
|
100
|
+
ModbusRequest createWriteSingleRegister(uint16_t address, uint16_t value);
|
|
101
|
+
|
|
102
|
+
bool sendRequest(const ModbusRequest& request, ModbusResponse& response);
|
|
103
|
+
|
|
104
|
+
protected:
|
|
105
|
+
std::string ip;
|
|
106
|
+
uint16_t port;
|
|
107
|
+
bool readBit(const std::string& remote, int& result) override;
|
|
108
|
+
bool writeBit(const std::string& remote, int value) override;
|
|
109
|
+
bool readByte(const std::string& remote, uint8_t& result) override;
|
|
110
|
+
bool writeByte(const std::string& remote, uint8_t value) override;
|
|
111
|
+
bool readWord(const std::string& remote, uint16_t& result) override;
|
|
112
|
+
bool writeWord(const std::string& remote, uint16_t value) override;
|
|
113
|
+
bool readDWord(const std::string& remote, uint32_t& result) override;
|
|
114
|
+
bool writeDWord(const std::string& remote, uint32_t value) override;
|
|
115
|
+
void connect() override;
|
|
116
|
+
|
|
117
|
+
private:
|
|
118
|
+
int sockfd;
|
|
119
|
+
uint8_t deviceAddress;
|
|
120
|
+
|
|
121
|
+
bool sendRaw(const std::vector<uint8_t>& requestPDU, std::vector<uint8_t>& responsePDU);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
#endif // MODBUS_H
|