node-red-contrib-tcp-client-server-avd 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/NodeRedTCP/README.md +408 -0
- package/NodeRedTCP/SPECIFICATIE.md +460 -0
- package/NodeRedTCP/nodes/tcp-client-server-avd.html +298 -0
- package/NodeRedTCP/nodes/tcp-client-server-avd.js +606 -0
- package/NodeRedTCP/package.json +32 -0
- package/README.md +355 -0
- package/SPECIFICATIE.md +402 -0
- package/nodes/tcp-client-server.html +298 -0
- package/nodes/tcp-client-server.js +606 -0
- package/package.json +27 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-RED TCP Client/Server Node
|
|
3
|
+
* Implementatie volgens SPECIFICATIE.md
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = function(RED) {
|
|
7
|
+
"use strict";
|
|
8
|
+
const net = require('net');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
// Helper functie voor UUID generatie
|
|
12
|
+
function generateId() {
|
|
13
|
+
if (typeof crypto.randomUUID === 'function') {
|
|
14
|
+
return crypto.randomUUID();
|
|
15
|
+
}
|
|
16
|
+
// Fallback voor oudere Node.js versies
|
|
17
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
18
|
+
const r = Math.random() * 16 | 0;
|
|
19
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
20
|
+
return v.toString(16);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Data formatting functies
|
|
25
|
+
function formatOutput(data, format, encoding) {
|
|
26
|
+
if (format === "string") {
|
|
27
|
+
if (Buffer.isBuffer(data)) {
|
|
28
|
+
return data.toString(encoding || 'utf8');
|
|
29
|
+
}
|
|
30
|
+
return String(data);
|
|
31
|
+
} else if (format === "buffer") {
|
|
32
|
+
if (Buffer.isBuffer(data)) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
return Buffer.from(data);
|
|
36
|
+
} else if (format === "json") {
|
|
37
|
+
if (Buffer.isBuffer(data)) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(data.toString(encoding || 'utf8'));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return { raw: data.toString(encoding || 'utf8') };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (typeof data === 'string') {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(data);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
} else if (format === "hex") {
|
|
53
|
+
if (Buffer.isBuffer(data)) {
|
|
54
|
+
return data.toString('hex');
|
|
55
|
+
}
|
|
56
|
+
if (typeof data === 'string') {
|
|
57
|
+
return Buffer.from(data, encoding || 'utf8').toString('hex');
|
|
58
|
+
}
|
|
59
|
+
return Buffer.from(String(data)).toString('hex');
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatInput(data, format, encoding) {
|
|
65
|
+
if (format === "string") {
|
|
66
|
+
if (Buffer.isBuffer(data)) {
|
|
67
|
+
return data.toString(encoding || 'utf8');
|
|
68
|
+
}
|
|
69
|
+
if (typeof data === 'object') {
|
|
70
|
+
return JSON.stringify(data);
|
|
71
|
+
}
|
|
72
|
+
return String(data);
|
|
73
|
+
} else if (format === "buffer") {
|
|
74
|
+
if (Buffer.isBuffer(data)) {
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
if (typeof data === 'string') {
|
|
78
|
+
return Buffer.from(data, encoding || 'utf8');
|
|
79
|
+
}
|
|
80
|
+
return Buffer.from(String(data));
|
|
81
|
+
} else if (format === "json") {
|
|
82
|
+
if (Buffer.isBuffer(data)) {
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
if (typeof data === 'string') {
|
|
86
|
+
return Buffer.from(data, encoding || 'utf8');
|
|
87
|
+
}
|
|
88
|
+
if (typeof data === 'object') {
|
|
89
|
+
return Buffer.from(JSON.stringify(data), encoding || 'utf8');
|
|
90
|
+
}
|
|
91
|
+
return Buffer.from(String(data), encoding || 'utf8');
|
|
92
|
+
} else if (format === "hex") {
|
|
93
|
+
if (typeof data === 'string') {
|
|
94
|
+
// Verwijder eventuele spaties en controleer op hex
|
|
95
|
+
const hex = data.replace(/\s/g, '');
|
|
96
|
+
if (/^[0-9a-fA-F]+$/.test(hex)) {
|
|
97
|
+
return Buffer.from(hex, 'hex');
|
|
98
|
+
}
|
|
99
|
+
return Buffer.from(data, encoding || 'utf8');
|
|
100
|
+
}
|
|
101
|
+
return Buffer.from(String(data), encoding || 'utf8');
|
|
102
|
+
}
|
|
103
|
+
return Buffer.from(String(data), encoding || 'utf8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function TCPClientServerNode(config) {
|
|
107
|
+
RED.nodes.createNode(this, config);
|
|
108
|
+
const node = this;
|
|
109
|
+
|
|
110
|
+
// Configuratie
|
|
111
|
+
node.mode = config.mode || "client";
|
|
112
|
+
node.name = config.name || "";
|
|
113
|
+
|
|
114
|
+
// Client configuratie
|
|
115
|
+
node.clientConfig = {
|
|
116
|
+
host: config.clientHost || "localhost",
|
|
117
|
+
port: parseInt(config.clientPort) || 8080,
|
|
118
|
+
ipVersion: config.clientIpVersion || "4",
|
|
119
|
+
autoConnect: config.clientAutoConnect !== false,
|
|
120
|
+
reconnect: config.clientReconnect !== false,
|
|
121
|
+
reconnectInterval: parseInt(config.clientReconnectInterval) || 5,
|
|
122
|
+
maxReconnectAttempts: parseInt(config.clientMaxReconnectAttempts) || 0,
|
|
123
|
+
connectionTimeout: parseInt(config.clientConnectionTimeout) || 10,
|
|
124
|
+
inputFormat: config.clientInputFormat || "string",
|
|
125
|
+
outputFormat: config.clientOutputFormat || "string",
|
|
126
|
+
encoding: config.clientEncoding || "utf8",
|
|
127
|
+
delimiter: config.clientDelimiter || "",
|
|
128
|
+
keepAlive: config.clientKeepAlive === true
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Server configuratie
|
|
132
|
+
node.serverConfig = {
|
|
133
|
+
port: parseInt(config.serverPort) || 8080,
|
|
134
|
+
bindAddress: config.serverBindAddress || "0.0.0.0",
|
|
135
|
+
ipVersion: config.serverIpVersion || "4",
|
|
136
|
+
autoStart: config.serverAutoStart !== false,
|
|
137
|
+
maxConnections: parseInt(config.serverMaxConnections) || 0,
|
|
138
|
+
connectionTimeout: parseInt(config.serverConnectionTimeout) || 600,
|
|
139
|
+
inputFormat: config.serverInputFormat || "string",
|
|
140
|
+
outputFormat: config.serverOutputFormat || "string",
|
|
141
|
+
encoding: config.serverEncoding || "utf8",
|
|
142
|
+
delimiter: config.serverDelimiter || "",
|
|
143
|
+
keepAlive: config.serverKeepAlive === true
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Client state
|
|
147
|
+
node.clientSocket = null;
|
|
148
|
+
node.clientConnected = false;
|
|
149
|
+
node.clientReconnectTimer = null;
|
|
150
|
+
node.clientReconnectAttempts = 0;
|
|
151
|
+
node.clientBuffer = Buffer.alloc(0);
|
|
152
|
+
|
|
153
|
+
// Server state
|
|
154
|
+
node.server = null;
|
|
155
|
+
node.serverListening = false;
|
|
156
|
+
node.serverClients = new Map(); // clientId -> socket
|
|
157
|
+
node.clientBuffers = new Map(); // clientId -> buffer
|
|
158
|
+
|
|
159
|
+
// Delimiter processing helper
|
|
160
|
+
function parseDelimiter(delimiter, encoding) {
|
|
161
|
+
if (delimiter === "\\n") {
|
|
162
|
+
return Buffer.from("\n");
|
|
163
|
+
} else if (delimiter === "\\r\\n") {
|
|
164
|
+
return Buffer.from("\r\n");
|
|
165
|
+
} else if (delimiter === "\\r") {
|
|
166
|
+
return Buffer.from("\r");
|
|
167
|
+
} else {
|
|
168
|
+
return Buffer.from(delimiter, encoding);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Client functionaliteit
|
|
173
|
+
function initClient() {
|
|
174
|
+
if (node.mode !== "client" && node.mode !== "both") {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (node.clientConfig.autoConnect) {
|
|
179
|
+
clientConnect();
|
|
180
|
+
} else {
|
|
181
|
+
node.status({ fill: "grey", shape: "ring", text: "niet verbonden" });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function clientConnect() {
|
|
186
|
+
if (node.clientSocket && node.clientConnected) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (node.clientSocket) {
|
|
191
|
+
node.clientSocket.destroy();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
node.status({ fill: "yellow", shape: "ring", text: "verbinden..." });
|
|
195
|
+
|
|
196
|
+
const options = {
|
|
197
|
+
port: node.clientConfig.port,
|
|
198
|
+
host: node.clientConfig.host,
|
|
199
|
+
family: node.clientConfig.ipVersion === "6" ? 6 : 4
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (node.clientConfig.keepAlive) {
|
|
203
|
+
options.keepAlive = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const socket = new net.Socket();
|
|
207
|
+
node.clientSocket = socket;
|
|
208
|
+
node.clientBuffer = Buffer.alloc(0);
|
|
209
|
+
|
|
210
|
+
// Timeout alleen voor connect, niet voor idle
|
|
211
|
+
socket.setTimeout(node.clientConfig.connectionTimeout * 1000);
|
|
212
|
+
|
|
213
|
+
socket.on('connect', function() {
|
|
214
|
+
node.clientConnected = true;
|
|
215
|
+
node.clientReconnectAttempts = 0;
|
|
216
|
+
// Schakel timeout uit na succesvolle connectie (geen idle timeout)
|
|
217
|
+
socket.setTimeout(0);
|
|
218
|
+
node.status({ fill: "green", shape: "dot", text: `verbonden ${node.clientConfig.host}:${node.clientConfig.port}` });
|
|
219
|
+
|
|
220
|
+
const msg = {
|
|
221
|
+
topic: "status",
|
|
222
|
+
payload: "connected",
|
|
223
|
+
status: "connected"
|
|
224
|
+
};
|
|
225
|
+
node.send(msg);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
socket.on('data', function(data) {
|
|
229
|
+
if (node.clientConfig.delimiter) {
|
|
230
|
+
const combined = Buffer.concat([node.clientBuffer, data]);
|
|
231
|
+
const delim = parseDelimiter(node.clientConfig.delimiter, node.clientConfig.encoding);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
let index;
|
|
234
|
+
|
|
235
|
+
while ((index = combined.indexOf(delim, offset)) !== -1) {
|
|
236
|
+
const message = combined.slice(offset, index);
|
|
237
|
+
const formatted = formatOutput(message, node.clientConfig.outputFormat, node.clientConfig.encoding);
|
|
238
|
+
const msg = {
|
|
239
|
+
topic: "data",
|
|
240
|
+
payload: formatted,
|
|
241
|
+
timestamp: new Date().toISOString()
|
|
242
|
+
};
|
|
243
|
+
node.send(msg);
|
|
244
|
+
offset = index + delim.length;
|
|
245
|
+
}
|
|
246
|
+
node.clientBuffer = combined.slice(offset);
|
|
247
|
+
} else {
|
|
248
|
+
const formatted = formatOutput(data, node.clientConfig.outputFormat, node.clientConfig.encoding);
|
|
249
|
+
const msg = {
|
|
250
|
+
topic: "data",
|
|
251
|
+
payload: formatted,
|
|
252
|
+
timestamp: new Date().toISOString()
|
|
253
|
+
};
|
|
254
|
+
node.send(msg);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
socket.on('close', function(hadError) {
|
|
259
|
+
node.clientConnected = false;
|
|
260
|
+
node.status({ fill: "red", shape: "ring", text: "verbinding gesloten" });
|
|
261
|
+
|
|
262
|
+
const msg = {
|
|
263
|
+
topic: "status",
|
|
264
|
+
payload: "disconnected",
|
|
265
|
+
status: "disconnected",
|
|
266
|
+
error: hadError ? "connection closed with error" : undefined
|
|
267
|
+
};
|
|
268
|
+
node.send(msg);
|
|
269
|
+
|
|
270
|
+
if (node.clientConfig.reconnect && (node.clientConfig.maxReconnectAttempts === 0 || node.clientReconnectAttempts < node.clientConfig.maxReconnectAttempts)) {
|
|
271
|
+
node.clientReconnectAttempts++;
|
|
272
|
+
node.clientReconnectTimer = setTimeout(function() {
|
|
273
|
+
clientConnect();
|
|
274
|
+
}, node.clientConfig.reconnectInterval * 1000);
|
|
275
|
+
node.status({ fill: "yellow", shape: "ring", text: `herconnectie in ${node.clientConfig.reconnectInterval}s...` });
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
socket.on('error', function(err) {
|
|
280
|
+
node.clientConnected = false;
|
|
281
|
+
node.status({ fill: "red", shape: "ring", text: `fout: ${err.message}` });
|
|
282
|
+
|
|
283
|
+
const msg = {
|
|
284
|
+
topic: "status",
|
|
285
|
+
payload: "error",
|
|
286
|
+
status: "error",
|
|
287
|
+
error: err.message
|
|
288
|
+
};
|
|
289
|
+
node.send(msg);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
socket.on('timeout', function() {
|
|
293
|
+
// Timeout wordt alleen gebruikt tijdens connect, niet tijdens verbonden state
|
|
294
|
+
if (!node.clientConnected) {
|
|
295
|
+
socket.destroy();
|
|
296
|
+
node.clientConnected = false;
|
|
297
|
+
node.status({ fill: "red", shape: "ring", text: "connect timeout" });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
socket.connect(options);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function clientDisconnect() {
|
|
305
|
+
if (node.clientReconnectTimer) {
|
|
306
|
+
clearTimeout(node.clientReconnectTimer);
|
|
307
|
+
node.clientReconnectTimer = null;
|
|
308
|
+
}
|
|
309
|
+
if (node.clientSocket) {
|
|
310
|
+
node.clientSocket.destroy();
|
|
311
|
+
node.clientSocket = null;
|
|
312
|
+
}
|
|
313
|
+
node.clientConnected = false;
|
|
314
|
+
node.clientReconnectAttempts = 0;
|
|
315
|
+
node.status({ fill: "grey", shape: "ring", text: "niet verbonden" });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function clientSend(data) {
|
|
319
|
+
if (!node.clientSocket || !node.clientConnected) {
|
|
320
|
+
node.error("Client niet verbonden");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const formatted = formatInput(data, node.clientConfig.inputFormat, node.clientConfig.encoding);
|
|
326
|
+
let toSend = formatted;
|
|
327
|
+
|
|
328
|
+
if (node.clientConfig.delimiter) {
|
|
329
|
+
const delim = parseDelimiter(node.clientConfig.delimiter, node.clientConfig.encoding);
|
|
330
|
+
toSend = Buffer.concat([formatted, delim]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
node.clientSocket.write(toSend);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
node.error("Fout bij verzenden: " + err.message);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Server functionaliteit
|
|
340
|
+
function initServer() {
|
|
341
|
+
if (node.mode !== "server" && node.mode !== "both") {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (node.serverConfig.autoStart) {
|
|
346
|
+
serverStart();
|
|
347
|
+
} else {
|
|
348
|
+
node.status({ fill: "grey", shape: "ring", text: "server gestopt" });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function serverStart() {
|
|
353
|
+
if (node.server && node.serverListening) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (node.server) {
|
|
358
|
+
node.server.close();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const options = {
|
|
362
|
+
family: node.serverConfig.ipVersion === "6" ? 6 : 4
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
node.server = net.createServer(options, function(socket) {
|
|
366
|
+
const clientId = generateId();
|
|
367
|
+
const remoteAddress = socket.remoteAddress;
|
|
368
|
+
const remotePort = socket.remotePort;
|
|
369
|
+
|
|
370
|
+
// Check max connections
|
|
371
|
+
if (node.serverConfig.maxConnections > 0 && node.serverClients.size >= node.serverConfig.maxConnections) {
|
|
372
|
+
socket.destroy();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
node.serverClients.set(clientId, socket);
|
|
377
|
+
node.clientBuffers.set(clientId, Buffer.alloc(0));
|
|
378
|
+
|
|
379
|
+
if (node.serverConfig.keepAlive) {
|
|
380
|
+
socket.setKeepAlive(true);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
socket.setTimeout(node.serverConfig.connectionTimeout * 1000);
|
|
384
|
+
|
|
385
|
+
// Client connected message
|
|
386
|
+
const connectMsg = {
|
|
387
|
+
topic: "client/connected",
|
|
388
|
+
clientId: clientId,
|
|
389
|
+
remoteAddress: remoteAddress,
|
|
390
|
+
remotePort: remotePort
|
|
391
|
+
};
|
|
392
|
+
node.send(connectMsg);
|
|
393
|
+
|
|
394
|
+
// Update status
|
|
395
|
+
updateServerStatus();
|
|
396
|
+
|
|
397
|
+
socket.on('data', function(data) {
|
|
398
|
+
if (node.serverConfig.delimiter) {
|
|
399
|
+
let buffer = node.clientBuffers.get(clientId) || Buffer.alloc(0);
|
|
400
|
+
buffer = Buffer.concat([buffer, data]);
|
|
401
|
+
const delim = parseDelimiter(node.serverConfig.delimiter, node.serverConfig.encoding);
|
|
402
|
+
|
|
403
|
+
let offset = 0;
|
|
404
|
+
let index;
|
|
405
|
+
while ((index = buffer.indexOf(delim, offset)) !== -1) {
|
|
406
|
+
const message = buffer.slice(offset, index);
|
|
407
|
+
const formatted = formatOutput(message, node.serverConfig.outputFormat, node.serverConfig.encoding);
|
|
408
|
+
const msg = {
|
|
409
|
+
topic: "data",
|
|
410
|
+
payload: formatted,
|
|
411
|
+
clientId: clientId,
|
|
412
|
+
remoteAddress: remoteAddress,
|
|
413
|
+
remotePort: remotePort,
|
|
414
|
+
timestamp: new Date().toISOString()
|
|
415
|
+
};
|
|
416
|
+
node.send(msg);
|
|
417
|
+
offset = index + delim.length;
|
|
418
|
+
}
|
|
419
|
+
node.clientBuffers.set(clientId, buffer.slice(offset));
|
|
420
|
+
} else {
|
|
421
|
+
const formatted = formatOutput(data, node.serverConfig.outputFormat, node.serverConfig.encoding);
|
|
422
|
+
const msg = {
|
|
423
|
+
topic: "data",
|
|
424
|
+
payload: formatted,
|
|
425
|
+
clientId: clientId,
|
|
426
|
+
remoteAddress: remoteAddress,
|
|
427
|
+
remotePort: remotePort,
|
|
428
|
+
timestamp: new Date().toISOString()
|
|
429
|
+
};
|
|
430
|
+
node.send(msg);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
socket.on('close', function() {
|
|
435
|
+
node.serverClients.delete(clientId);
|
|
436
|
+
node.clientBuffers.delete(clientId);
|
|
437
|
+
|
|
438
|
+
const disconnectMsg = {
|
|
439
|
+
topic: "client/disconnected",
|
|
440
|
+
clientId: clientId
|
|
441
|
+
};
|
|
442
|
+
node.send(disconnectMsg);
|
|
443
|
+
|
|
444
|
+
updateServerStatus();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
socket.on('error', function(err) {
|
|
448
|
+
node.error("Client socket error: " + err.message);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
socket.on('timeout', function() {
|
|
452
|
+
// Timeout wordt gebruikt voor idle detection (optioneel)
|
|
453
|
+
// Als timeout is ingesteld, sluit dan de verbinding na idle periode
|
|
454
|
+
socket.destroy();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
node.server.on('error', function(err) {
|
|
459
|
+
node.serverListening = false;
|
|
460
|
+
node.status({ fill: "red", shape: "ring", text: `fout: ${err.message}` });
|
|
461
|
+
|
|
462
|
+
const msg = {
|
|
463
|
+
topic: "status",
|
|
464
|
+
payload: "error",
|
|
465
|
+
status: "error",
|
|
466
|
+
port: node.serverConfig.port,
|
|
467
|
+
error: err.message
|
|
468
|
+
};
|
|
469
|
+
node.send(msg);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
node.server.listen(node.serverConfig.port, node.serverConfig.bindAddress, function() {
|
|
473
|
+
node.serverListening = true;
|
|
474
|
+
updateServerStatus();
|
|
475
|
+
|
|
476
|
+
const msg = {
|
|
477
|
+
topic: "status",
|
|
478
|
+
payload: "listening",
|
|
479
|
+
status: "listening",
|
|
480
|
+
port: node.serverConfig.port
|
|
481
|
+
};
|
|
482
|
+
node.send(msg);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function serverStop() {
|
|
487
|
+
if (node.server) {
|
|
488
|
+
// Close all client connections
|
|
489
|
+
node.serverClients.forEach(function(socket, clientId) {
|
|
490
|
+
socket.destroy();
|
|
491
|
+
});
|
|
492
|
+
node.serverClients.clear();
|
|
493
|
+
node.clientBuffers.clear();
|
|
494
|
+
|
|
495
|
+
node.server.close(function() {
|
|
496
|
+
node.server = null;
|
|
497
|
+
node.serverListening = false;
|
|
498
|
+
node.status({ fill: "grey", shape: "ring", text: "server gestopt" });
|
|
499
|
+
|
|
500
|
+
const msg = {
|
|
501
|
+
topic: "status",
|
|
502
|
+
payload: "stopped",
|
|
503
|
+
status: "stopped",
|
|
504
|
+
port: node.serverConfig.port
|
|
505
|
+
};
|
|
506
|
+
node.send(msg);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function updateServerStatus() {
|
|
512
|
+
const clientCount = node.serverClients.size;
|
|
513
|
+
if (node.serverListening) {
|
|
514
|
+
if (clientCount > 0) {
|
|
515
|
+
node.status({ fill: "blue", shape: "dot", text: `luisteren op ${node.serverConfig.port} (${clientCount} clients)` });
|
|
516
|
+
} else {
|
|
517
|
+
node.status({ fill: "green", shape: "dot", text: `luisteren op ${node.serverConfig.port}` });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function serverSend(clientId, data) {
|
|
523
|
+
const socket = node.serverClients.get(clientId);
|
|
524
|
+
if (!socket) {
|
|
525
|
+
node.error(`Client ${clientId} niet gevonden`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
const formatted = formatInput(data, node.serverConfig.inputFormat, node.serverConfig.encoding);
|
|
531
|
+
let toSend = formatted;
|
|
532
|
+
|
|
533
|
+
if (node.serverConfig.delimiter) {
|
|
534
|
+
const delim = parseDelimiter(node.serverConfig.delimiter, node.serverConfig.encoding);
|
|
535
|
+
toSend = Buffer.concat([formatted, delim]);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
socket.write(toSend);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
node.error("Fout bij verzenden: " + err.message);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function serverBroadcast(data) {
|
|
545
|
+
node.serverClients.forEach(function(socket, clientId) {
|
|
546
|
+
serverSend(clientId, data);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Input message handler
|
|
551
|
+
node.on('input', function(msg) {
|
|
552
|
+
const topic = msg.topic || "";
|
|
553
|
+
const payload = msg.payload;
|
|
554
|
+
|
|
555
|
+
if (node.mode === "client" || node.mode === "both") {
|
|
556
|
+
if (topic === "connect") {
|
|
557
|
+
clientConnect();
|
|
558
|
+
return;
|
|
559
|
+
} else if (topic === "disconnect") {
|
|
560
|
+
clientDisconnect();
|
|
561
|
+
return;
|
|
562
|
+
} else if (topic === "send" || !topic) {
|
|
563
|
+
// Default: send data
|
|
564
|
+
clientSend(payload);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (node.mode === "server" || node.mode === "both") {
|
|
570
|
+
if (topic === "start") {
|
|
571
|
+
serverStart();
|
|
572
|
+
return;
|
|
573
|
+
} else if (topic === "stop") {
|
|
574
|
+
serverStop();
|
|
575
|
+
return;
|
|
576
|
+
} else if (topic === "send") {
|
|
577
|
+
if (msg.clientId) {
|
|
578
|
+
serverSend(msg.clientId, payload);
|
|
579
|
+
} else {
|
|
580
|
+
node.error("clientId vereist voor server send");
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
} else if (topic === "broadcast") {
|
|
584
|
+
serverBroadcast(payload);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Cleanup bij node close
|
|
591
|
+
node.on('close', function() {
|
|
592
|
+
if (node.clientSocket) {
|
|
593
|
+
clientDisconnect();
|
|
594
|
+
}
|
|
595
|
+
if (node.server) {
|
|
596
|
+
serverStop();
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// Initialize
|
|
601
|
+
initClient();
|
|
602
|
+
initServer();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
RED.nodes.registerType("tcp-client-server-avd", TCPClientServerNode);
|
|
606
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-tcp-client-server-avd",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node-RED node voor TCP client en/of server functionaliteit",
|
|
5
|
+
"main": "nodes/tcp-client-server-avd.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"node-red",
|
|
8
|
+
"tcp",
|
|
9
|
+
"client",
|
|
10
|
+
"server",
|
|
11
|
+
"socket"
|
|
12
|
+
],
|
|
13
|
+
"node-red": {
|
|
14
|
+
"nodes": {
|
|
15
|
+
"tcp-client-server-avd": "nodes/tcp-client-server-avd.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=12.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"files": [
|
|
25
|
+
"nodes/",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": ""
|
|
31
|
+
}
|
|
32
|
+
}
|