@atelieruldetehnologii/node-red-custome-solutions 0.62.22

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.
Files changed (107) hide show
  1. package/NOTICE +56 -0
  2. package/PROPRIETARY-LICENSE.txt +157 -0
  3. package/README.md +105 -0
  4. package/TERMS-AND-CONDITIONS.md +311 -0
  5. package/advantech-read/advantech-read.html +775 -0
  6. package/advantech-read/advantech-read.js +449 -0
  7. package/advantech-write/advantech-write.html +413 -0
  8. package/advantech-write/advantech-write.js +338 -0
  9. package/bess-control/bess-control.html +569 -0
  10. package/bess-control/bess-control.js +541 -0
  11. package/bess-control/lib/allocation.js +149 -0
  12. package/bess-control/lib/batteries.js +86 -0
  13. package/bess-control/lib/helpers.js +157 -0
  14. package/bess-control/lib/messages.js +131 -0
  15. package/bess-control/lib/runtime.js +156 -0
  16. package/carlo-gavazzi/carlo-gavazzi.html +4327 -0
  17. package/carlo-gavazzi/carlo-gavazzi.js +3081 -0
  18. package/control-export/control-export.html +3644 -0
  19. package/control-export/control-export.js +2192 -0
  20. package/control-reactive/control-reactive.html +3136 -0
  21. package/control-reactive/control-reactive.js +2124 -0
  22. package/fronius-emulator/README.md +258 -0
  23. package/fronius-emulator/fronius-emulator.html +170 -0
  24. package/fronius-emulator/fronius-emulator.js +520 -0
  25. package/fronius-emulator/lib/binary-codec.js +65 -0
  26. package/fronius-emulator/lib/fronius-register-map.js +185 -0
  27. package/fronius-emulator/lib/modbus-parser.js +61 -0
  28. package/fronius-emulator/lib/modbus-tcp-server.js +224 -0
  29. package/fronius-emulator/lib/validation.js +206 -0
  30. package/fronius-emulator/lib/virtual-meter.js +132 -0
  31. package/fronius-read/fronius-read.html +3944 -0
  32. package/fronius-read/fronius-read.js +1466 -0
  33. package/fronius-read/lib/inverter-profiles.js +711 -0
  34. package/fronius-read/lib/sunspec-core.js +303 -0
  35. package/fronius-read/lib/sunspec-model-parsers.js +845 -0
  36. package/fronius-write/fronius-write.html +1789 -0
  37. package/fronius-write/fronius-write.js +2531 -0
  38. package/influxdb/influxdb.html +1077 -0
  39. package/influxdb/influxdb.js +1487 -0
  40. package/janitza/janitza.html +2562 -0
  41. package/janitza/janitza.js +1500 -0
  42. package/janitza/lib/janitza-profiles.js +371 -0
  43. package/lib/a3-test-server.js +199 -0
  44. package/lib/adam-6000-profiles.js +173 -0
  45. package/lib/licensing/constants.js +103 -0
  46. package/lib/licensing/device-id.js +251 -0
  47. package/lib/licensing/errors.js +21 -0
  48. package/lib/licensing/license-client.js +377 -0
  49. package/lib/licensing/license-state.js +50 -0
  50. package/lib/licensing/node-guard.js +150 -0
  51. package/lib/licensing/package-integrity.js +167 -0
  52. package/lib/licensing/package-integrity.json +513 -0
  53. package/lib/licensing/public-key.pem +3 -0
  54. package/lib/licensing/public-key.pem.example +2 -0
  55. package/lib/licensing/public-keys.js +18 -0
  56. package/lib/licensing/storage.js +63 -0
  57. package/lib/licensing/token-verifier.js +84 -0
  58. package/lib/numbers.js +26 -0
  59. package/license/atelier-license.html +88 -0
  60. package/license/atelier-license.js +170 -0
  61. package/modbus-tcp-discover/modbus-tcp-discover.html +190 -0
  62. package/modbus-tcp-discover/modbus-tcp-discover.js +706 -0
  63. package/multi-battery/multi-battery.html +649 -0
  64. package/multi-battery/multi-battery.js +668 -0
  65. package/multi-grid/multi-grid.html +745 -0
  66. package/multi-grid/multi-grid.js +1392 -0
  67. package/multi-inverter/lib/engine-core.js +237 -0
  68. package/multi-inverter/multi-inverter.html +2674 -0
  69. package/multi-inverter/multi-inverter.js +1393 -0
  70. package/package.json +109 -0
  71. package/phoenix-contact/lib/eem-ma370-advanced.js +175 -0
  72. package/phoenix-contact/lib/eem-ma370.js +138 -0
  73. package/phoenix-contact/lib/integrations.js +149 -0
  74. package/phoenix-contact/lib/phoenix-contact-profile.js +35030 -0
  75. package/phoenix-contact/phoenix-contact.html +840 -0
  76. package/phoenix-contact/phoenix-contact.js +679 -0
  77. package/pv-generator/pv-generator.html +284 -0
  78. package/pv-generator/pv-generator.js +387 -0
  79. package/pylontech-read/pylontech-read.html +763 -0
  80. package/pylontech-read/pylontech-read.js +518 -0
  81. package/pylontech-write/pylontech-write.html +405 -0
  82. package/pylontech-write/pylontech-write.js +546 -0
  83. package/scada-aggregator/lib/aggregate.js +72 -0
  84. package/scada-aggregator/lib/groups.js +39 -0
  85. package/scada-aggregator/scada-aggregator.html +108 -0
  86. package/scada-aggregator/scada-aggregator.js +117 -0
  87. package/scada-server/lib/aggregator-map.js +58 -0
  88. package/scada-server/lib/encoding.js +235 -0
  89. package/scada-server/lib/modbus-server.js +492 -0
  90. package/scada-server/lib/object-path.js +18 -0
  91. package/scada-server/lib/register-map.js +288 -0
  92. package/scada-server/scada-server.html +1419 -0
  93. package/scada-server/scada-server.js +704 -0
  94. package/scripts/check-syntax.js +66 -0
  95. package/scripts/package-integrity.js +46 -0
  96. package/scripts/package-release.js +116 -0
  97. package/scripts/run-tests.js +2178 -0
  98. package/scripts/tgz-integrity.js +74 -0
  99. package/sma-read/lib/sunspec-core.js +83 -0
  100. package/sma-read/lib/sunspec-model-parsers.js +853 -0
  101. package/sma-read/sma-read.html +659 -0
  102. package/sma-read/sma-read.js +458 -0
  103. package/sma-write/sma-write.html +105 -0
  104. package/sma-write/sma-write.js +270 -0
  105. package/victron-read/lib/mqtt-state.js +176 -0
  106. package/victron-read/victron-read.html +1074 -0
  107. package/victron-read/victron-read.js +1222 -0
@@ -0,0 +1,1500 @@
1
+ module.exports = function (RED) {
2
+
3
+ "use strict";
4
+
5
+ const { runLicensedNode } = require("../lib/licensing/node-guard");
6
+
7
+
8
+ const net = require("net");
9
+
10
+ const janitzaProfiles = require("./lib/janitza-profiles");
11
+ const influxMeasurementRegistry = new Map();
12
+
13
+
14
+ const TRANSACTION_ID = 1;
15
+
16
+ const PROTOCOL_ID = 0;
17
+
18
+ const MESSAGE_LENGTH = 6;
19
+
20
+ const FUNCTION_CODE = 3;
21
+
22
+ const SOCKET_TIMEOUT_MS = 5000;
23
+ const MULTI_GRID_DEFAULT_FIELDS = [
24
+ "summary.power.total_w",
25
+ "summary.power.l1_w",
26
+ "summary.power.l2_w",
27
+ "summary.power.l3_w"
28
+ ];
29
+ const BESS_CONTROL_DEFAULT_FIELDS = MULTI_GRID_DEFAULT_FIELDS.slice();
30
+ const CONTROL_EXPORT_REACTIVE_DEFAULT_FIELDS = [
31
+ "summary.power.total_w",
32
+ "summary.power.l1_w",
33
+ "summary.power.l2_w",
34
+ "summary.power.l3_w",
35
+ "summary.reactive.total_var",
36
+ "summary.reactive.l1_var",
37
+ "summary.reactive.l2_var",
38
+ "summary.reactive.l3_var"
39
+ ];
40
+ const SCADA_DEFAULT_FIELDS = [
41
+ "summary.power.total_w",
42
+ "summary.reactive.total_var",
43
+ "summary.frequency_hz",
44
+ "summary.voltage.l1_l2_v",
45
+ "summary.voltage.l2_l3_v",
46
+ "summary.voltage.l3_l1_v"
47
+ ];
48
+
49
+
50
+ function toPositiveNumber(value, fallback) {
51
+
52
+ const n = Number(value);
53
+
54
+ return Number.isFinite(n) && n > 0 ? n : fallback;
55
+
56
+ }
57
+
58
+ function toPollInterval(value, fallback) {
59
+ const text = String(value || "").trim().toLowerCase();
60
+ if (text === "off" || text === "0") return 0;
61
+ const n = Number(value);
62
+ return Number.isFinite(n) && n > 0 ? n : fallback;
63
+ }
64
+
65
+
66
+ function getByPath(obj, path) {
67
+
68
+ if (!path || path === "full") {
69
+
70
+ return obj;
71
+
72
+ }
73
+
74
+ return path.split(".").reduce(function (acc, key) {
75
+
76
+ return acc !== null && acc !== undefined ? acc[key] : undefined;
77
+
78
+ }, obj);
79
+
80
+ }
81
+
82
+
83
+ function setByPath(obj, path, value) {
84
+
85
+ const parts = String(path || "").split(".");
86
+
87
+ let target = obj;
88
+
89
+
90
+ parts.forEach(function (part, index) {
91
+
92
+ if (index === parts.length - 1) {
93
+
94
+ target[part] = value;
95
+
96
+ return;
97
+
98
+ }
99
+
100
+ if (!target[part] || typeof target[part] !== "object") {
101
+
102
+ target[part] = {};
103
+
104
+ }
105
+
106
+ target = target[part];
107
+
108
+ });
109
+
110
+ }
111
+
112
+
113
+ function normalizePayloadFields(fields, allowEmpty) {
114
+
115
+ const keepEmpty = !!allowEmpty;
116
+
117
+
118
+ if (Array.isArray(fields) && fields.length) {
119
+
120
+ const normalized = Array.from(new Set(fields.map(function (f) {
121
+
122
+ return String(f || "").trim();
123
+
124
+ }).filter(Boolean)));
125
+
126
+
127
+ if (!normalized.length) {
128
+
129
+ return keepEmpty ? [] : ["full"];
130
+
131
+ }
132
+
133
+ if (normalized.includes("full")) {
134
+
135
+ return ["full"];
136
+
137
+ }
138
+
139
+ return normalized;
140
+
141
+ }
142
+
143
+
144
+ if (typeof fields === "string" && fields.trim()) {
145
+
146
+ const trimmed = fields.trim();
147
+
148
+ if (trimmed[0] === "[") {
149
+
150
+ try {
151
+
152
+ const parsed = JSON.parse(trimmed);
153
+
154
+ if (Array.isArray(parsed)) {
155
+
156
+ return normalizePayloadFields(parsed, keepEmpty);
157
+
158
+ }
159
+
160
+ } catch (e) {
161
+
162
+ // ignore invalid editor state
163
+
164
+ }
165
+
166
+ }
167
+
168
+ return [trimmed];
169
+
170
+ }
171
+
172
+
173
+ return keepEmpty ? [] : ["full"];
174
+
175
+ }
176
+
177
+
178
+ function sanitizePayloadFieldsForMode(fields, multiGrid, bessControl, controlExportReactive, scadaServer, influxDb, expertMode) {
179
+
180
+ const hasSpecialMode = !!(multiGrid || bessControl || controlExportReactive || scadaServer || influxDb);
181
+
182
+ const normalized = normalizePayloadFields(fields, !!(hasSpecialMode && expertMode));
183
+
184
+
185
+ if (multiGrid && normalized.includes("full")) {
186
+
187
+ return MULTI_GRID_DEFAULT_FIELDS.slice();
188
+
189
+ }
190
+
191
+ if (bessControl && normalized.includes("full")) {
192
+
193
+ return BESS_CONTROL_DEFAULT_FIELDS.slice();
194
+
195
+ }
196
+
197
+ if (controlExportReactive && normalized.includes("full")) {
198
+
199
+ return CONTROL_EXPORT_REACTIVE_DEFAULT_FIELDS.slice();
200
+
201
+ }
202
+
203
+ if (scadaServer && normalized.includes("full")) {
204
+
205
+ return SCADA_DEFAULT_FIELDS.slice();
206
+
207
+ }
208
+
209
+ if (influxDb && normalized.includes("full")) {
210
+
211
+ return ["full"];
212
+
213
+ }
214
+
215
+ return normalized;
216
+
217
+ }
218
+
219
+
220
+ function getPayloadLabel(path) {
221
+
222
+ const labels = {
223
+
224
+ "full": "Toate valorile",
225
+
226
+ "summary.voltage.l1_n_v": "Tensiune L1-N",
227
+
228
+ "summary.voltage.l2_n_v": "Tensiune L2-N",
229
+
230
+ "summary.voltage.l3_n_v": "Tensiune L3-N",
231
+
232
+ "summary.voltage.l1_l2_v": "Tensiune L1-L2",
233
+
234
+ "summary.voltage.l2_l3_v": "Tensiune L2-L3",
235
+
236
+ "summary.voltage.l3_l1_v": "Tensiune L3-L1",
237
+
238
+ "summary.current.l1_a": "Curent L1",
239
+
240
+ "summary.current.l2_a": "Curent L2",
241
+
242
+ "summary.current.l3_a": "Curent L3",
243
+
244
+ "summary.current.neutral_a": "Curent neutral",
245
+
246
+ "summary.power.l1_w": "Putere L1",
247
+
248
+ "summary.power.l2_w": "Putere L2",
249
+
250
+ "summary.power.l3_w": "Putere L3",
251
+
252
+ "summary.power.total_w": "Putere totala",
253
+
254
+ "summary.apparent.l1_va": "Putere aparenta L1",
255
+
256
+ "summary.apparent.l2_va": "Putere aparenta L2",
257
+
258
+ "summary.apparent.l3_va": "Putere aparenta L3",
259
+
260
+ "summary.apparent.total_va": "Putere aparenta totala",
261
+
262
+ "summary.reactive.l1_var": "Putere reactiva L1",
263
+
264
+ "summary.reactive.l2_var": "Putere reactiva L2",
265
+
266
+ "summary.reactive.l3_var": "Putere reactiva L3",
267
+
268
+ "summary.reactive.total_var": "Putere reactiva totala",
269
+
270
+ "summary.power_factor.l1": "Cos phi L1",
271
+
272
+ "summary.power_factor.l2": "Cos phi L2",
273
+
274
+ "summary.power_factor.l3": "Cos phi L3",
275
+
276
+ "summary.frequency_hz": "Frecventa",
277
+
278
+ "summary.phase_sequence": "Ordine faze",
279
+
280
+ "summary.energy.import_kwh": "Energie importata",
281
+
282
+ "summary.energy.export_kwh": "Energie exportata",
283
+
284
+ "summary.energy.l1_kwh": "Energie L1",
285
+
286
+ "summary.energy.l2_kwh": "Energie L2",
287
+
288
+ "summary.energy.l3_kwh": "Energie L3",
289
+
290
+ "summary.energy.apparent_kvah": "Energie aparenta",
291
+
292
+ "summary.energy.reactive_import_kvarh": "Energie reactiva",
293
+
294
+ "summary.energy.reactive_inductive_kvarh": "Energie reactiva inductiva",
295
+
296
+ "summary.energy.reactive_capacitive_kvarh": "Energie reactiva capacitiva",
297
+
298
+ "summary.thd.voltage.l1_pct": "THD tensiune L1",
299
+
300
+ "summary.thd.voltage.l2_pct": "THD tensiune L2",
301
+
302
+ "summary.thd.voltage.l3_pct": "THD tensiune L3",
303
+
304
+ "summary.thd.current.l1_pct": "THD curent L1",
305
+
306
+ "summary.thd.current.l2_pct": "THD curent L2",
307
+
308
+ "summary.thd.current.l3_pct": "THD curent L3"
309
+
310
+ };
311
+
312
+ return labels[path] || path;
313
+
314
+ }
315
+
316
+
317
+ function getPayloadUnit(path) {
318
+
319
+ const units = {
320
+
321
+ "summary.voltage.l1_n_v": "V",
322
+
323
+ "summary.voltage.l2_n_v": "V",
324
+
325
+ "summary.voltage.l3_n_v": "V",
326
+
327
+ "summary.voltage.l1_l2_v": "V",
328
+
329
+ "summary.voltage.l2_l3_v": "V",
330
+
331
+ "summary.voltage.l3_l1_v": "V",
332
+
333
+ "summary.current.l1_a": "A",
334
+
335
+ "summary.current.l2_a": "A",
336
+
337
+ "summary.current.l3_a": "A",
338
+
339
+ "summary.current.neutral_a": "A",
340
+
341
+ "summary.power.l1_w": "W",
342
+
343
+ "summary.power.l2_w": "W",
344
+
345
+ "summary.power.l3_w": "W",
346
+
347
+ "summary.power.total_w": "W",
348
+
349
+ "summary.apparent.l1_va": "VA",
350
+
351
+ "summary.apparent.l2_va": "VA",
352
+
353
+ "summary.apparent.l3_va": "VA",
354
+
355
+ "summary.apparent.total_va": "VA",
356
+
357
+ "summary.reactive.l1_var": "var",
358
+
359
+ "summary.reactive.l2_var": "var",
360
+
361
+ "summary.reactive.l3_var": "var",
362
+
363
+ "summary.reactive.total_var": "var",
364
+
365
+ "summary.frequency_hz": "Hz",
366
+
367
+ "summary.energy.import_kwh": "kWh",
368
+
369
+ "summary.energy.export_kwh": "kWh",
370
+
371
+ "summary.energy.l1_kwh": "kWh",
372
+
373
+ "summary.energy.l2_kwh": "kWh",
374
+
375
+ "summary.energy.l3_kwh": "kWh",
376
+
377
+ "summary.energy.apparent_kvah": "kVAh",
378
+
379
+ "summary.energy.reactive_import_kvarh": "kvarh",
380
+
381
+ "summary.energy.reactive_inductive_kvarh": "kvarh",
382
+
383
+ "summary.energy.reactive_capacitive_kvarh": "kvarh",
384
+
385
+ "summary.thd.voltage.l1_pct": "%",
386
+
387
+ "summary.thd.voltage.l2_pct": "%",
388
+
389
+ "summary.thd.voltage.l3_pct": "%",
390
+
391
+ "summary.thd.current.l1_pct": "%",
392
+
393
+ "summary.thd.current.l2_pct": "%",
394
+
395
+ "summary.thd.current.l3_pct": "%"
396
+
397
+ };
398
+
399
+ return units[path] || "";
400
+
401
+ }
402
+
403
+
404
+ function getCompactPayloadKey(path) {
405
+
406
+ const mapping = {
407
+
408
+ "summary.power.total_w": "power_w",
409
+
410
+ "summary.reactive.total_var": "reactive_total_var"
411
+
412
+ };
413
+
414
+ return mapping[path] || path.replace(/^summary\./, "").replace(/\./g, "_");
415
+
416
+ }
417
+
418
+
419
+ function getScadaPayloadKey(path) {
420
+
421
+ const mapping = {
422
+
423
+ "summary.power.total_w": "active_power_w",
424
+
425
+ "summary.reactive.total_var": "reactive_power_var",
426
+
427
+ "summary.frequency_hz": "frequency_hz",
428
+
429
+ "summary.voltage.l1_l2_v": "voltage_l1_l2_v",
430
+
431
+ "summary.voltage.l2_l3_v": "voltage_l2_l3_v",
432
+
433
+ "summary.voltage.l3_l1_v": "voltage_l1_l3_v",
434
+
435
+ "summary.power.l1_w": "power_l1_w",
436
+
437
+ "summary.power.l2_w": "power_l2_w",
438
+
439
+ "summary.power.l3_w": "power_l3_w",
440
+
441
+ "summary.current.l1_a": "current_l1_a",
442
+
443
+ "summary.current.l2_a": "current_l2_a",
444
+
445
+ "summary.current.l3_a": "current_l3_a",
446
+
447
+ "summary.voltage.l1_n_v": "voltage_l1_n_v",
448
+
449
+ "summary.voltage.l2_n_v": "voltage_l2_n_v",
450
+
451
+ "summary.voltage.l3_n_v": "voltage_l3_n_v"
452
+
453
+ };
454
+
455
+ return mapping[path] || getCompactPayloadKey(path);
456
+
457
+ }
458
+
459
+
460
+ function flattenNumericFields(source, prefix, target) {
461
+
462
+ Object.keys(source || {}).forEach(function (key) {
463
+
464
+ const value = source[key];
465
+ const nextKey = prefix ? prefix + "_" + key : key;
466
+
467
+ if (typeof value === "number" && Number.isFinite(value)) {
468
+ target[nextKey] = value;
469
+ return;
470
+ }
471
+
472
+ if (typeof value === "boolean") {
473
+ target[nextKey] = value;
474
+ return;
475
+ }
476
+
477
+ if (value && typeof value === "object" && !Array.isArray(value)) {
478
+ flattenNumericFields(value, nextKey, target);
479
+ }
480
+ });
481
+
482
+ }
483
+
484
+
485
+ function registerInfluxMeasurement(nodeId, identityKey) {
486
+
487
+ const key = String(identityKey || "").trim().toLowerCase();
488
+ if (!key) {
489
+ return;
490
+ }
491
+
492
+ if (!influxMeasurementRegistry.has(key)) {
493
+ influxMeasurementRegistry.set(key, new Set());
494
+ }
495
+
496
+ influxMeasurementRegistry.get(key).add(nodeId);
497
+
498
+ }
499
+
500
+
501
+ function unregisterInfluxMeasurement(nodeId, identityKey) {
502
+
503
+ const key = String(identityKey || "").trim().toLowerCase();
504
+ if (!key || !influxMeasurementRegistry.has(key)) {
505
+ return;
506
+ }
507
+
508
+ const ids = influxMeasurementRegistry.get(key);
509
+ ids.delete(nodeId);
510
+
511
+ if (!ids.size) {
512
+ influxMeasurementRegistry.delete(key);
513
+ }
514
+
515
+ }
516
+
517
+
518
+ function hasDuplicateInfluxMeasurement(nodeId, identityKey) {
519
+
520
+ const key = String(identityKey || "").trim().toLowerCase();
521
+ if (!key || !influxMeasurementRegistry.has(key)) {
522
+ return false;
523
+ }
524
+
525
+ const ids = influxMeasurementRegistry.get(key);
526
+ return ids.size > 1 || (ids.size === 1 && !ids.has(nodeId));
527
+
528
+ }
529
+
530
+
531
+ function formatSingleStatus(path, value) {
532
+
533
+ const unit = getPayloadUnit(path);
534
+
535
+ if (value === undefined || value === null) {
536
+
537
+ return getPayloadLabel(path) + ": n/a";
538
+
539
+ }
540
+
541
+ return getPayloadLabel(path) + ": " + String(value) + (unit ? " " + unit : "");
542
+
543
+ }
544
+
545
+
546
+ function dumpBuffer(buffer) {
547
+
548
+ return buffer.toString("hex").toUpperCase();
549
+
550
+ }
551
+
552
+
553
+ function parsePhaseSequence(raw) {
554
+
555
+ if (raw === 1) {
556
+
557
+ return "right";
558
+
559
+ }
560
+
561
+ if (raw === -1) {
562
+
563
+ return "left";
564
+
565
+ }
566
+
567
+ if (raw === 0) {
568
+
569
+ return "none";
570
+
571
+ }
572
+
573
+ return String(raw);
574
+
575
+ }
576
+
577
+
578
+ function parseMeterResponse(buffer, expectedUnitId, profile) {
579
+
580
+ const modelProfile = profile || janitzaProfiles.getProfile();
581
+
582
+ const expectedByteCount = modelProfile.registerCount * 2;
583
+
584
+
585
+ if (!Buffer.isBuffer(buffer) || buffer.length < 9) {
586
+
587
+ throw new Error("Raspuns Modbus prea scurt");
588
+
589
+ }
590
+
591
+
592
+ const transactionId = buffer.readUInt16BE(0);
593
+
594
+ if (transactionId !== TRANSACTION_ID) {
595
+
596
+ throw new Error("Transaction ID invalid: " + transactionId);
597
+
598
+ }
599
+
600
+
601
+ const protocolId = buffer.readUInt16BE(2);
602
+
603
+ if (protocolId !== PROTOCOL_ID) {
604
+
605
+ throw new Error("Protocol ID invalid: " + protocolId);
606
+
607
+ }
608
+
609
+
610
+ const messageLength = buffer.readUInt16BE(4);
611
+
612
+ if (buffer.length < 6 + messageLength) {
613
+
614
+ throw new Error("Pachet Modbus incomplet");
615
+
616
+ }
617
+
618
+
619
+ const unitId = buffer.readUInt8(6);
620
+
621
+ if (unitId !== expectedUnitId) {
622
+
623
+ throw new Error("Unit ID invalid: " + unitId);
624
+
625
+ }
626
+
627
+
628
+ const functionCode = buffer.readUInt8(7);
629
+
630
+ if (functionCode === (FUNCTION_CODE | 0x80)) {
631
+
632
+ const exceptionCode = buffer.length > 8 ? buffer.readUInt8(8) : -1;
633
+
634
+ throw new Error("Modbus exception code: " + exceptionCode);
635
+
636
+ }
637
+
638
+ if (functionCode !== FUNCTION_CODE) {
639
+
640
+ throw new Error("Function code invalid: " + functionCode);
641
+
642
+ }
643
+
644
+
645
+ const byteCount = buffer.readUInt8(8);
646
+
647
+ if (byteCount !== expectedByteCount) {
648
+
649
+ throw new Error("Byte count invalid: " + byteCount);
650
+
651
+ }
652
+
653
+
654
+ const expectedPayloadEnd = 9 + byteCount;
655
+
656
+ if (buffer.length < expectedPayloadEnd) {
657
+
658
+ throw new Error("Date Modbus incomplete");
659
+
660
+ }
661
+
662
+
663
+ const data = buffer.slice(9, expectedPayloadEnd);
664
+
665
+
666
+ function readFloat(address) {
667
+
668
+ return data.readFloatBE((address - modelProfile.startAddress) * 2);
669
+
670
+ }
671
+
672
+
673
+ function whToKwh(value) {
674
+
675
+ return value / 1000;
676
+
677
+ }
678
+
679
+
680
+ function varhToKvarh(value) {
681
+
682
+ return value / 1000;
683
+
684
+ }
685
+
686
+
687
+ return {
688
+
689
+ summary: {
690
+
691
+ voltage: {
692
+
693
+ l1_n_v: readFloat(19000),
694
+
695
+ l2_n_v: readFloat(19002),
696
+
697
+ l3_n_v: readFloat(19004),
698
+
699
+ l1_l2_v: readFloat(19006),
700
+
701
+ l2_l3_v: readFloat(19008),
702
+
703
+ l3_l1_v: readFloat(19010)
704
+
705
+ },
706
+
707
+ current: {
708
+
709
+ l1_a: readFloat(19012),
710
+
711
+ l2_a: readFloat(19014),
712
+
713
+ l3_a: readFloat(19016),
714
+
715
+ neutral_a: readFloat(19018)
716
+
717
+ },
718
+
719
+ power: {
720
+
721
+ l1_w: readFloat(19020),
722
+
723
+ l2_w: readFloat(19022),
724
+
725
+ l3_w: readFloat(19024),
726
+
727
+ total_w: readFloat(19026)
728
+
729
+ },
730
+
731
+ apparent: {
732
+
733
+ l1_va: readFloat(19028),
734
+
735
+ l2_va: readFloat(19030),
736
+
737
+ l3_va: readFloat(19032),
738
+
739
+ total_va: readFloat(19034)
740
+
741
+ },
742
+
743
+ reactive: {
744
+
745
+ l1_var: readFloat(19036),
746
+
747
+ l2_var: readFloat(19038),
748
+
749
+ l3_var: readFloat(19040),
750
+
751
+ total_var: readFloat(19042)
752
+
753
+ },
754
+
755
+ power_factor: {
756
+
757
+ l1: readFloat(19044),
758
+
759
+ l2: readFloat(19046),
760
+
761
+ l3: readFloat(19048)
762
+
763
+ },
764
+
765
+ frequency_hz: readFloat(19050),
766
+
767
+ phase_sequence: parsePhaseSequence(readFloat(19052)),
768
+
769
+ energy: {
770
+
771
+ l1_kwh: whToKwh(readFloat(19054)),
772
+
773
+ l2_kwh: whToKwh(readFloat(19056)),
774
+
775
+ l3_kwh: whToKwh(readFloat(19058)),
776
+
777
+ import_kwh: whToKwh(readFloat(19062) + readFloat(19064) + readFloat(19066)),
778
+
779
+ export_kwh: whToKwh(readFloat(19076)),
780
+
781
+ apparent_kvah: whToKwh(readFloat(19084)),
782
+
783
+ reactive_import_kvarh: varhToKvarh(readFloat(19092)),
784
+
785
+ reactive_inductive_kvarh: varhToKvarh(readFloat(19100)),
786
+
787
+ reactive_capacitive_kvarh: varhToKvarh(readFloat(19108))
788
+
789
+ },
790
+
791
+ thd: {
792
+
793
+ voltage: {
794
+
795
+ l1_pct: readFloat(19110),
796
+
797
+ l2_pct: readFloat(19112),
798
+
799
+ l3_pct: readFloat(19114)
800
+
801
+ },
802
+
803
+ current: {
804
+
805
+ l1_pct: readFloat(19116),
806
+
807
+ l2_pct: readFloat(19118),
808
+
809
+ l3_pct: readFloat(19120)
810
+
811
+ }
812
+
813
+ }
814
+
815
+ },
816
+
817
+ meta: {
818
+
819
+ model: modelProfile.id,
820
+
821
+ device: modelProfile.deviceName,
822
+
823
+ unit_id: expectedUnitId,
824
+
825
+ register_count: modelProfile.registerCount,
826
+
827
+ start_address: modelProfile.startAddress,
828
+
829
+ timestamp: new Date().toISOString(),
830
+
831
+ raw_hex: dumpBuffer(buffer.slice(0, expectedPayloadEnd))
832
+
833
+ }
834
+
835
+ };
836
+
837
+ }
838
+
839
+
840
+ function buildRequestBuffer(unitId, startAddress, registerCount) {
841
+
842
+ const buffer = Buffer.alloc(12);
843
+
844
+ buffer.writeUInt16BE(TRANSACTION_ID, 0);
845
+
846
+ buffer.writeUInt16BE(PROTOCOL_ID, 2);
847
+
848
+ buffer.writeUInt16BE(MESSAGE_LENGTH, 4);
849
+
850
+ buffer.writeUInt8(unitId, 6);
851
+
852
+ buffer.writeUInt8(FUNCTION_CODE, 7);
853
+
854
+ buffer.writeUInt16BE(startAddress, 8);
855
+
856
+ buffer.writeUInt16BE(registerCount, 10);
857
+
858
+ return buffer;
859
+
860
+ }
861
+
862
+
863
+ function JanitzaNode(config) {
864
+
865
+ RED.nodes.createNode(this, config);
866
+
867
+ const node = this;
868
+
869
+ runLicensedNode(node, RED, config, function startLicensedRuntime() {
870
+
871
+
872
+ node.name = String(config.name || "").trim();
873
+
874
+ node.model = String(config.model || janitzaProfiles.DEFAULT_PROFILE_ID).trim();
875
+
876
+ node.profile = janitzaProfiles.getProfile(node.model);
877
+
878
+ node.host = String(config.host || "").trim();
879
+
880
+ node.port = toPositiveNumber(config.port, 502);
881
+
882
+ node.unitId = toPositiveNumber(config.unitId, 1);
883
+
884
+ node.timestep = toPollInterval(config.timestep, 1000);
885
+
886
+ node.expertMode = !!config.expertMode;
887
+
888
+ node.payloadFields = sanitizePayloadFieldsForMode(config.payloadFields, !!config.multiGrid, !!config.bessControl, !!config.controlExportReactive, !!config.scadaServer, !!config.influxDb, node.expertMode);
889
+
890
+ node.multiGrid = !!config.multiGrid;
891
+
892
+ node.bessControl = !!config.bessControl;
893
+
894
+ node.controlExportReactive = !!config.controlExportReactive;
895
+
896
+ node.scadaServer = !!config.scadaServer;
897
+
898
+ node.influxDb = !!config.influxDb;
899
+
900
+ if (node.multiGrid) {
901
+ node.bessControl = false; node.controlExportReactive = false; node.scadaServer = false;
902
+ } else if (node.bessControl) {
903
+ node.controlExportReactive = false; node.scadaServer = false;
904
+ } else if (node.controlExportReactive) {
905
+ node.scadaServer = false;
906
+ }
907
+
908
+ node.influxMeasurement = String(config.influxMeasurement || "").trim();
909
+ node.influxNode = String(config.influxNode || "").trim();
910
+
911
+ node.meterId = String(config.meterId || "").trim();
912
+
913
+
914
+ let intervalHandle = null;
915
+
916
+ let startupHandle = null;
917
+
918
+ let running = false;
919
+
920
+ let closed = false;
921
+ let registeredInfluxIdentityKey = "";
922
+
923
+
924
+ function getEffectiveMeterId() {
925
+
926
+ return node.meterId || ("janitza" + String(node.unitId));
927
+
928
+ }
929
+
930
+
931
+ function getInfluxMeasurement() {
932
+
933
+ return node.influxMeasurement || getEffectiveMeterId();
934
+
935
+ }
936
+
937
+
938
+ function getInfluxIdentityKey() {
939
+
940
+ return String(getInfluxMeasurement() || "").trim().toLowerCase() + "::" + String(getEffectiveMeterId() || "").trim().toLowerCase();
941
+
942
+ }
943
+
944
+
945
+ function hasInfluxMeasurementConflict() {
946
+
947
+ return node.influxDb && hasDuplicateInfluxMeasurement(node.id, getInfluxIdentityKey());
948
+
949
+ }
950
+
951
+
952
+ function buildInfluxFields(baseParsed, selectedFields) {
953
+
954
+ const fields = {};
955
+
956
+ if (selectedFields.includes("full")) {
957
+ flattenNumericFields(baseParsed && baseParsed.summary ? baseParsed.summary : {}, "", fields);
958
+ return fields;
959
+ }
960
+
961
+ selectedFields.forEach(function (field) {
962
+ const value = getByPath(baseParsed, field);
963
+ if (typeof value === "number" && Number.isFinite(value)) {
964
+ fields[getCompactPayloadKey(field)] = value;
965
+ return;
966
+ }
967
+
968
+ if (typeof value === "boolean") {
969
+ fields[getCompactPayloadKey(field)] = value;
970
+ return;
971
+ }
972
+
973
+ if (value && typeof value === "object" && !Array.isArray(value)) {
974
+ flattenNumericFields(value, getCompactPayloadKey(field), fields);
975
+ }
976
+ });
977
+
978
+ return fields;
979
+
980
+ }
981
+
982
+
983
+ function buildInfluxMessage(baseParsed, selectedFields) {
984
+
985
+ const timestamp = baseParsed && baseParsed.meta ? baseParsed.meta.timestamp : new Date().toISOString();
986
+ const meterId = getEffectiveMeterId();
987
+ const fields = buildInfluxFields(baseParsed, selectedFields);
988
+
989
+ return {
990
+ topic: meterId,
991
+ meter_id: meterId,
992
+ payload_field: selectedFields.includes("full") ? "full" : (selectedFields[0] || "full"),
993
+ janitza: baseParsed,
994
+ timestamp: timestamp,
995
+ payload: fields,
996
+ influx: {
997
+ measurement: getInfluxMeasurement(),
998
+ tags: {
999
+ device_type: "janitza",
1000
+ meter_id: meterId,
1001
+ host: node.host,
1002
+ unit_id: String(node.unitId),
1003
+ model: node.profile && node.profile.id ? node.profile.id : ""
1004
+ },
1005
+ fields: fields,
1006
+ timestamp: timestamp
1007
+ }
1008
+ };
1009
+
1010
+ }
1011
+
1012
+
1013
+ function buildSingleMessage(baseParsed, field, value) {
1014
+
1015
+ const outMsg = {
1016
+
1017
+ payload_field: field,
1018
+
1019
+ janitza: baseParsed,
1020
+
1021
+ timestamp: baseParsed && baseParsed.meta ? baseParsed.meta.timestamp : new Date().toISOString()
1022
+
1023
+ };
1024
+
1025
+ outMsg.ip = node.host;
1026
+ outMsg.host = node.host;
1027
+ outMsg.port = node.port;
1028
+ outMsg.unit_id = node.unitId;
1029
+ outMsg.device_type = "janitza";
1030
+ outMsg.model = node.profile && node.profile.id ? node.profile.id : "";
1031
+
1032
+
1033
+ if (node.multiGrid || node.bessControl || node.controlExportReactive || node.scadaServer) {
1034
+
1035
+ outMsg.topic = getEffectiveMeterId();
1036
+
1037
+ outMsg.meter_id = getEffectiveMeterId();
1038
+
1039
+
1040
+ if (field === "full") {
1041
+
1042
+ outMsg.payload = baseParsed;
1043
+
1044
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
1045
+
1046
+ outMsg.payload = value;
1047
+
1048
+ } else {
1049
+
1050
+ outMsg.payload = {};
1051
+
1052
+ outMsg.payload[node.scadaServer ? getScadaPayloadKey(field) : getCompactPayloadKey(field)] = value;
1053
+
1054
+ }
1055
+
1056
+ } else {
1057
+
1058
+ outMsg.topic = getPayloadLabel(field);
1059
+
1060
+ outMsg.payload = value;
1061
+
1062
+ }
1063
+
1064
+
1065
+ return outMsg;
1066
+
1067
+ }
1068
+
1069
+
1070
+ function setDisconnectedStatus(message) {
1071
+
1072
+ node.status({ fill: "red", shape: "ring", text: message });
1073
+
1074
+ }
1075
+
1076
+
1077
+ function setReadingStatus() {
1078
+
1079
+ node.status({ fill: "yellow", shape: "dot", text: "citire..." });
1080
+
1081
+ }
1082
+
1083
+
1084
+ function setStatusForSelection(selectedFields, parsed) {
1085
+
1086
+ if (selectedFields.includes("full")) {
1087
+
1088
+ node.status({ fill: "green", shape: "dot", text: "Toate valorile" });
1089
+
1090
+ return;
1091
+
1092
+ }
1093
+
1094
+
1095
+ if (selectedFields.length === 1) {
1096
+
1097
+ const field = selectedFields[0];
1098
+
1099
+ node.status({ fill: "green", shape: "dot", text: formatSingleStatus(field, getByPath(parsed, field)) });
1100
+
1101
+ return;
1102
+
1103
+ }
1104
+
1105
+
1106
+ node.status({ fill: "green", shape: "dot", text: String(selectedFields.length) + " valori selectate" });
1107
+
1108
+ }
1109
+
1110
+
1111
+ function readRegisterBlock(startAddress, registerCount) {
1112
+
1113
+ return new Promise(function (resolve, reject) {
1114
+
1115
+ const socket = new net.Socket();
1116
+
1117
+ let chunks = [];
1118
+
1119
+ let finished = false;
1120
+
1121
+
1122
+ function done(err, result) {
1123
+
1124
+ if (finished) {
1125
+
1126
+ return;
1127
+
1128
+ }
1129
+
1130
+ finished = true;
1131
+
1132
+
1133
+ try {
1134
+
1135
+ socket.destroy();
1136
+
1137
+ } catch (e) {
1138
+
1139
+ // ignore socket cleanup errors
1140
+
1141
+ }
1142
+
1143
+
1144
+ if (err) {
1145
+
1146
+ reject(err);
1147
+
1148
+ } else {
1149
+
1150
+ resolve(result);
1151
+
1152
+ }
1153
+
1154
+ }
1155
+
1156
+
1157
+ socket.setTimeout(SOCKET_TIMEOUT_MS);
1158
+
1159
+
1160
+ socket.connect(node.port, node.host, function () {
1161
+
1162
+ try {
1163
+
1164
+ socket.write(buildRequestBuffer(node.unitId, startAddress, registerCount));
1165
+
1166
+ } catch (err) {
1167
+
1168
+ done(err);
1169
+
1170
+ }
1171
+
1172
+ });
1173
+
1174
+
1175
+ socket.on("data", function (data) {
1176
+
1177
+ chunks.push(data);
1178
+
1179
+ const total = Buffer.concat(chunks);
1180
+
1181
+
1182
+ if (total.length >= 9) {
1183
+
1184
+ const functionCode = total.readUInt8(7);
1185
+
1186
+ if (functionCode === (FUNCTION_CODE | 0x80)) {
1187
+
1188
+ try {
1189
+
1190
+ parseMeterResponse(total, node.unitId, node.profile);
1191
+
1192
+ } catch (err) {
1193
+
1194
+ done(err);
1195
+
1196
+ }
1197
+
1198
+ return;
1199
+
1200
+ }
1201
+
1202
+
1203
+ const byteCount = total.readUInt8(8);
1204
+
1205
+ const expectedLength = 9 + byteCount;
1206
+
1207
+ if (total.length >= expectedLength) {
1208
+
1209
+ try {
1210
+
1211
+ if (byteCount !== registerCount * 2) {
1212
+
1213
+ throw new Error("Byte count invalid: " + byteCount);
1214
+
1215
+ }
1216
+
1217
+ done(null, {
1218
+
1219
+ buffer: total.slice(0, expectedLength),
1220
+
1221
+ data: total.slice(9, expectedLength)
1222
+
1223
+ });
1224
+
1225
+ } catch (err) {
1226
+
1227
+ done(err);
1228
+
1229
+ }
1230
+
1231
+ }
1232
+
1233
+ }
1234
+
1235
+ });
1236
+
1237
+
1238
+ socket.on("timeout", function () {
1239
+
1240
+ done(new Error("conexiune pierduta: timeout"));
1241
+
1242
+ });
1243
+
1244
+
1245
+ socket.on("error", function (err) {
1246
+
1247
+ done(new Error("conexiune pierduta: " + err.message));
1248
+
1249
+ });
1250
+
1251
+
1252
+ socket.on("close", function () {
1253
+
1254
+ if (!finished && chunks.length === 0) {
1255
+
1256
+ done(new Error("conexiune pierduta: fara raspuns"));
1257
+
1258
+ }
1259
+
1260
+ });
1261
+
1262
+ });
1263
+
1264
+ }
1265
+
1266
+
1267
+ async function readMeter() {
1268
+
1269
+ const base = await readRegisterBlock(node.profile.startAddress, node.profile.registerCount);
1270
+
1271
+ const parsed = parseMeterResponse(base.buffer, node.unitId, node.profile);
1272
+
1273
+ const selectedFields = normalizePayloadFields(
1274
+
1275
+ node.payloadFields,
1276
+
1277
+ !!(node.expertMode && (node.multiGrid || node.bessControl || node.controlExportReactive || node.scadaServer || node.influxDb))
1278
+
1279
+ );
1280
+
1281
+
1282
+ if (!selectedFields.includes("full")) {
1283
+
1284
+ const ranges = janitzaProfiles.buildExtendedReadRanges(selectedFields, node.profile, normalizePayloadFields);
1285
+
1286
+
1287
+ for (let i = 0; i < ranges.length; i += 1) {
1288
+
1289
+ const range = ranges[i];
1290
+
1291
+ const block = await readRegisterBlock(range.start, range.count);
1292
+
1293
+
1294
+ range.items.forEach(function (item) {
1295
+
1296
+ setByPath(parsed, item.field, janitzaProfiles.readValueFromRegisterData(block.data, range.start, item.definition));
1297
+
1298
+ });
1299
+
1300
+ }
1301
+
1302
+ }
1303
+
1304
+
1305
+ return parsed;
1306
+
1307
+ }
1308
+
1309
+
1310
+ function sendParsedResult(parsed) {
1311
+
1312
+ const selectedFields = normalizePayloadFields(
1313
+
1314
+ node.payloadFields,
1315
+
1316
+ !!(node.expertMode && (node.multiGrid || node.bessControl || node.controlExportReactive || node.scadaServer || node.influxDb))
1317
+
1318
+ );
1319
+
1320
+
1321
+ setStatusForSelection(selectedFields, parsed);
1322
+
1323
+
1324
+ const secondaryInflux = node.influxDb && !!(node.multiGrid || node.bessControl || node.controlExportReactive || node.scadaServer);
1325
+ let influxMessage = null;
1326
+
1327
+ if (node.influxDb) {
1328
+
1329
+ if (hasInfluxMeasurementConflict()) {
1330
+ node.status({
1331
+ fill: "red",
1332
+ shape: "ring",
1333
+ text: "meter_id+measurement duplicat"
1334
+ });
1335
+ node.error("Janitza InfluxDB: combinatie duplicata Meter ID + Measurement ('" + getEffectiveMeterId() + "' @ '" + getInfluxMeasurement() + "'). Configureaza un Meter ID unic pentru acelasi measurement.");
1336
+ return;
1337
+ }
1338
+
1339
+ influxMessage = buildInfluxMessage(parsed, selectedFields);
1340
+ if (!secondaryInflux) {
1341
+ node.send(influxMessage);
1342
+ return;
1343
+ }
1344
+ }
1345
+
1346
+
1347
+ if (selectedFields.includes("full")) {
1348
+
1349
+ node.send(buildSingleMessage(parsed, "full", parsed));
1350
+
1351
+ return;
1352
+
1353
+ }
1354
+
1355
+
1356
+ if (node.multiGrid || node.bessControl || node.controlExportReactive || node.scadaServer) {
1357
+
1358
+ const aggregatedPayload = {};
1359
+
1360
+ selectedFields.forEach(function (field) {
1361
+
1362
+ aggregatedPayload[node.scadaServer ? getScadaPayloadKey(field) : getCompactPayloadKey(field)] = getByPath(parsed, field);
1363
+
1364
+ });
1365
+
1366
+ const integratedMessage = buildSingleMessage(parsed, node.scadaServer ? "scada" : "selected", aggregatedPayload);
1367
+ if (influxMessage) {
1368
+ const target = RED.nodes.getNode(node.influxNode);
1369
+ if (target && typeof target.receive === "function") target.receive(RED.util.cloneMessage(influxMessage));
1370
+ else node.warn("Janitza: selecteaza un nod adt-influxdb pentru integrarea secundara.");
1371
+ }
1372
+ node.send(integratedMessage);
1373
+
1374
+ return;
1375
+
1376
+ }
1377
+
1378
+
1379
+ node.send(selectedFields.map(function (field) {
1380
+
1381
+ return buildSingleMessage(parsed, field, getByPath(parsed, field));
1382
+
1383
+ }));
1384
+
1385
+ }
1386
+
1387
+
1388
+ async function executeRead() {
1389
+
1390
+ if (closed || running) {
1391
+
1392
+ return;
1393
+
1394
+ }
1395
+
1396
+
1397
+ running = true;
1398
+
1399
+
1400
+ try {
1401
+
1402
+ setReadingStatus();
1403
+
1404
+ sendParsedResult(await readMeter());
1405
+
1406
+ } catch (err) {
1407
+
1408
+ setDisconnectedStatus(err.message);
1409
+
1410
+ node.error("Eroare Janitza (" + node.host + ":" + node.port + ", unitId=" + node.unitId + "): " + err.message);
1411
+
1412
+ } finally {
1413
+
1414
+ running = false;
1415
+
1416
+ }
1417
+
1418
+ }
1419
+
1420
+
1421
+ if (!node.host) {
1422
+
1423
+ setDisconnectedStatus("IP lipsa");
1424
+
1425
+ node.error("Configuratie invalida: IP lipsa");
1426
+
1427
+ } else {
1428
+
1429
+ if (node.influxDb) {
1430
+ registeredInfluxIdentityKey = getInfluxIdentityKey();
1431
+ registerInfluxMeasurement(node.id, registeredInfluxIdentityKey);
1432
+
1433
+ if (hasInfluxMeasurementConflict()) {
1434
+ node.status({
1435
+ fill: "red",
1436
+ shape: "ring",
1437
+ text: "meter_id+measurement duplicat"
1438
+ });
1439
+ node.error("Janitza InfluxDB: combinatie duplicata Meter ID + Measurement ('" + getEffectiveMeterId() + "' @ '" + getInfluxMeasurement() + "'). Configureaza un Meter ID unic pentru acelasi measurement.");
1440
+ }
1441
+ }
1442
+
1443
+ if (node.timestep > 0) {
1444
+ startupHandle = setTimeout(function () {
1445
+
1446
+ executeRead();
1447
+
1448
+ }, 0);
1449
+
1450
+ intervalHandle = setInterval(function () {
1451
+
1452
+ executeRead();
1453
+
1454
+ }, node.timestep);
1455
+ }
1456
+
1457
+ }
1458
+
1459
+ node.on("input", function () {
1460
+ executeRead();
1461
+ });
1462
+
1463
+
1464
+ node.on("close", function (done) {
1465
+
1466
+ closed = true;
1467
+
1468
+ if (registeredInfluxIdentityKey) {
1469
+ unregisterInfluxMeasurement(node.id, registeredInfluxIdentityKey);
1470
+ registeredInfluxIdentityKey = "";
1471
+ }
1472
+
1473
+ if (startupHandle) {
1474
+
1475
+ clearTimeout(startupHandle);
1476
+
1477
+ startupHandle = null;
1478
+
1479
+ }
1480
+
1481
+ if (intervalHandle) {
1482
+
1483
+ clearInterval(intervalHandle);
1484
+
1485
+ intervalHandle = null;
1486
+
1487
+ }
1488
+
1489
+ done();
1490
+
1491
+ });
1492
+
1493
+
1494
+ });
1495
+ }
1496
+
1497
+
1498
+ RED.nodes.registerType("janitza", JanitzaNode);
1499
+
1500
+ };