@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,1074 @@
1
+ <style>
2
+ .victron-read-editor {
3
+ --vr-border: #d7dde5;
4
+ --vr-muted: #667085;
5
+ --vr-surface: #f8fafc;
6
+ }
7
+
8
+ .victron-read-editor .vr-section {
9
+ margin: 0 0 18px;
10
+ padding: 14px 16px 16px;
11
+ border: 1px solid var(--vr-border);
12
+ border-radius: 10px;
13
+ background: var(--vr-surface);
14
+ }
15
+
16
+ .victron-read-editor .vr-section-title {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ margin: 0 0 4px;
21
+ font-size: 15px;
22
+ font-weight: 600;
23
+ color: #243447;
24
+ }
25
+
26
+ .victron-read-editor .vr-section-note {
27
+ margin: 0 0 14px;
28
+ color: var(--vr-muted);
29
+ font-size: 12px;
30
+ line-height: 1.45;
31
+ }
32
+
33
+ .victron-read-editor .vr-grid {
34
+ display: grid;
35
+ grid-template-columns: repeat(2, minmax(0, 1fr));
36
+ gap: 12px 16px;
37
+ }
38
+
39
+ .victron-read-editor .form-row {
40
+ margin-bottom: 12px;
41
+ }
42
+
43
+ .victron-read-editor .vr-grid .form-row,
44
+ .victron-read-editor .form-row:last-child {
45
+ margin-bottom: 0;
46
+ }
47
+
48
+ .victron-read-editor .form-row > label {
49
+ width: 170px;
50
+ font-weight: 600;
51
+ }
52
+
53
+ .victron-read-editor .form-row > input[type="text"],
54
+ .victron-read-editor .form-row > input[type="password"],
55
+ .victron-read-editor .form-row > select {
56
+ width: calc(100% - 180px);
57
+ }
58
+
59
+ .victron-read-editor .vr-checkbox-row > input[type="checkbox"] {
60
+ display: inline-block;
61
+ width: auto;
62
+ vertical-align: top;
63
+ }
64
+
65
+ .victron-read-editor .vr-inline-note {
66
+ margin-left: 8px;
67
+ color: var(--vr-muted);
68
+ line-height: 1.4;
69
+ }
70
+
71
+ .victron-read-editor .vr-toolbar {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: 8px;
75
+ margin-bottom: 10px;
76
+ }
77
+
78
+ .victron-read-editor .vr-field-list {
79
+ max-height: 360px;
80
+ overflow: auto;
81
+ padding: 12px;
82
+ border: 1px solid var(--vr-border);
83
+ border-radius: 10px;
84
+ background: #fff;
85
+ }
86
+
87
+ .victron-read-editor .vr-field-group {
88
+ margin-bottom: 14px;
89
+ }
90
+
91
+ .victron-read-editor .vr-field-group:last-child {
92
+ margin-bottom: 0;
93
+ }
94
+
95
+ .victron-read-editor .vr-field-group-title {
96
+ margin: 0 0 8px;
97
+ font-size: 12px;
98
+ font-weight: 700;
99
+ text-transform: uppercase;
100
+ letter-spacing: 0.03em;
101
+ color: #344054;
102
+ }
103
+
104
+ .victron-read-editor .vr-field-group-grid {
105
+ display: grid;
106
+ grid-template-columns: repeat(2, minmax(0, 1fr));
107
+ gap: 8px 16px;
108
+ }
109
+
110
+ @media (max-width: 980px) {
111
+ .victron-read-editor .vr-grid,
112
+ .victron-read-editor .vr-field-group-grid {
113
+ grid-template-columns: 1fr;
114
+ }
115
+ }
116
+
117
+ .victron-read-editor .vr-field-item {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ margin: 0;
122
+ line-height: 1.35;
123
+ }
124
+
125
+ .victron-read-editor .vr-field-meta {
126
+ margin-left: auto;
127
+ font-size: 11px;
128
+ color: var(--vr-muted);
129
+ }
130
+
131
+ .victron-read-editor .vr-load-status {
132
+ margin-bottom: 10px;
133
+ font-size: 12px;
134
+ color: var(--vr-muted);
135
+ }
136
+ </style>
137
+
138
+ <script type="text/javascript">
139
+ (function () {
140
+ function normalizePayloadFields(payloadFields) {
141
+ if (Array.isArray(payloadFields)) {
142
+ const filtered = payloadFields
143
+ .map(function (value) { return String(value || "").trim(); })
144
+ .filter(Boolean);
145
+
146
+ const unique = Array.from(new Set(filtered));
147
+ if (unique.includes("full")) {
148
+ return ["full"];
149
+ }
150
+ return unique;
151
+ }
152
+
153
+ if (typeof payloadFields === "string") {
154
+ const trimmed = payloadFields.trim();
155
+ if (!trimmed) {
156
+ return ["full"];
157
+ }
158
+
159
+ if (trimmed[0] === "[") {
160
+ try {
161
+ const parsed = JSON.parse(trimmed);
162
+ if (Array.isArray(parsed)) {
163
+ return normalizePayloadFields(parsed);
164
+ }
165
+ } catch (error) {
166
+ // ignore invalid JSON
167
+ }
168
+ }
169
+
170
+ return [trimmed];
171
+ }
172
+
173
+ return ["full"];
174
+ }
175
+
176
+ const BASIC_CATEGORY_SEGMENTS = new Set([
177
+ "vebus",
178
+ "battery",
179
+ "batteries",
180
+ "pvinverter",
181
+ "grid",
182
+ "solarcharger",
183
+ "inverter",
184
+ "invertercharger"
185
+ ]);
186
+
187
+ const SPECIAL_EXPERT_LONG_PRESS_MS = 5000;
188
+
189
+ function isBasicMetricSegment(segment) {
190
+ const s = String(segment || "").toLowerCase();
191
+ if (!s) {
192
+ return false;
193
+ }
194
+
195
+ if (["v", "i", "p", "temp", "soc", "soh", "state", "mode"].includes(s)) {
196
+ return true;
197
+ }
198
+
199
+ return s.includes("voltage") || s.includes("current") || s.includes("power") ||
200
+ s.includes("temperature") || s.includes("frequency") || s.includes("energy") ||
201
+ s.includes("yield") || s.includes("consumption");
202
+ }
203
+
204
+ function isBasicFieldPath(path) {
205
+ const segments = String(path || "").toLowerCase().split(".").filter(Boolean);
206
+ if (!segments.length) {
207
+ return false;
208
+ }
209
+
210
+ const hasCategory = segments.some(function (segment) {
211
+ return BASIC_CATEGORY_SEGMENTS.has(segment);
212
+ });
213
+ if (!hasCategory) {
214
+ return false;
215
+ }
216
+
217
+ return segments.some(isBasicMetricSegment);
218
+ }
219
+
220
+ function groupFields(items) {
221
+ const groups = {};
222
+
223
+ items.forEach(function (item) {
224
+ const path = String(item.path || "").trim();
225
+ if (!path) {
226
+ return;
227
+ }
228
+
229
+ const topLevel = path.includes(".") ? path.split(".")[0] : "root";
230
+ if (!groups[topLevel]) {
231
+ groups[topLevel] = [];
232
+ }
233
+
234
+ groups[topLevel].push(item);
235
+ });
236
+
237
+ return Object.keys(groups)
238
+ .sort(function (a, b) { return a.localeCompare(b); })
239
+ .map(function (name) {
240
+ return {
241
+ label: name,
242
+ fields: groups[name].sort(function (a, b) {
243
+ return String(a.path).localeCompare(String(b.path));
244
+ })
245
+ };
246
+ });
247
+ }
248
+
249
+ function renderFieldList(items, selectedFields) {
250
+ const $container = $("#node-victron-read-field-list-container").empty();
251
+ const selected = new Set(normalizePayloadFields(selectedFields).filter(function (field) {
252
+ return field !== "full";
253
+ }));
254
+
255
+ if (!Array.isArray(items) || !items.length) {
256
+ $container.append('<div class="vr-inline-note">Nu s-au detectat campuri pentru sursa curenta. Pentru MQTT, verifica deploy + mesaje primite.</div>');
257
+ return;
258
+ }
259
+
260
+ const groups = groupFields(items);
261
+
262
+ groups.forEach(function (group) {
263
+ const $group = $('<div class="vr-field-group"></div>');
264
+ const $title = $('<p class="vr-field-group-title"></p>').text(group.label);
265
+ const $grid = $('<div class="vr-field-group-grid"></div>');
266
+
267
+ group.fields.forEach(function (item) {
268
+ const path = String(item.path || "");
269
+ const checkboxId = "victron-read-field-" + path.replace(/[^a-zA-Z0-9_]/g, "_");
270
+ const $checkbox = $('<input type="checkbox" class="vr-field-checkbox">')
271
+ .attr("id", checkboxId)
272
+ .attr("data-field", path)
273
+ .prop("checked", selected.has(path));
274
+
275
+ const $label = $('<label class="vr-field-item"></label>')
276
+ .attr("for", checkboxId)
277
+ .append($checkbox)
278
+ .append($('<span></span>').text(path));
279
+
280
+ const type = String(item.type || "").trim();
281
+ if (type) {
282
+ $label.append($('<span class="vr-field-meta"></span>').text(type));
283
+ }
284
+
285
+ $grid.append($label);
286
+ });
287
+
288
+ $group.append($title).append($grid);
289
+ $container.append($group);
290
+ });
291
+ }
292
+
293
+ function readSelectedFields() {
294
+ const payloadMode = String($("#node-input-payloadMode").val() || "full");
295
+ if (payloadMode !== "selected") {
296
+ return ["full"];
297
+ }
298
+
299
+ const selected = [];
300
+ $("#node-victron-read-field-list-container .vr-field-checkbox:checked").each(function () {
301
+ selected.push(String($(this).attr("data-field") || "").trim());
302
+ });
303
+
304
+ return Array.from(new Set(selected));
305
+ }
306
+
307
+ function updatePayloadModeUi() {
308
+ const payloadMode = String($("#node-input-payloadMode").val() || "full");
309
+ $("#row-victron-read-field-list").toggle(payloadMode === "selected");
310
+ }
311
+
312
+ function updateSourceUi() {
313
+ const sourceType = String($("#node-input-sourceType").val() || "global");
314
+ const isGlobal = sourceType === "global";
315
+
316
+ $("#row-victron-global-variable").toggle(isGlobal);
317
+ $("#row-victron-global-path").toggle(isGlobal);
318
+
319
+ $("#row-victron-mqtt-url").toggle(!isGlobal);
320
+ $("#row-victron-mqtt-topic").toggle(!isGlobal);
321
+ $("#row-victron-mqtt-topic-qos").toggle(!isGlobal);
322
+ $("#row-victron-mqtt-auth").toggle(!isGlobal);
323
+ $("#row-victron-mqtt-auth-pass").toggle(!isGlobal);
324
+ $("#row-victron-mqtt-client-id").toggle(!isGlobal);
325
+ $("#row-victron-mqtt-path").toggle(!isGlobal);
326
+ $("#row-victron-mqtt-root-path").toggle(false);
327
+ $("#row-victron-mqtt-flags").toggle(!isGlobal);
328
+ $("#row-victron-mqtt-portal-id").toggle(!isGlobal);
329
+ $("#row-victron-mqtt-keepalive").toggle(!isGlobal);
330
+ $("#row-victron-mqtt-freshness").toggle(!isGlobal);
331
+ $("#row-victron-mqtt-discover").toggle(!isGlobal);
332
+ }
333
+
334
+ function readNodeIdForEndpoint(node) {
335
+ const id = String((node && node.id) || "").trim();
336
+ if (!id || id === "_ADD_") {
337
+ return "";
338
+ }
339
+ return "/" + encodeURIComponent(id);
340
+ }
341
+
342
+ RED.nodes.registerType("victron-read", {
343
+ category: "Atelierul de Tehnologii",
344
+ color: "#9cc4ff",
345
+ defaults: {
346
+ license: { value: "", type: "atelier-license", required: true },
347
+ name: { value: "" },
348
+ sourceType: { value: "mqtt" },
349
+ globalVariable: { value: "victron", required: true },
350
+ globalPath: { value: "" },
351
+
352
+ mqttUrl: { value: "mqtt://127.0.0.1:1883", required: true },
353
+ mqttTopicFilter: { value: "N/#", required: true },
354
+ mqttQos: { value: 0, required: true, validate: RED.validators.number() },
355
+ mqttClientId: { value: "" },
356
+ mqttUsername: { value: "root" },
357
+ mqttTopicPathMode: { value: "victron" },
358
+ mqttStripPortalId: { value: true },
359
+ mqttRootPath: { value: "" },
360
+ mqttPortalId: { value: "" },
361
+ mqttKeepaliveEnabled: { value: true },
362
+ mqttKeepaliveInterval: { value: 30000, required: true, validate: RED.validators.number() },
363
+ mqttStaleAfter: { value: 120000, required: true, validate: RED.validators.number() },
364
+ emitOnlyChanges: { value: true },
365
+ expertMode: { value: false },
366
+ includeFieldTokens: { value: "" },
367
+ excludeFieldTokens: { value: ".serial,.id,.name,.rawvalue,.seqno,.heartbeat,.diagnostics,.leds,.timelastcontact" },
368
+ onlyNumericFields: { value: true },
369
+ maxFields: { value: 150, required: true, validate: RED.validators.number() },
370
+
371
+ pollInterval: {
372
+ value: 5000,
373
+ required: true,
374
+ validate: function (value) {
375
+ const text = String(value || "").trim().toLowerCase();
376
+ if (text === "off") return true;
377
+ const n = Number(text);
378
+ return Number.isFinite(n) && n >= 0;
379
+ }
380
+ },
381
+ payloadMode: { value: "default" },
382
+ payloadFields: { value: ["full"] },
383
+ deviceId: { value: "victron", required: true },
384
+ topic: { value: "" },
385
+ influxMeasurement: { value: "victron" }
386
+ },
387
+ credentials: {
388
+ mqttPassword: { type: "password" }
389
+ },
390
+ inputs: 0,
391
+ outputs: 1,
392
+ icon: "font-awesome/fa-bolt",
393
+ label: function () {
394
+ return this.name || this.deviceId || "victron-read";
395
+ },
396
+ oneditprepare: function () {
397
+ const node = this;
398
+ const initialFields = normalizePayloadFields(node.payloadFields || ["full"]);
399
+ const initialMode = ["default", "full", "selected"].includes(node.payloadMode)
400
+ ? node.payloadMode
401
+ : (initialFields.includes("full") ? "default" : "selected");
402
+ // Extended field selection is session-only and must always start OFF on reopen.
403
+ let expertModeUnlocked = false;
404
+ let mqttDiscoveryReady = false;
405
+ let selectAllLongPressTimer = null;
406
+ let selectAllLongPressTriggered = false;
407
+ let lastLoadedFields = [];
408
+ let visibleFieldPathSet = new Set();
409
+
410
+ $("#node-input-payloadMode").val(initialMode);
411
+ updatePayloadModeUi();
412
+ updateSourceUi();
413
+ syncMqttRootPathVisibility();
414
+ renderFieldList([], initialFields);
415
+
416
+ function syncInputs() {
417
+ const text = String($("#node-input-pollInterval").val() || "").trim().toLowerCase();
418
+ node.inputs = text === "off" || text === "0" ? 1 : 0;
419
+ }
420
+ $("#node-input-pollInterval").off(".victronReadPoll").on("keyup.victronReadPoll change.victronReadPoll", syncInputs);
421
+ syncInputs();
422
+
423
+ (function seedRootSelectFromSavedValue() {
424
+ const savedRoot = String(node.mqttRootPath || "").trim();
425
+ if (!savedRoot) return;
426
+ const $root = $("#node-input-mqttRootPath");
427
+ const hasOption = $root.find("option").filter(function () {
428
+ return String($(this).attr("value") || "") === savedRoot;
429
+ }).length > 0;
430
+ if (!hasOption) {
431
+ $root.append($("<option></option>").attr("value", savedRoot).text(savedRoot));
432
+ }
433
+ $root.val(savedRoot);
434
+ })();
435
+
436
+ function setLoadStatus(text, isError) {
437
+ const $status = $("#node-victron-read-load-status");
438
+ $status.text(text || "");
439
+ $status.css("color", isError ? "#b42318" : "#667085");
440
+ }
441
+
442
+ function ensureDefaultInputValue(selector, fallback) {
443
+ const $el = $(selector);
444
+ if (!$el.length) return;
445
+ const current = String($el.val() || "").trim();
446
+ if (!current) {
447
+ $el.val(String(fallback || ""));
448
+ }
449
+ }
450
+
451
+ function syncMqttRootPathVisibility() {
452
+ const sourceType = String($("#node-input-sourceType").val() || "global");
453
+ const isMqtt = sourceType === "mqtt";
454
+ $("#row-victron-mqtt-root-path").toggle(isMqtt && mqttDiscoveryReady);
455
+ }
456
+
457
+ function setMqttDiscoveryReady(value) {
458
+ mqttDiscoveryReady = !!value;
459
+ syncMqttRootPathVisibility();
460
+ }
461
+
462
+ function clearSelectAllLongPressTimer() {
463
+ if (selectAllLongPressTimer) {
464
+ clearTimeout(selectAllLongPressTimer);
465
+ selectAllLongPressTimer = null;
466
+ }
467
+ }
468
+
469
+ function getVisibleFields(items) {
470
+ const list = Array.isArray(items) ? items : [];
471
+ if (expertModeUnlocked) {
472
+ return list;
473
+ }
474
+ return list.filter(function (item) {
475
+ return isBasicFieldPath(item && item.path);
476
+ });
477
+ }
478
+
479
+ function getCurrentMqttSettings() {
480
+ return {
481
+ nodeId: String((node && node.id) || "").trim(),
482
+ mqttUrl: String($("#node-input-mqttUrl").val() || "").trim(),
483
+ mqttTopicFilter: String($("#node-input-mqttTopicFilter").val() || "").trim(),
484
+ mqttQos: Number($("#node-input-mqttQos").val() || 0),
485
+ mqttClientId: String($("#node-input-mqttClientId").val() || "").trim(),
486
+ mqttUsername: String($("#node-input-mqttUsername").val() || "").trim(),
487
+ mqttPassword: String($("#node-input-mqttPassword").val() || ""),
488
+ mqttTopicPathMode: String($("#node-input-mqttTopicPathMode").val() || "victron"),
489
+ mqttStripPortalId: !!$("#node-input-mqttStripPortalId").is(":checked"),
490
+ mqttRootPath: String($("#node-input-mqttRootPath").val() || "").trim(),
491
+ mqttPortalId: String($("#node-input-mqttPortalId").val() || "").trim(),
492
+ timeoutMs: 5000
493
+ };
494
+ }
495
+
496
+ function updateMqttRootPathOptions(roots, preserveValue) {
497
+ const $select = $("#node-input-mqttRootPath");
498
+ const previousValue = String(
499
+ preserveValue !== undefined
500
+ ? preserveValue
501
+ : ($select.val() || "")
502
+ ).trim();
503
+
504
+ $select.empty();
505
+ $select.append($("<option></option>").attr("value", "").text("All roots"));
506
+
507
+ (Array.isArray(roots) ? roots : []).forEach(function (root) {
508
+ const value = String(root || "").trim();
509
+ if (!value) return;
510
+ $select.append($("<option></option>").attr("value", value).text(value));
511
+ });
512
+
513
+ $select.val(previousValue);
514
+ if ($select.val() !== previousValue) {
515
+ $select.val("");
516
+ }
517
+ }
518
+
519
+ function discoverMqttData(done) {
520
+ const settings = getCurrentMqttSettings();
521
+ if (!settings.mqttUrl) {
522
+ setLoadStatus("Completeaza MQTT URL.", true);
523
+ setMqttDiscoveryReady(false);
524
+ if (typeof done === "function") done(new Error("missing_mqtt_url"));
525
+ return;
526
+ }
527
+
528
+ setLoadStatus("Conectare MQTT si descoperire campuri...", false);
529
+
530
+ $.ajax({
531
+ url: "victron-read/discover-mqtt",
532
+ method: "POST",
533
+ contentType: "application/json",
534
+ data: JSON.stringify(settings),
535
+ dataType: "json",
536
+ timeout: 9000
537
+ }).done(function (response) {
538
+ if (!response || !response.ok) {
539
+ setLoadStatus((response && response.error) || "Nu s-a putut stabili conexiunea MQTT.", true);
540
+ setMqttDiscoveryReady(false);
541
+ if (typeof done === "function") done(new Error((response && response.error) || "discover_failed"));
542
+ return;
543
+ }
544
+
545
+ setMqttDiscoveryReady(true);
546
+ if (response.portalId && !String($("#node-input-mqttPortalId").val() || "").trim()) {
547
+ $("#node-input-mqttPortalId").val(response.portalId);
548
+ }
549
+ updateMqttRootPathOptions(response.roots || [], settings.mqttRootPath);
550
+ if (typeof done === "function") done(null, response);
551
+ }).fail(function (xhr) {
552
+ const message = (xhr && xhr.responseJSON && xhr.responseJSON.error)
553
+ ? xhr.responseJSON.error
554
+ : "Eroare la conectarea MQTT.";
555
+ setLoadStatus(message, true);
556
+ setMqttDiscoveryReady(false);
557
+ if (typeof done === "function") done(new Error(message));
558
+ });
559
+ }
560
+
561
+ function loadFields() {
562
+ const sourceType = String($("#node-input-sourceType").val() || "global");
563
+ const globalVar = String($("#node-input-globalVariable").val() || "").trim();
564
+ const globalPath = String($("#node-input-globalPath").val() || "").trim();
565
+ const mqttRootPath = String($("#node-input-mqttRootPath").val() || "").trim();
566
+
567
+ if (sourceType === "global" && !globalVar) {
568
+ setLoadStatus("Completeaza mai intai Global variable.", true);
569
+ renderFieldList([], readSelectedFields());
570
+ return;
571
+ }
572
+
573
+ function renderFromResponse(response) {
574
+ if (!response || !response.ok) {
575
+ setLoadStatus((response && response.error) || "Nu s-au putut incarca campurile.", true);
576
+ if (sourceType === "mqtt") {
577
+ setMqttDiscoveryReady(false);
578
+ }
579
+ renderFieldList([], readSelectedFields());
580
+ return;
581
+ }
582
+
583
+ const selected = normalizePayloadFields(readSelectedFields());
584
+ lastLoadedFields = Array.isArray(response.fields) ? response.fields.slice() : [];
585
+ if (sourceType === "mqtt") {
586
+ const roots = Array.isArray(response.roots) ? response.roots : [];
587
+ if (response.portalId && !String($("#node-input-mqttPortalId").val() || "").trim()) {
588
+ $("#node-input-mqttPortalId").val(response.portalId);
589
+ }
590
+ const mqttReady = roots.length > 0 || lastLoadedFields.length > 0;
591
+ setMqttDiscoveryReady(mqttReady);
592
+ updateMqttRootPathOptions(roots, mqttRootPath);
593
+ }
594
+ const visibleFields = getVisibleFields(lastLoadedFields);
595
+ visibleFieldPathSet = new Set(visibleFields.map(function (item) { return String(item.path || ""); }));
596
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
597
+ renderFieldList(visibleFields, selected);
598
+ setLoadStatus(
599
+ "Campuri afisate: " + String(visibleFields.length) +
600
+ " / " + String(lastLoadedFields.length) +
601
+ (expertModeUnlocked ? " (expert)" : " (basic)")
602
+ , false);
603
+ }
604
+
605
+ if (sourceType === "mqtt") {
606
+ setLoadStatus("Se incarca campurile MQTT din runtime...", false);
607
+ const runtimeEndpoint = "victron-read/fields" + readNodeIdForEndpoint(node);
608
+ $.getJSON(runtimeEndpoint, {
609
+ sourceType: "mqtt",
610
+ mqttRootPath: mqttRootPath
611
+ }).done(function (runtimeResponse) {
612
+ if (runtimeResponse && runtimeResponse.ok) {
613
+ renderFromResponse(runtimeResponse);
614
+ return;
615
+ }
616
+
617
+ setMqttDiscoveryReady(false);
618
+ setLoadStatus(
619
+ (runtimeResponse && runtimeResponse.error)
620
+ ? runtimeResponse.error
621
+ : "Nu s-au detectat campuri MQTT. Apasa Connect & Discover.",
622
+ true
623
+ );
624
+ renderFieldList([], readSelectedFields());
625
+ }).fail(function () {
626
+ setMqttDiscoveryReady(false);
627
+ setLoadStatus("Eroare la citirea campurilor MQTT. Apasa Connect & Discover.", true);
628
+ renderFieldList([], readSelectedFields());
629
+ });
630
+ return;
631
+ }
632
+
633
+ setLoadStatus("Se incarca campurile disponibile...", false);
634
+ const endpoint = "victron-read/fields" + readNodeIdForEndpoint(node);
635
+ const params = {
636
+ sourceType: sourceType,
637
+ globalVar: globalVar,
638
+ globalPath: globalPath
639
+ };
640
+
641
+ $.getJSON(endpoint, params)
642
+ .done(renderFromResponse)
643
+ .fail(function (xhr) {
644
+ const message = (xhr && xhr.responseJSON && xhr.responseJSON.error)
645
+ ? xhr.responseJSON.error
646
+ : "Eroare la citirea campurilor.";
647
+ setLoadStatus(message, true);
648
+ });
649
+ }
650
+
651
+ $("#node-input-sourceType").off(".victronRead").on("change.victronRead", function () {
652
+ if (String($("#node-input-sourceType").val() || "global") === "mqtt") {
653
+ setMqttDiscoveryReady(false);
654
+ }
655
+ updateSourceUi();
656
+ syncMqttRootPathVisibility();
657
+ setLoadStatus("Sursa modificata. Apasa Refresh fields.", false);
658
+ });
659
+
660
+ $("#node-input-payloadMode").off(".victronRead").on("change.victronRead", updatePayloadModeUi);
661
+
662
+ $("#node-victron-read-refresh-fields").off(".victronRead").on("click.victronRead", function (event) {
663
+ event.preventDefault();
664
+ loadFields();
665
+ });
666
+
667
+ $("#node-victron-read-discover-mqtt").off(".victronRead").on("click.victronRead", function (event) {
668
+ event.preventDefault();
669
+ const runtimeEndpoint = "victron-read/fields" + readNodeIdForEndpoint(node);
670
+ const mqttRootPath = String($("#node-input-mqttRootPath").val() || "").trim();
671
+
672
+ $.getJSON(runtimeEndpoint, {
673
+ sourceType: "mqtt",
674
+ mqttRootPath: mqttRootPath
675
+ }).done(function (runtimeResponse) {
676
+ if (runtimeResponse && runtimeResponse.ok && (Number(runtimeResponse.count || 0) > 0 || (runtimeResponse.roots || []).length > 0)) {
677
+ setMqttDiscoveryReady(true);
678
+ updateMqttRootPathOptions(runtimeResponse.roots || [], mqttRootPath);
679
+ const selected = normalizePayloadFields(readSelectedFields());
680
+ lastLoadedFields = Array.isArray(runtimeResponse.fields) ? runtimeResponse.fields.slice() : [];
681
+ const visibleFields = getVisibleFields(lastLoadedFields);
682
+ visibleFieldPathSet = new Set(visibleFields.map(function (item) { return String(item.path || ""); }));
683
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
684
+ renderFieldList(visibleFields, selected);
685
+ setLoadStatus(
686
+ "Conectat (runtime). Campuri afisate: " +
687
+ String(visibleFields.length) + " / " + String(lastLoadedFields.length),
688
+ false
689
+ );
690
+ return;
691
+ }
692
+
693
+ discoverMqttData(function (err, response) {
694
+ if (err) {
695
+ return;
696
+ }
697
+ const selected = normalizePayloadFields(readSelectedFields());
698
+ lastLoadedFields = Array.isArray(response.fields) ? response.fields.slice() : [];
699
+ const visibleFields = getVisibleFields(lastLoadedFields);
700
+ visibleFieldPathSet = new Set(visibleFields.map(function (item) { return String(item.path || ""); }));
701
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
702
+ renderFieldList(visibleFields, selected);
703
+ setLoadStatus(
704
+ "Conectat. Mesaje: " + String(response.messages || 0) + ". Campuri afisate: " +
705
+ String(visibleFields.length) + " / " + String(lastLoadedFields.length),
706
+ false
707
+ );
708
+ });
709
+ }).fail(function () {
710
+ discoverMqttData(function (err, response) {
711
+ if (err) {
712
+ return;
713
+ }
714
+ const selected = normalizePayloadFields(readSelectedFields());
715
+ lastLoadedFields = Array.isArray(response.fields) ? response.fields.slice() : [];
716
+ const visibleFields = getVisibleFields(lastLoadedFields);
717
+ visibleFieldPathSet = new Set(visibleFields.map(function (item) { return String(item.path || ""); }));
718
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
719
+ renderFieldList(visibleFields, selected);
720
+ setLoadStatus(
721
+ "Conectat. Mesaje: " + String(response.messages || 0) + ". Campuri afisate: " +
722
+ String(visibleFields.length) + " / " + String(lastLoadedFields.length),
723
+ false
724
+ );
725
+ });
726
+ });
727
+ });
728
+
729
+ $("#node-victron-read-select-all")
730
+ .off(".victronRead")
731
+ .on("mousedown.victronRead touchstart.victronRead", function () {
732
+ if ($("#node-input-payloadMode").val() !== "selected") {
733
+ return;
734
+ }
735
+
736
+ selectAllLongPressTriggered = false;
737
+ clearSelectAllLongPressTimer();
738
+ selectAllLongPressTimer = setTimeout(function () {
739
+ expertModeUnlocked = true;
740
+ selectAllLongPressTriggered = true;
741
+ const selected = normalizePayloadFields(readSelectedFields());
742
+ const visibleFields = getVisibleFields(lastLoadedFields);
743
+ visibleFieldPathSet = new Set(visibleFields.map(function (item) { return String(item.path || ""); }));
744
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
745
+ renderFieldList(visibleFields, selected);
746
+ if (RED && RED.notify) {
747
+ RED.notify("Selectia extinsa a fost activata.", "success");
748
+ }
749
+ }, SPECIAL_EXPERT_LONG_PRESS_MS);
750
+ })
751
+ .on("mouseup.victronRead mouseleave.victronRead touchend.victronRead touchcancel.victronRead", function () {
752
+ clearSelectAllLongPressTimer();
753
+ })
754
+ .on("click.victronRead", function (event) {
755
+ if (selectAllLongPressTriggered) {
756
+ event.preventDefault();
757
+ event.stopImmediatePropagation();
758
+ selectAllLongPressTriggered = false;
759
+ return;
760
+ }
761
+ event.preventDefault();
762
+ $("#node-victron-read-field-list-container .vr-field-checkbox").prop("checked", true);
763
+ });
764
+
765
+ $("#node-victron-read-clear-all").off(".victronRead").on("click.victronRead", function (event) {
766
+ event.preventDefault();
767
+ $("#node-victron-read-field-list-container .vr-field-checkbox").prop("checked", false);
768
+ });
769
+
770
+ $("#node-input-mqttRootPath").off(".victronRead").on("change.victronRead", function () {
771
+ if (String($("#node-input-sourceType").val() || "global") !== "mqtt") {
772
+ return;
773
+ }
774
+ loadFields();
775
+ });
776
+
777
+ $("#node-input-globalVariable, #node-input-globalPath")
778
+ .off(".victronRead")
779
+ .on("change.victronRead keyup.victronRead", function () {
780
+ setLoadStatus("Apasa Refresh fields pentru a reincarca lista.", false);
781
+ });
782
+
783
+ ensureDefaultInputValue("#node-input-mqttUrl", "mqtt://127.0.0.1:1883");
784
+ ensureDefaultInputValue("#node-input-mqttTopicFilter", "N/#");
785
+ ensureDefaultInputValue("#node-input-mqttQos", "0");
786
+ ensureDefaultInputValue("#node-input-mqttUsername", "root");
787
+ ensureDefaultInputValue("#node-input-mqttKeepaliveInterval", "30000");
788
+ ensureDefaultInputValue("#node-input-mqttStaleAfter", "120000");
789
+ ensureDefaultInputValue("#node-input-deviceId", "victron");
790
+ ensureDefaultInputValue("#node-input-influxMeasurement", "victron");
791
+ ensureDefaultInputValue("#node-input-maxFields", "150");
792
+
793
+ setMqttDiscoveryReady(false);
794
+ loadFields();
795
+
796
+ node.__victronVisibleFieldPathSet = visibleFieldPathSet;
797
+ },
798
+ oneditsave: function () {
799
+ const selectedNow = normalizePayloadFields(readSelectedFields());
800
+ const payloadMode = String($("#node-input-payloadMode").val() || "full");
801
+ const previousSaved = normalizePayloadFields(this.payloadFields || ["full"]);
802
+ const visibleSet = this.__victronVisibleFieldPathSet instanceof Set
803
+ ? this.__victronVisibleFieldPathSet
804
+ : new Set();
805
+
806
+ if (payloadMode !== "selected") {
807
+ this.payloadFields = ["full"];
808
+ } else {
809
+ const visibleSelected = selectedNow.filter(function (field) {
810
+ return field !== "full";
811
+ });
812
+
813
+ const hiddenPreviouslySelected = previousSaved.filter(function (field) {
814
+ return field !== "full" && !visibleSet.has(field);
815
+ });
816
+
817
+ const merged = Array.from(new Set(visibleSelected.concat(hiddenPreviouslySelected)));
818
+ this.payloadFields = merged;
819
+ }
820
+
821
+ // Extended field selection is always session-only and resets on Done/Deploy.
822
+ this.expertMode = false;
823
+ const pollText = String($("#node-input-pollInterval").val() || "").trim().toLowerCase();
824
+ this.inputs = pollText === "off" || pollText === "0" ? 1 : 0;
825
+ delete this.__victronVisibleFieldPathSet;
826
+ },
827
+ oneditcancel: function () {
828
+ $("#node-input-sourceType").off(".victronRead");
829
+ $("#node-input-payloadMode").off(".victronRead");
830
+ $("#node-victron-read-refresh-fields").off(".victronRead");
831
+ $("#node-victron-read-discover-mqtt").off(".victronRead");
832
+ $("#node-victron-read-select-all").off(".victronRead");
833
+ $("#node-victron-read-clear-all").off(".victronRead");
834
+ $("#node-input-mqttRootPath").off(".victronRead");
835
+ $("#node-input-globalVariable, #node-input-globalPath").off(".victronRead");
836
+ delete this.__victronVisibleFieldPathSet;
837
+ }
838
+ });
839
+ })();
840
+ </script>
841
+
842
+ <script type="text/html" data-template-name="victron-read">
843
+ <div class="form-row"><label for="node-input-license"><i class="fa fa-key"></i> License</label><input type="text" id="node-input-license"></div>
844
+ <div class="victron-read-editor">
845
+ <div class="vr-section">
846
+ <div class="vr-section-title"><i class="fa fa-sign-in"></i> Source</div>
847
+ <p class="vr-section-note">Poti citi din MQTT Victron (recomandat) sau din variabila globala existenta.</p>
848
+
849
+ <div class="form-row">
850
+ <label for="node-input-sourceType"><i class="fa fa-random"></i> Source type</label>
851
+ <select id="node-input-sourceType">
852
+ <option value="mqtt">MQTT (Victron)</option>
853
+ <option value="global">Global variable</option>
854
+ </select>
855
+ </div>
856
+
857
+ <div class="vr-grid">
858
+ <div class="form-row">
859
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
860
+ <input type="text" id="node-input-name" placeholder="victron-read">
861
+ </div>
862
+
863
+ <div class="form-row" id="row-victron-global-variable">
864
+ <label for="node-input-globalVariable"><i class="fa fa-globe"></i> Global variable</label>
865
+ <input type="text" id="node-input-globalVariable" placeholder="victron">
866
+ </div>
867
+
868
+ <div class="form-row" id="row-victron-global-path">
869
+ <label for="node-input-globalPath"><i class="fa fa-sitemap"></i> Global path (optional)</label>
870
+ <input type="text" id="node-input-globalPath" placeholder="telemetry">
871
+ </div>
872
+
873
+ <div class="form-row" id="row-victron-mqtt-url">
874
+ <label for="node-input-mqttUrl"><i class="fa fa-plug"></i> MQTT URL</label>
875
+ <input type="text" id="node-input-mqttUrl" placeholder="mqtt://127.0.0.1:1883">
876
+ </div>
877
+
878
+ <div class="form-row" id="row-victron-mqtt-topic">
879
+ <label for="node-input-mqttTopicFilter"><i class="fa fa-filter"></i> Topic filter</label>
880
+ <input type="text" id="node-input-mqttTopicFilter" placeholder="N/#">
881
+ </div>
882
+
883
+ <div class="form-row" id="row-victron-mqtt-topic-qos">
884
+ <label for="node-input-mqttQos"><i class="fa fa-sort-numeric-asc"></i> MQTT QoS</label>
885
+ <input type="text" id="node-input-mqttQos" placeholder="0">
886
+ </div>
887
+
888
+ <div class="form-row" id="row-victron-mqtt-auth">
889
+ <label for="node-input-mqttUsername"><i class="fa fa-user"></i> MQTT user</label>
890
+ <input type="text" id="node-input-mqttUsername" placeholder="root">
891
+ </div>
892
+
893
+ <div class="form-row" id="row-victron-mqtt-auth-pass">
894
+ <label for="node-input-mqttPassword"><i class="fa fa-lock"></i> MQTT password</label>
895
+ <input type="password" id="node-input-mqttPassword" placeholder="optional">
896
+ </div>
897
+
898
+ <div class="form-row" id="row-victron-mqtt-client-id">
899
+ <label for="node-input-mqttClientId"><i class="fa fa-id-badge"></i> MQTT client id</label>
900
+ <input type="text" id="node-input-mqttClientId" placeholder="optional">
901
+ </div>
902
+
903
+ <div class="form-row" id="row-victron-mqtt-portal-id">
904
+ <label for="node-input-mqttPortalId"><i class="fa fa-key"></i> Portal ID</label>
905
+ <input type="text" id="node-input-mqttPortalId" placeholder="detectat automat sau introdus manual">
906
+ </div>
907
+
908
+ <div class="form-row" id="row-victron-mqtt-path">
909
+ <label for="node-input-mqttTopicPathMode"><i class="fa fa-sitemap"></i> Topic path mode</label>
910
+ <select id="node-input-mqttTopicPathMode">
911
+ <option value="victron">Victron (fara prefix/topic id)</option>
912
+ <option value="raw">Raw (topic complet)</option>
913
+ </select>
914
+ </div>
915
+
916
+ <div class="form-row" id="row-victron-mqtt-root-path">
917
+ <label for="node-input-mqttRootPath"><i class="fa fa-folder-open"></i> MQTT root path</label>
918
+ <select id="node-input-mqttRootPath">
919
+ <option value="">All roots</option>
920
+ </select>
921
+ </div>
922
+
923
+ <div class="form-row" id="row-victron-mqtt-freshness">
924
+ <label for="node-input-mqttStaleAfter"><i class="fa fa-clock-o"></i> Stale after (ms)</label>
925
+ <input type="text" id="node-input-mqttStaleAfter" placeholder="120000">
926
+ </div>
927
+ </div>
928
+
929
+ <div class="form-row vr-checkbox-row" id="row-victron-mqtt-flags">
930
+ <label for="node-input-mqttStripPortalId"><i class="fa fa-scissors"></i> MQTT options</label>
931
+ <input type="checkbox" id="node-input-mqttStripPortalId">
932
+ <span class="vr-inline-note">Elimina portal id din calea topicului (cand mode = Victron).</span>
933
+ </div>
934
+
935
+ <div class="form-row vr-checkbox-row" id="row-victron-mqtt-keepalive">
936
+ <label for="node-input-mqttKeepaliveEnabled"><i class="fa fa-heartbeat"></i> Victron keepalive</label>
937
+ <input type="checkbox" id="node-input-mqttKeepaliveEnabled">
938
+ <span class="vr-inline-note">Publica periodic <code>R/&lt;Portal ID&gt;/keepalive</code>.</span>
939
+ <input type="text" id="node-input-mqttKeepaliveInterval" style="width:110px; margin-left:8px;" placeholder="30000">
940
+ <span class="vr-inline-note">ms</span>
941
+ </div>
942
+
943
+ <div class="form-row" id="row-victron-mqtt-discover">
944
+ <label></label>
945
+ <button type="button" class="red-ui-button" id="node-victron-read-discover-mqtt"><i class="fa fa-link"></i> Connect & Discover</button>
946
+ <span class="vr-inline-note">Se conecteaza la broker cu setarile de mai sus si populeaza automat root path + campuri.</span>
947
+ </div>
948
+ </div>
949
+
950
+ <div class="vr-section">
951
+ <div class="vr-section-title"><i class="fa fa-line-chart"></i> Influx Mapping</div>
952
+ <p class="vr-section-note">Format output: <code>msg.influx.measurement</code>, <code>msg.influx.tags</code>, <code>msg.influx.fields</code>, <code>msg.influx.timestamp</code>.</p>
953
+
954
+ <div class="vr-grid">
955
+ <div class="form-row">
956
+ <label for="node-input-deviceId"><i class="fa fa-id-badge"></i> Device ID</label>
957
+ <input type="text" id="node-input-deviceId" placeholder="victron">
958
+ </div>
959
+
960
+ <div class="form-row">
961
+ <label for="node-input-topic"><i class="fa fa-comment"></i> Topic (optional)</label>
962
+ <input type="text" id="node-input-topic" placeholder="victron">
963
+ </div>
964
+
965
+ <div class="form-row">
966
+ <label for="node-input-influxMeasurement"><i class="fa fa-area-chart"></i> Measurement</label>
967
+ <input type="text" id="node-input-influxMeasurement" placeholder="victron">
968
+ </div>
969
+
970
+ <div class="form-row">
971
+ <label for="node-input-pollInterval"><i class="fa fa-refresh"></i> Poll interval</label>
972
+ <input type="text" id="node-input-pollInterval" placeholder="5000 sau off">
973
+ </div>
974
+
975
+ <div class="form-row">
976
+ <label for="node-input-payloadMode"><i class="fa fa-list"></i> Payload mode</label>
977
+ <select id="node-input-payloadMode">
978
+ <option value="default">Default operational</option>
979
+ <option value="full">Full payload (nefiltrat)</option>
980
+ <option value="selected">Selected fields</option>
981
+ </select>
982
+ </div>
983
+ </div>
984
+
985
+ <div class="vr-grid" style="margin-top:12px;">
986
+ <div class="form-row">
987
+ <label for="node-input-includeFieldTokens"><i class="fa fa-plus-circle"></i> Include tokens</label>
988
+ <input type="text" id="node-input-includeFieldTokens" placeholder="ex: .dc.,.ac.,battery.,grid.,vebus.">
989
+ </div>
990
+
991
+ <div class="form-row">
992
+ <label for="node-input-excludeFieldTokens"><i class="fa fa-minus-circle"></i> Exclude tokens</label>
993
+ <input type="text" id="node-input-excludeFieldTokens" placeholder=".serial,.id,.name,.heartbeat">
994
+ </div>
995
+
996
+ <div class="form-row">
997
+ <label for="node-input-maxFields"><i class="fa fa-compress"></i> Max fields/msg</label>
998
+ <input type="text" id="node-input-maxFields" placeholder="150">
999
+ </div>
1000
+ </div>
1001
+
1002
+ <div class="form-row vr-checkbox-row">
1003
+ <label for="node-input-onlyNumericFields"><i class="fa fa-hashtag"></i> Numeric only</label>
1004
+ <input type="checkbox" id="node-input-onlyNumericFields">
1005
+ <span class="vr-inline-note">Aplicat profilului Default operational.</span>
1006
+ </div>
1007
+
1008
+ <div class="form-row vr-checkbox-row">
1009
+ <label for="node-input-emitOnlyChanges"><i class="fa fa-exchange"></i> Changes only</label>
1010
+ <input type="checkbox" id="node-input-emitOnlyChanges">
1011
+ <span class="vr-inline-note">Emite imediat la modificări și evită mesajele identice.</span>
1012
+ </div>
1013
+ </div>
1014
+
1015
+ <div class="vr-section" id="row-victron-read-field-list" style="display:none;">
1016
+ <div class="vr-section-title"><i class="fa fa-filter"></i> Select Fields</div>
1017
+ <p class="vr-section-note">Apasa <b>Refresh fields</b>. In modul normal vezi valorile de baza.</p>
1018
+
1019
+ <div class="vr-toolbar">
1020
+ <button type="button" class="red-ui-button" id="node-victron-read-refresh-fields"><i class="fa fa-refresh"></i> Refresh fields</button>
1021
+ <button type="button" class="red-ui-button" id="node-victron-read-select-all"><i class="fa fa-check-square-o"></i> Select all</button>
1022
+ <button type="button" class="red-ui-button" id="node-victron-read-clear-all"><i class="fa fa-square-o"></i> Clear</button>
1023
+ </div>
1024
+
1025
+ <div class="vr-load-status" id="node-victron-read-load-status"></div>
1026
+ <div id="node-victron-read-field-list-container" class="vr-field-list"></div>
1027
+ </div>
1028
+ </div>
1029
+ </script>
1030
+
1031
+ <script type="text/html" data-help-name="victron-read">
1032
+ <p>Nodul <b>victron-read</b> poate citi direct din MQTT Victron sau dintr-o variabila globala si emite date pregatite pentru <code>adt-influxdb</code>.</p>
1033
+ <ul>
1034
+ <li><b>MQTT source</b> foloseste keepalive Victron pentru a solicita si mentine telemetria completa.</li>
1035
+ <li><b>Portal ID</b> poate fi introdus manual sau detectat automat din topicurile primite.</li>
1036
+ <li><b>Default operational</b> aplica filtrele de trafic; <b>Full</b> trimite toate campurile; <b>Selected</b> trimite strict valorile bifate.</li>
1037
+ <li><b>Stale after</b> elimina valorile MQTT care nu au mai fost actualizate, prevenind transmiterea datelor vechi.</li>
1038
+ <li>Pentru selectia campurilor pe MQTT, fa deploy, asteapta mesaje, apoi <b>Refresh fields</b>.</li>
1039
+ </ul>
1040
+
1041
+ <h3>Referinta completa parametri</h3>
1042
+ <ul>
1043
+ <li><code>sourceType</code>: <code>global</code> sau <code>mqtt</code>.</li>
1044
+ <li><code>globalVariable</code> si <code>globalPath</code>: sursa cand se citeste din context global.</li>
1045
+ <li><code>mqttUrl</code>, <code>mqttTopicFilter</code>, <code>mqttQos</code>, <code>mqttClientId</code>, <code>mqttUsername</code>, <code>mqttPassword</code>: conexiunea MQTT.</li>
1046
+ <li><code>mqttTopicPathMode</code>: modul in care topicul MQTT este convertit in cale de payload.</li>
1047
+ <li><code>mqttStripPortalId</code>: elimina portal ID-ul din calea rezultata.</li>
1048
+ <li><code>mqttRootPath</code>: prefixul radacina pentru datele MQTT mapate.</li>
1049
+ <li><code>mqttPortalId</code>: portal ID Victron, manual sau autodetectat.</li>
1050
+ <li><code>mqttKeepaliveEnabled</code> si <code>mqttKeepaliveInterval</code>: mentin fluxul MQTT Victron activ.</li>
1051
+ <li><code>mqttStaleAfter</code>: elimina campuri MQTT care nu au mai fost actualizate.</li>
1052
+ <li><code>emitOnlyChanges</code>: emite doar cand apar modificari.</li>
1053
+ <li><code>includeFieldTokens</code> si <code>excludeFieldTokens</code>: filtre text pentru campurile emise.</li>
1054
+ <li><code>onlyNumericFields</code>: pastreaza doar valori numerice in profilul operational.</li>
1055
+ <li><code>maxFields</code>: limiteaza numarul de campuri per mesaj.</li>
1056
+ <li><code>pollInterval</code>: intervalul de emitere pentru sursa globala sau snapshot MQTT. Valoarea <code>off</code> sau <code>0</code> opreste emiterea periodica si activeaza intrarea pentru trigger manual.</li>
1057
+ <li><code>payloadMode</code>: <code>default</code>, <code>full</code> sau <code>selected</code>.</li>
1058
+ <li><code>payloadFields</code>: lista campurilor selectate.</li>
1059
+ <li><code>deviceId</code>: ID-ul dispozitivului inclus in topic/tag-uri.</li>
1060
+ <li><code>topic</code>: topic-ul de iesire configurat manual.</li>
1061
+ <li><code>influxMeasurement</code>: measurement-ul folosit pentru <code>msg.influx</code>.</li>
1062
+ </ul>
1063
+
1064
+ <h3>Exemplu MQTT operational</h3>
1065
+ <pre>{
1066
+ "topic": "victron/site",
1067
+ "payload": {
1068
+ "battery.soc": 72.4,
1069
+ "grid.power_w": 1300,
1070
+ "pv.power_w": 6400
1071
+ }
1072
+ }</pre>
1073
+ </script>
1074
+