@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,763 @@
1
+ <style>
2
+ .pylontech-read-editor {
3
+ --pr-border: #d7dde5;
4
+ --pr-muted: #667085;
5
+ --pr-surface: #f8fafc;
6
+ }
7
+
8
+ .pylontech-read-editor .pr-section {
9
+ margin: 0 0 18px;
10
+ padding: 14px 16px 16px;
11
+ border: 1px solid var(--pr-border);
12
+ border-radius: 10px;
13
+ background: var(--pr-surface);
14
+ }
15
+
16
+ .pylontech-read-editor .pr-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
+ .pylontech-read-editor .pr-section-note {
27
+ margin: 0 0 14px;
28
+ color: var(--pr-muted);
29
+ font-size: 12px;
30
+ line-height: 1.45;
31
+ }
32
+
33
+ .pylontech-read-editor .pr-grid {
34
+ display: grid;
35
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
36
+ gap: 12px 16px;
37
+ }
38
+
39
+ .pylontech-read-editor .form-row {
40
+ margin-bottom: 12px;
41
+ }
42
+
43
+ .pylontech-read-editor .pr-grid .form-row,
44
+ .pylontech-read-editor .form-row:last-child {
45
+ margin-bottom: 0;
46
+ }
47
+
48
+ .pylontech-read-editor .form-row > label {
49
+ width: 150px;
50
+ font-weight: 600;
51
+ }
52
+
53
+ .pylontech-read-editor .form-row > input[type="text"],
54
+ .pylontech-read-editor .form-row > select {
55
+ width: calc(100% - 160px);
56
+ }
57
+
58
+ .pylontech-read-editor .pr-checkbox-row > input[type="checkbox"] {
59
+ display: inline-block;
60
+ width: auto;
61
+ vertical-align: top;
62
+ }
63
+
64
+ .pylontech-read-editor .pr-inline-note {
65
+ margin-left: 8px;
66
+ color: var(--pr-muted);
67
+ line-height: 1.4;
68
+ }
69
+
70
+ .pylontech-read-editor .pr-field-list {
71
+ max-height: 320px;
72
+ overflow: auto;
73
+ padding: 12px;
74
+ border: 1px solid var(--pr-border);
75
+ border-radius: 10px;
76
+ background: #fff;
77
+ }
78
+
79
+ .pylontech-read-editor .pr-field-group {
80
+ margin-bottom: 14px;
81
+ }
82
+
83
+ .pylontech-read-editor .pr-field-group:last-child {
84
+ margin-bottom: 0;
85
+ }
86
+
87
+ .pylontech-read-editor .pr-field-group-title {
88
+ margin: 0 0 8px;
89
+ font-size: 12px;
90
+ font-weight: 700;
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.03em;
93
+ color: #344054;
94
+ }
95
+
96
+ .pylontech-read-editor .pr-field-group-grid {
97
+ display: grid;
98
+ grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
99
+ gap: 8px 16px;
100
+ }
101
+
102
+ .pylontech-read-editor .pr-field-item {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 8px;
106
+ margin: 0;
107
+ line-height: 1.35;
108
+ }
109
+
110
+ .pylontech-read-editor .pr-toolbar {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 8px;
114
+ margin-bottom: 10px;
115
+ }
116
+ </style>
117
+
118
+ <script type="text/javascript">
119
+
120
+ (function () {
121
+
122
+ const FIELD_GROUPS = [
123
+ {
124
+ label: "System",
125
+ fields: [
126
+ "system_mode",
127
+ "system_heartbeat",
128
+ "system_status",
129
+ "system_timing_status",
130
+ "system_timing_failure_reason"
131
+ ]
132
+ },
133
+ {
134
+ label: "Battery Core",
135
+ fields: [
136
+ "system_bus_voltage",
137
+ "battery_voltage",
138
+ "battery_total_voltage",
139
+ "battery_current",
140
+ "battery_temperature",
141
+ "battery_terminal_temperature",
142
+ "battery_cell_average_voltage",
143
+ "battery_insulation_resistance",
144
+ "battery_soc",
145
+ "battery_soe",
146
+ "battery_soh",
147
+ "battery_cycle_count",
148
+ "battery_operating_status",
149
+ "battery_charge_discharge_status"
150
+ ]
151
+ },
152
+ {
153
+ label: "Capacity And Energy",
154
+ fields: [
155
+ "battery_chargeable_capacity",
156
+ "battery_dischargeable_capacity",
157
+ "battery_chargeable_energy",
158
+ "battery_dischargeable_energy",
159
+ "battery_single_charge_capacity",
160
+ "battery_single_discharge_capacity",
161
+ "battery_daily_charge_capacity",
162
+ "battery_daily_discharge_capacity",
163
+ "battery_cumulative_charge_capacity",
164
+ "battery_cumulative_discharge_capacity",
165
+ "battery_rated_voltage",
166
+ "battery_rated_capacity",
167
+ "battery_rated_energy"
168
+ ]
169
+ },
170
+ {
171
+ label: "Suggested Limits",
172
+ fields: [
173
+ "battery_suggest_charge_voltage",
174
+ "battery_suggest_charge_current",
175
+ "battery_suggest_charge_power_w",
176
+ "battery_suggest_discharge_voltage",
177
+ "battery_suggest_discharge_current",
178
+ "battery_suggest_discharge_power_w"
179
+ ]
180
+ },
181
+ {
182
+ label: "Cells And Thermal",
183
+ fields: [
184
+ "battery_cell_max_voltage",
185
+ "battery_cell_max_voltage_id",
186
+ "battery_cell_min_voltage",
187
+ "battery_cell_min_voltage_id",
188
+ "battery_cell_max_temperature",
189
+ "battery_cell_max_temperature_id",
190
+ "battery_cell_min_temperature",
191
+ "battery_cell_min_temperature_id",
192
+ "battery_module_max_voltage",
193
+ "battery_module_max_voltage_id",
194
+ "battery_module_min_voltage",
195
+ "battery_module_min_voltage_id",
196
+ "battery_module_max_temperature",
197
+ "battery_module_max_temperature_id",
198
+ "battery_module_min_temperature",
199
+ "battery_module_min_temperature_id",
200
+ "battery_module_terminal_max_temperature",
201
+ "battery_module_terminal_max_temperature_id",
202
+ "battery_module_terminal_min_temperature",
203
+ "battery_module_terminal_min_temperature_id",
204
+ "battery_cells_per_cluster"
205
+ ]
206
+ },
207
+ {
208
+ label: "Status And Alarms",
209
+ fields: [
210
+ "battery_status_flag",
211
+ "battery_switch_flag",
212
+ "battery_alarm_level",
213
+ "battery_zero_level_error_code_1",
214
+ "battery_zero_level_error_code_2",
215
+ "battery_zero_level_error_code_3",
216
+ "battery_zero_level_error_code_4",
217
+ "battery_alarm_level_1_status_1",
218
+ "battery_alarm_level_1_status_2",
219
+ "battery_alarm_level_1_status_3",
220
+ "battery_alarm_level_1_status_4",
221
+ "battery_alarm_level_1_status_5",
222
+ "battery_alarm_level_1_status_6",
223
+ "battery_alarm_level_2_status_1",
224
+ "battery_alarm_level_2_status_2",
225
+ "battery_alarm_level_2_status_3",
226
+ "battery_alarm_level_2_status_4",
227
+ "battery_alarm_level_2_status_5",
228
+ "battery_alarm_level_2_status_6",
229
+ "battery_alarm_level_3_status_1",
230
+ "battery_alarm_level_3_status_2",
231
+ "battery_alarm_level_3_status_3",
232
+ "battery_alarm_level_3_status_4",
233
+ "battery_alarm_level_3_status_5",
234
+ "battery_alarm_level_3_status_6"
235
+ ]
236
+ },
237
+ {
238
+ label: "Cluster Topology",
239
+ fields: [
240
+ "battery_online_clusters",
241
+ "battery_total_clusters",
242
+ "battery_merged_clusters",
243
+ "battery_modules_per_cluster",
244
+ "battery_cells_per_cluster",
245
+ "battery_cluster_bus_connect_flag_1_16",
246
+ "battery_cluster_bus_connect_flag_17_32",
247
+ "battery_cluster_online_flag_1_16",
248
+ "battery_cluster_online_flag_17_32"
249
+ ]
250
+ },
251
+ {
252
+ label: "Environment",
253
+ fields: [
254
+ "battery_module_humidity_max",
255
+ "battery_module_humidity_max_id",
256
+ "battery_module_humidity_min",
257
+ "battery_module_humidity_min_id",
258
+ "battery_module_smoke_concentration_max",
259
+ "battery_module_smoke_concentration_max_id",
260
+ "battery_module_smoke_concentration_min",
261
+ "battery_module_smoke_concentration_min_id"
262
+ ]
263
+ },
264
+ {
265
+ label: "PCS Grid Load",
266
+ fields: [
267
+ "pcs_count",
268
+ "pcs_status",
269
+ "pcs_device_operating_status",
270
+ "pcs_charge_discharge_status",
271
+ "pcs_grid_mode",
272
+ "pcs_work_mode",
273
+ "pcs_operating_status",
274
+ "pcs_alarm_error_code_1",
275
+ "pcs_alarm_error_code_2",
276
+ "pcs_alarm_error_code_3",
277
+ "pcs_alarm_error_code_4",
278
+ "pcs_total_active_power_w",
279
+ "pcs_total_reactive_power_var",
280
+ "pcs_total_apparent_power_va",
281
+ "pcs_total_power_factor",
282
+ "pcs_frequency_hz",
283
+ "grid_total_active_power_w",
284
+ "grid_total_reactive_power_var",
285
+ "grid_total_apparent_power_va",
286
+ "grid_total_power_factor",
287
+ "grid_frequency_hz",
288
+ "load_total_active_power_w",
289
+ "load_total_reactive_power_var",
290
+ "load_total_apparent_power_va",
291
+ "load_total_power_factor",
292
+ "load_frequency_hz",
293
+ "pcs_dc_input_voltage",
294
+ "pcs_dc_bus_voltage",
295
+ "pcs_dc_current",
296
+ "pcs_dc_power_w",
297
+ "pcs_dc_cumulative_charge_capacity",
298
+ "pcs_dc_cumulative_discharge_capacity"
299
+ ]
300
+ },
301
+ {
302
+ label: "PV",
303
+ fields: [
304
+ "pv_count",
305
+ "pv_status",
306
+ "pv_operating_status",
307
+ "pv_alarm_error_code_1",
308
+ "pv_alarm_error_code_2",
309
+ "pv_alarm_error_code_3",
310
+ "pv_alarm_error_code_4",
311
+ "pv_total_power_w",
312
+ "pv1_total_power_w",
313
+ "pv2_total_power_w",
314
+ "pv3_total_power_w",
315
+ "pv1_input_voltage",
316
+ "pv2_input_voltage",
317
+ "pv3_input_voltage",
318
+ "pv_total_current",
319
+ "pv1_total_current",
320
+ "pv2_total_current",
321
+ "pv3_total_current",
322
+ "pv_dc_bus_voltage",
323
+ "pv_dc_bus_power_w",
324
+ "pv_leakage_current",
325
+ "pv_module_temperature",
326
+ "pv_cumulative_production_kwh",
327
+ "pv_environment_temperature"
328
+ ]
329
+ },
330
+ {
331
+ label: "Auxiliary Devices",
332
+ fields: [
333
+ "fire_system_count",
334
+ "fire_system_1_status",
335
+ "fire_system_1_flag",
336
+ "fire_system_2_status",
337
+ "fire_system_2_flag",
338
+ "hvac_count",
339
+ "hvac_status",
340
+ "hvac_operating_status",
341
+ "hvac_error_code",
342
+ "hvac_inner_temperature",
343
+ "hvac_inner_humidity",
344
+ "hvac_component_switch_status",
345
+ "liquid_cooling_count",
346
+ "liquid_cooling_status",
347
+ "liquid_cooling_work_status",
348
+ "liquid_cooling_operating_status",
349
+ "liquid_supply_temperature",
350
+ "liquid_return_temperature",
351
+ "liquid_environment_temperature",
352
+ "liquid_supply_pressure",
353
+ "liquid_return_pressure",
354
+ "liquid_main_component_status",
355
+ "liquid_error_code",
356
+ "liquid_error_level",
357
+ "dehumidifier_count",
358
+ "dehumidifier_status",
359
+ "dehumidifier_work_status",
360
+ "dehumidifier_status_flag",
361
+ "dehumidifier_temperature",
362
+ "dehumidifier_humidity"
363
+ ]
364
+ },
365
+ {
366
+ label: "String Summary",
367
+ fields: [
368
+ "string_bus_voltage",
369
+ "string_battery_voltage",
370
+ "string_current",
371
+ "string_temperature",
372
+ "string_terminal_temperature",
373
+ "string_cell_average_voltage",
374
+ "string_positive_ground_resistance",
375
+ "string_negative_ground_resistance",
376
+ "string_soc",
377
+ "string_soe",
378
+ "string_soh",
379
+ "string_cycle_count",
380
+ "string_chargeable_capacity",
381
+ "string_dischargeable_capacity",
382
+ "string_chargeable_energy",
383
+ "string_dischargeable_energy",
384
+ "string_operating_status",
385
+ "string_charge_discharge_status",
386
+ "string_suggest_charge_power_w",
387
+ "string_suggest_discharge_power_w",
388
+ "string_cell_max_voltage",
389
+ "string_cell_min_voltage",
390
+ "string_cell_max_temperature",
391
+ "string_cell_min_temperature",
392
+ "string_cells_count",
393
+ "string_modules_count",
394
+ "string_alarm_level"
395
+ ]
396
+ }
397
+ ];
398
+
399
+
400
+ function normalizePayloadFields(payloadFields) {
401
+
402
+ if (Array.isArray(payloadFields)) {
403
+
404
+ const filtered = payloadFields
405
+ .map(function (value) { return String(value || "").trim(); })
406
+ .filter(Boolean);
407
+
408
+ if (!filtered.length) {
409
+ return ["full"];
410
+ }
411
+
412
+ const unique = Array.from(new Set(filtered));
413
+
414
+ if (unique.includes("full")) {
415
+ return ["full"];
416
+ }
417
+
418
+ return unique;
419
+ }
420
+
421
+
422
+ if (typeof payloadFields === "string") {
423
+
424
+ const trimmed = payloadFields.trim();
425
+
426
+ if (!trimmed) {
427
+ return ["full"];
428
+ }
429
+
430
+ if (trimmed[0] === "[") {
431
+ try {
432
+ const parsed = JSON.parse(trimmed);
433
+ if (Array.isArray(parsed)) {
434
+ return normalizePayloadFields(parsed);
435
+ }
436
+ } catch (e) {
437
+ // ignore
438
+ }
439
+ }
440
+
441
+ return [trimmed];
442
+ }
443
+
444
+ return ["full"];
445
+ }
446
+
447
+
448
+ function readSelectedFields() {
449
+
450
+ if ($("#node-input-bessControl").prop("checked")) {
451
+ return [
452
+ "battery_soc",
453
+ "battery_alarm_level",
454
+ "battery_operating_status",
455
+ "battery_charge_discharge_status"
456
+ ];
457
+ }
458
+
459
+ if ($("#node-input-payloadMode").val() === "full") {
460
+ return ["full"];
461
+ }
462
+
463
+ const selected = [];
464
+
465
+ $(".pr-field-checkbox").each(function () {
466
+ if ($(this).prop("checked")) {
467
+ selected.push($(this).val());
468
+ }
469
+ });
470
+
471
+ return selected.length ? selected : ["full"];
472
+ }
473
+
474
+
475
+ function renderFieldList(selectedFields) {
476
+
477
+ const container = $("#node-input-payloadFields-list");
478
+ const selected = new Set(normalizePayloadFields(selectedFields));
479
+
480
+ container.empty();
481
+
482
+ FIELD_GROUPS.forEach(function (group) {
483
+
484
+ const groupEl = $('<div class="pr-field-group"></div>');
485
+ const titleEl = $('<div class="pr-field-group-title"></div>').text(group.label);
486
+ const gridEl = $('<div class="pr-field-group-grid"></div>');
487
+
488
+ group.fields.forEach(function (field) {
489
+
490
+ const row = $(`
491
+ <label class="pr-field-item">
492
+ <input type="checkbox" class="pr-field-checkbox" value="${field}">
493
+ <span>${field}</span>
494
+ </label>
495
+ `);
496
+
497
+ row.find("input").prop("checked", selected.has(field));
498
+ gridEl.append(row);
499
+
500
+ });
501
+
502
+ groupEl.append(titleEl).append(gridEl);
503
+ container.append(groupEl);
504
+
505
+ });
506
+
507
+ }
508
+
509
+
510
+ function refreshModeUI() {
511
+
512
+ const bessControl = $("#node-input-bessControl").prop("checked");
513
+ const payloadMode = $("#node-input-payloadMode").val();
514
+ const hideFields = bessControl || payloadMode === "full";
515
+
516
+ $("#node-pylontech-read-fields-wrap").toggle(!hideFields);
517
+ $("#node-pylontech-read-bess-note").toggle(bessControl);
518
+ $("#node-pylontech-read-battery-id-row").toggle(bessControl);
519
+ $("#node-input-batteryId").toggleClass("input-error", bessControl && !String($("#node-input-batteryId").val() || "").trim());
520
+ }
521
+
522
+
523
+ RED.nodes.registerType("pylontech-read", {
524
+
525
+ category: "Atelierul de Tehnologii",
526
+ color: "#6ec6ff",
527
+
528
+ defaults: {
529
+ license: { value: "", type: "atelier-license", required: true },
530
+ name: { value: "" },
531
+ host: { value: "", required: true },
532
+ port: { value: 502, required: true, validate: RED.validators.number() },
533
+ unitId: { value: 1, required: true, validate: RED.validators.number() },
534
+ functionCode: { value: "fc4" },
535
+ pollInterval: {
536
+ value: 5000,
537
+ required: true,
538
+ validate: function (value) {
539
+ const text = String(value || "").trim().toLowerCase();
540
+ if (text === "off") return true;
541
+ const n = Number(text);
542
+ return Number.isFinite(n) && n >= 0;
543
+ }
544
+ },
545
+ bessControl: { value: false },
546
+ batteryId: {
547
+ value: "",
548
+ validate: function (value) {
549
+ const bessControlChecked = $("#node-input-bessControl").length
550
+ ? $("#node-input-bessControl").prop("checked")
551
+ : !!this.bessControl;
552
+
553
+ return !bessControlChecked || String(value || "").trim().length > 0;
554
+ }
555
+ },
556
+ payloadMode: { value: "full" },
557
+ payloadFields: { value: ["full"] }
558
+ },
559
+
560
+ inputs: 0,
561
+ outputs: 1,
562
+ icon: "font-awesome/fa-battery-three-quarters",
563
+
564
+ label: function () {
565
+ return this.name || this.batteryId || this.host || "pylontech-read";
566
+ },
567
+
568
+ oneditprepare: function () {
569
+
570
+ const node = this;
571
+
572
+ const selected = normalizePayloadFields(this.payloadFields || ["full"]);
573
+ const initialMode = selected.includes("full") ? "full" : "selected";
574
+
575
+ function syncInputs() {
576
+ const text = String($("#node-input-pollInterval").val() || "").trim().toLowerCase();
577
+ node.inputs = text === "off" || text === "0" ? 1 : 0;
578
+ }
579
+
580
+ $("#node-input-payloadMode").val(initialMode);
581
+ renderFieldList(selected);
582
+ refreshModeUI();
583
+
584
+ $("#node-input-payloadMode").off(".pylontechRead").on("change.pylontechRead", refreshModeUI);
585
+ $("#node-input-bessControl").off(".pylontechRead").on("change.pylontechRead", refreshModeUI);
586
+ $("#node-input-batteryId").off(".pylontechRead").on("keyup.pylontechRead change.pylontechRead", refreshModeUI);
587
+ $("#node-input-pollInterval").off(".pylontechRead").on("keyup.pylontechRead change.pylontechRead", syncInputs);
588
+ $("#node-input-host").off(".pylontechReadName").on("input.pylontechReadName change.pylontechReadName", function () {
589
+ $("#node-input-name").val(String($(this).val() || "").trim());
590
+ });
591
+ syncInputs();
592
+
593
+ $("#node-pylontech-select-all").off(".pylontechRead").on("click.pylontechRead", function (event) {
594
+ event.preventDefault();
595
+ $(".pr-field-checkbox").prop("checked", true);
596
+ });
597
+
598
+ $("#node-pylontech-clear-all").off(".pylontechRead").on("click.pylontechRead", function (event) {
599
+ event.preventDefault();
600
+ $(".pr-field-checkbox").prop("checked", false);
601
+ });
602
+ },
603
+
604
+ oneditsave: function () {
605
+ this.payloadFields = readSelectedFields();
606
+ const text = String($("#node-input-pollInterval").val() || "").trim().toLowerCase();
607
+ this.inputs = text === "off" || text === "0" ? 1 : 0;
608
+ $("#node-input-host").off(".pylontechReadName");
609
+ },
610
+
611
+ oneditcancel: function () {
612
+ $("#node-input-host").off(".pylontechReadName");
613
+ }
614
+
615
+ });
616
+
617
+ })();
618
+
619
+ </script>
620
+
621
+
622
+ <script type="text/html" data-template-name="pylontech-read">
623
+ <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>
624
+
625
+ <div class="pylontech-read-editor">
626
+
627
+ <div class="pr-section">
628
+ <div class="pr-section-title"><i class="fa fa-plug"></i> Connection</div>
629
+ <p class="pr-section-note">Configuratia de citire Modbus TCP pentru bateria sau EMS-ul Pylontech.</p>
630
+
631
+ <div class="pr-grid">
632
+ <div class="form-row">
633
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
634
+ <input type="text" id="node-input-name" placeholder="pylontech-read">
635
+ </div>
636
+
637
+ <div class="form-row" id="node-pylontech-read-battery-id-row" style="display:none;">
638
+ <label for="node-input-batteryId"><i class="fa fa-id-badge"></i> Battery ID</label>
639
+ <input type="text" id="node-input-batteryId" placeholder="bat1">
640
+ </div>
641
+
642
+ <div class="form-row">
643
+ <label for="node-input-host"><i class="fa fa-globe"></i> Host</label>
644
+ <input type="text" id="node-input-host" placeholder="192.168.1.100">
645
+ </div>
646
+
647
+ <div class="form-row">
648
+ <label for="node-input-port"><i class="fa fa-ellipsis-h"></i> Port</label>
649
+ <input type="text" id="node-input-port" placeholder="502">
650
+ </div>
651
+
652
+ <div class="form-row">
653
+ <label for="node-input-unitId"><i class="fa fa-hashtag"></i> Unit ID</label>
654
+ <input type="text" id="node-input-unitId" placeholder="1">
655
+ </div>
656
+
657
+ <div class="form-row">
658
+ <label for="node-input-functionCode"><i class="fa fa-exchange"></i> Function Code</label>
659
+ <select id="node-input-functionCode">
660
+ <option value="fc4">FC4 - Input Registers</option>
661
+ <option value="fc3">FC3 - Holding Registers</option>
662
+ </select>
663
+ </div>
664
+
665
+ <div class="form-row">
666
+ <label for="node-input-pollInterval"><i class="fa fa-refresh"></i> Poll interval</label>
667
+ <input type="text" id="node-input-pollInterval" placeholder="5000 sau off">
668
+ </div>
669
+ </div>
670
+ </div>
671
+
672
+ <div class="pr-section">
673
+ <div class="pr-section-title"><i class="fa fa-filter"></i> Payload</div>
674
+ <p class="pr-section-note">Poti citi tot payload-ul, doar campurile selectate sau modul special pentru integrarea cu controlul BESS.</p>
675
+
676
+ <div class="form-row pr-checkbox-row">
677
+ <label for="node-input-bessControl"><i class="fa fa-link"></i> BESS control mode</label>
678
+ <input type="checkbox" id="node-input-bessControl">
679
+ <span class="pr-inline-note">Trimite <code>battery_soc</code>, <code>battery_alarm_level</code>, <code>battery_operating_status</code> si <code>battery_charge_discharge_status</code> pentru lantul cu <code>multi-battery</code>.</span>
680
+ </div>
681
+
682
+ <div class="form-row">
683
+ <label for="node-input-payloadMode"><i class="fa fa-list"></i> Payload mode</label>
684
+ <select id="node-input-payloadMode">
685
+ <option value="full">Full payload</option>
686
+ <option value="selected">Selected fields</option>
687
+ </select>
688
+ </div>
689
+
690
+ <div class="form-row" id="node-pylontech-read-bess-note" style="display:none;">
691
+ <label></label>
692
+ <span class="pr-inline-note">In modul BESS control, selectia de campuri este ignorata.</span>
693
+ </div>
694
+
695
+ <div id="node-pylontech-read-fields-wrap">
696
+ <div class="pr-toolbar">
697
+ <a href="#" class="button" id="node-pylontech-select-all"><i class="fa fa-check-square-o"></i> Select all</a>
698
+ <a href="#" class="button" id="node-pylontech-clear-all"><i class="fa fa-square-o"></i> Clear all</a>
699
+ </div>
700
+
701
+ <div class="pr-field-list" id="node-input-payloadFields-list"></div>
702
+ </div>
703
+ </div>
704
+
705
+ </div>
706
+
707
+ </script>
708
+
709
+
710
+ <script type="text/html" data-help-name="pylontech-read">
711
+
712
+ <p>
713
+ Nodul <b>pylontech-read</b> citeste registrele Pylontech/EMS prin Modbus TCP.
714
+ </p>
715
+
716
+ <p>
717
+ Pentru protocolul <b>Pylon LEMS</b>, setarea implicita este <code>FC4 - Input Registers</code>.
718
+ Nodul nu citeste un bloc mare de la o adresa de start; construieste automat ferestre mici de
719
+ citire doar pentru registrele utile. <code>battery_soc</code> este citit direct din registrul
720
+ <code>9008</code>, conform documentului Modbus.
721
+ </p>
722
+
723
+ <h3>Moduri de iesire</h3>
724
+ <ul>
725
+ <li><b>Full payload</b>: emite un singur mesaj cu toate campurile cunoscute</li>
726
+ <li><b>Selected fields</b>: emite valori doar pentru campurile bifate</li>
727
+ <li><b>BESS control mode</b>: emite <code>battery_soc</code>, <code>battery_alarm_level</code>, <code>battery_operating_status</code> si <code>battery_charge_discharge_status</code></li>
728
+ </ul>
729
+
730
+ <h3>Unitati utile</h3>
731
+ <ul>
732
+ <li><code>battery_soc</code>, <code>battery_soe</code>, <code>battery_soh</code>, <code>string_soc</code>, <code>string_soe</code> si <code>string_soh</code> sunt emise in procente 0..100.</li>
733
+ <li>Frecventele sunt emise in Hz, iar factorul de putere este emis 0..1.</li>
734
+ <li>Umiditatile sunt emise in procente 0..100.</li>
735
+ </ul>
736
+
737
+ <h3>Integrare recomandata</h3>
738
+ <pre>pylontech-read -> multi-battery -> pylontech-write</pre>
739
+
740
+ <h3>Referinta completa parametri</h3>
741
+ <ul>
742
+ <li><code>host</code>, <code>port</code>, <code>unitId</code>: conexiunea Modbus TCP catre baterie/EMS.</li>
743
+ <li><code>functionCode</code>: FC4 pentru Input Registers sau FC3 pentru Holding Registers.</li>
744
+ <li><code>pollInterval</code>: intervalul de citire in milisecunde. Valoarea <code>off</code> sau <code>0</code> opreste poll-ul si activeaza intrarea pentru trigger manual.</li>
745
+ <li><code>bessControl</code>: emite payload compact pentru <code>multi-battery</code>.</li>
746
+ <li><code>batteryId</code>: ID-ul bateriei transmis in topic si payload.</li>
747
+ <li><code>payloadMode</code>: <code>full</code> sau <code>selected</code>.</li>
748
+ <li><code>payloadFields</code>: lista campurilor selectate cand <code>payloadMode = selected</code>.</li>
749
+ <li><code>payloadFields-list</code>: zona editorului in care sunt afisate checkbox-urile pentru <code>payloadFields</code>.</li>
750
+ </ul>
751
+
752
+ <h3>Exemplu BESS control mode</h3>
753
+ <pre>{
754
+ "topic": "battery-1",
755
+ "battery_id": "battery-1",
756
+ "payload": {
757
+ "battery_soc": 76,
758
+ "battery_alarm_level": 0,
759
+ "battery_operating_status": 1
760
+ }
761
+ }</pre>
762
+
763
+ </script>