@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,649 @@
1
+ <style>
2
+ .multi-battery-editor {
3
+ --mb-border: #d7dde5;
4
+ --mb-muted: #667085;
5
+ --mb-surface: #f7faf8;
6
+ --mb-head: #edf7f0;
7
+ --mb-danger-bg: #fff3f2;
8
+ --mb-danger-border: #f2c2bf;
9
+ --mb-danger-text: #b54745;
10
+ }
11
+
12
+ .multi-battery-editor .mb-section {
13
+ margin: 0 0 18px;
14
+ padding: 14px 16px 16px;
15
+ border: 1px solid var(--mb-border);
16
+ border-radius: 10px;
17
+ background: var(--mb-surface);
18
+ }
19
+
20
+ .multi-battery-editor .mb-section-title {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 8px;
24
+ margin: 0 0 4px;
25
+ font-size: 15px;
26
+ font-weight: 600;
27
+ color: #243447;
28
+ }
29
+
30
+ .multi-battery-editor .mb-section-note {
31
+ margin: 0 0 14px;
32
+ color: var(--mb-muted);
33
+ font-size: 12px;
34
+ line-height: 1.45;
35
+ }
36
+
37
+ .multi-battery-editor .mb-grid {
38
+ display: grid;
39
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
40
+ gap: 12px 16px;
41
+ }
42
+
43
+ .multi-battery-editor .form-row {
44
+ margin-bottom: 12px;
45
+ }
46
+
47
+ .multi-battery-editor .mb-grid .form-row,
48
+ .multi-battery-editor .form-row:last-child {
49
+ margin-bottom: 0;
50
+ }
51
+
52
+ .multi-battery-editor .form-row > label {
53
+ width: 170px;
54
+ font-weight: 600;
55
+ }
56
+
57
+ .multi-battery-editor .form-row > input[type="text"],
58
+ .multi-battery-editor .form-row > input[type="number"],
59
+ .multi-battery-editor .form-row > select {
60
+ width: calc(100% - 180px);
61
+ }
62
+
63
+ .multi-battery-editor .mb-inline-note {
64
+ margin-left: 8px;
65
+ color: var(--mb-muted);
66
+ line-height: 1.4;
67
+ }
68
+
69
+ .multi-battery-editor .mb-checkbox-row > input[type="checkbox"] {
70
+ display: inline-block;
71
+ width: auto;
72
+ vertical-align: top;
73
+ }
74
+
75
+ .multi-battery-editor .mb-summary {
76
+ display: inline-flex;
77
+ align-items: center;
78
+ gap: 10px;
79
+ padding: 8px 12px;
80
+ border: 1px solid var(--mb-border);
81
+ border-radius: 999px;
82
+ background: #fff;
83
+ color: #344054;
84
+ font-weight: 600;
85
+ }
86
+
87
+ .multi-battery-editor .mb-table-wrap {
88
+ overflow-x: auto;
89
+ border: 1px solid var(--mb-border);
90
+ border-radius: 10px;
91
+ background: #fff;
92
+ }
93
+
94
+ .multi-battery-editor .mb-table {
95
+ width: 100%;
96
+ min-width: 1180px;
97
+ border-collapse: collapse;
98
+ }
99
+
100
+ .multi-battery-editor .mb-table th,
101
+ .multi-battery-editor .mb-table td {
102
+ padding: 8px;
103
+ border-bottom: 1px solid #e6ebf2;
104
+ vertical-align: middle;
105
+ }
106
+
107
+ .multi-battery-editor .mb-table th {
108
+ position: sticky;
109
+ top: 0;
110
+ background: var(--mb-head);
111
+ text-align: left;
112
+ font-size: 12px;
113
+ text-transform: uppercase;
114
+ letter-spacing: 0.03em;
115
+ color: #455468;
116
+ z-index: 1;
117
+ }
118
+
119
+ .multi-battery-editor .mb-table tbody tr:nth-child(even) {
120
+ background: #fafcfb;
121
+ }
122
+
123
+ .multi-battery-editor .mb-table td input {
124
+ width: 100%;
125
+ margin: 0;
126
+ box-sizing: border-box;
127
+ }
128
+
129
+ .multi-battery-editor .mb-table td.mb-col-index {
130
+ width: 44px;
131
+ text-align: center;
132
+ color: var(--mb-muted);
133
+ font-weight: 600;
134
+ }
135
+
136
+ .multi-battery-editor .mb-validation {
137
+ display: none;
138
+ margin-top: 12px;
139
+ padding: 10px 12px;
140
+ border: 1px solid var(--mb-danger-border);
141
+ border-radius: 8px;
142
+ background: var(--mb-danger-bg);
143
+ color: var(--mb-danger-text);
144
+ font-weight: 600;
145
+ }
146
+ </style>
147
+
148
+ <script type="text/javascript">
149
+
150
+ (function () {
151
+
152
+ function equalDefaultShare(count) {
153
+ return count > 0 ? (100 / count) : 100;
154
+ }
155
+
156
+
157
+ function toNum(value, fallback) {
158
+ const numeric = Number(value);
159
+ return Number.isFinite(numeric) ? numeric : fallback;
160
+ }
161
+
162
+
163
+ function readBatteryRows() {
164
+
165
+ const batteries = [];
166
+
167
+ $("#node-input-batteries-container .mb-battery-row").each(function (index) {
168
+
169
+ const row = $(this);
170
+
171
+ batteries.push({
172
+ id: row.find(".mb-battery-id").val().trim() || ("bat" + (index + 1)),
173
+ shareChargePct: toNum(row.find(".mb-battery-share-charge").val(), 0),
174
+ shareDischargePct: toNum(row.find(".mb-battery-share-discharge").val(), 0),
175
+ maxChargeW: toNum(row.find(".mb-battery-max-charge").val(), 150000),
176
+ maxDischargeW: toNum(row.find(".mb-battery-max-discharge").val(), 150000),
177
+ socMin: toNum(row.find(".mb-battery-soc-min").val(), 30),
178
+ socHyst: toNum(row.find(".mb-battery-soc-hyst").val(), 5),
179
+ rampUpW: toNum(row.find(".mb-battery-ramp-up").val(), 1000),
180
+ rampDownW: toNum(row.find(".mb-battery-ramp-down").val(), 25000),
181
+ priorityWeight: toNum(row.find(".mb-battery-priority").val(), 1)
182
+ });
183
+
184
+ });
185
+
186
+ return batteries;
187
+
188
+ }
189
+
190
+
191
+ function validateBatteryDistribution() {
192
+
193
+ const batteries = readBatteryRows();
194
+ const seen = new Set();
195
+ let message = "";
196
+
197
+ batteries.forEach(function (item) {
198
+
199
+ if (message) return;
200
+
201
+ if (!item.id) {
202
+ message = "Fiecare baterie trebuie sa aiba Battery ID.";
203
+ return;
204
+ }
205
+
206
+ if (seen.has(item.id)) {
207
+ message = "Battery ID-urile trebuie sa fie unice.";
208
+ return;
209
+ }
210
+
211
+ seen.add(item.id);
212
+
213
+ if (!Number.isFinite(item.shareChargePct) || !Number.isFinite(item.shareDischargePct)) {
214
+ message = "Procentele de charge/discharge trebuie sa fie numerice.";
215
+ return;
216
+ }
217
+
218
+ if (item.shareChargePct < 0 || item.shareDischargePct < 0) {
219
+ message = "Procentele nu pot fi negative.";
220
+ return;
221
+ }
222
+
223
+ if (!Number.isFinite(item.socMin) || !Number.isFinite(item.socHyst) || item.socMin < 0 || item.socHyst < 0) {
224
+ message = "SOC Min si SOC Hyst + trebuie sa fie valori numerice pozitive.";
225
+ return;
226
+ }
227
+
228
+ });
229
+
230
+ if (!message) {
231
+
232
+ const totalCharge = batteries.reduce(function (sum, item) { return sum + Number(item.shareChargePct || 0); }, 0);
233
+ const totalDischarge = batteries.reduce(function (sum, item) { return sum + Number(item.shareDischargePct || 0); }, 0);
234
+
235
+ if (Math.abs(totalCharge - 100) > 0.01 || Math.abs(totalDischarge - 100) > 0.01) {
236
+ message = "Suma procentelor trebuie sa fie 100%. Charge este " + totalCharge.toFixed(2) + "%, discharge este " + totalDischarge.toFixed(2) + "%.";
237
+ }
238
+
239
+ }
240
+
241
+ $("#node-multi-battery-validation").text(message).toggle(!!message);
242
+ return !message;
243
+
244
+ }
245
+
246
+
247
+ function buildBatteryRows(node, count) {
248
+
249
+ const container = $("#node-input-batteries-container");
250
+ const existing = Array.isArray(node.batteries) ? node.batteries : [];
251
+ const defaultShare = equalDefaultShare(count);
252
+ const table = $(`
253
+ <div class="mb-table-wrap">
254
+ <table class="mb-table">
255
+ <thead>
256
+ <tr>
257
+ <th>#</th>
258
+ <th>Battery ID</th>
259
+ <th>Charge %</th>
260
+ <th>Discharge %</th>
261
+ <th>Max Ch W</th>
262
+ <th>Max Dch W</th>
263
+ <th>SOC Min</th>
264
+ <th>SOC Hyst +%</th>
265
+ <th>Ramp Up</th>
266
+ <th>Ramp Down</th>
267
+ <th>Priority</th>
268
+ </tr>
269
+ </thead>
270
+ <tbody></tbody>
271
+ </table>
272
+ </div>
273
+ `);
274
+
275
+ const tbody = table.find("tbody");
276
+
277
+ for (let index = 0; index < count; index += 1) {
278
+
279
+ const item = existing[index] || {};
280
+ const row = $(`
281
+ <tr class="mb-battery-row">
282
+ <td class="mb-col-index">${index + 1}</td>
283
+ <td><input type="text" class="mb-battery-id" placeholder="bat${index + 1}" value="${item.id || ""}"></td>
284
+ <td><input type="number" class="mb-battery-share-charge" step="0.01" value="${toNum(item.shareChargePct, defaultShare)}"></td>
285
+ <td><input type="number" class="mb-battery-share-discharge" step="0.01" value="${toNum(item.shareDischargePct, defaultShare)}"></td>
286
+ <td><input type="number" class="mb-battery-max-charge" step="1" value="${toNum(item.maxChargeW, 150000)}"></td>
287
+ <td><input type="number" class="mb-battery-max-discharge" step="1" value="${toNum(item.maxDischargeW, 150000)}"></td>
288
+ <td><input type="number" class="mb-battery-soc-min" step="0.1" value="${toNum(item.socMin, 30)}"></td>
289
+ <td><input type="number" class="mb-battery-soc-hyst" step="0.1" value="${toNum(item.socHyst, 5)}"></td>
290
+ <td><input type="number" class="mb-battery-ramp-up" step="1" value="${toNum(item.rampUpW, 1000)}"></td>
291
+ <td><input type="number" class="mb-battery-ramp-down" step="1" value="${toNum(item.rampDownW, 25000)}"></td>
292
+ <td><input type="number" class="mb-battery-priority" step="0.1" value="${toNum(item.priorityWeight, 1)}"></td>
293
+ </tr>
294
+ `);
295
+
296
+ tbody.append(row);
297
+
298
+ }
299
+
300
+ container.empty().append(table);
301
+ container.find("input").off(".multiBatteryValidate").on("keyup.multiBatteryValidate change.multiBatteryValidate", validateBatteryDistribution);
302
+ validateBatteryDistribution();
303
+
304
+ }
305
+
306
+
307
+ RED.nodes.registerType("multi-battery", {
308
+
309
+ category: "Atelierul de Tehnologii",
310
+ color: "#7acb9d",
311
+
312
+ defaults: {
313
+ license: { value: "", type: "atelier-license", required: true },
314
+ name: { value: "" },
315
+ expertMode: { value: false },
316
+ batterySocPath: { value: "payload.battery_soc" },
317
+ externalControlStatusPath: { value: "payload.external_control_status" },
318
+ alarmLevelPath: { value: "payload.battery_alarm_level" },
319
+ operatingStatusPath: { value: "payload.battery_operating_status" },
320
+ deratingReasonPath: { value: "payload.battery_derating_reason" },
321
+ availableChargePath: { value: "" },
322
+ availableDischargePath: { value: "" },
323
+ staleTimeoutMs: { value: 0, validate: RED.validators.number() },
324
+ feedbackTimeoutMs: { value: 5000, validate: RED.validators.number() },
325
+ outputTopic: { value: "bess/setpoint" },
326
+ strategy: { value: "fixed_share" },
327
+ enableDebugOutput: { value: true },
328
+ batteryCount: {
329
+ value: 1,
330
+ validate: function (value) {
331
+ return Number(value) >= 1;
332
+ }
333
+ },
334
+ batteries: {
335
+ value: [{
336
+ id: "bat1",
337
+ shareChargePct: 100,
338
+ shareDischargePct: 100,
339
+ maxChargeW: 150000,
340
+ maxDischargeW: 150000,
341
+ socMin: 30,
342
+ socHyst: 5,
343
+ rampUpW: 1000,
344
+ rampDownW: 25000,
345
+ priorityWeight: 1
346
+ }],
347
+ validate: function (value) {
348
+
349
+ const arr = Array.isArray(value) ? value : [];
350
+ const seen = new Set();
351
+ let totalCharge = 0;
352
+ let totalDischarge = 0;
353
+
354
+ for (const item of arr) {
355
+
356
+ const id = String((item && item.id) || "").trim();
357
+ const charge = Number(item && item.shareChargePct);
358
+ const discharge = Number(item && item.shareDischargePct);
359
+
360
+ if (!id) return false;
361
+ if (seen.has(id)) return false;
362
+ if (!Number.isFinite(charge) || !Number.isFinite(discharge)) return false;
363
+ if (charge < 0 || discharge < 0) return false;
364
+
365
+ seen.add(id);
366
+ totalCharge += charge;
367
+ totalDischarge += discharge;
368
+
369
+ }
370
+
371
+ return arr.length >= 1 &&
372
+ Math.abs(totalCharge - 100) <= 0.01 &&
373
+ Math.abs(totalDischarge - 100) <= 0.01;
374
+
375
+ }
376
+ }
377
+ },
378
+
379
+ inputs: 1,
380
+ outputs: 2,
381
+ icon: "font-awesome/fa-battery-half",
382
+
383
+ label: function () {
384
+ return this.name || "multi-battery";
385
+ },
386
+
387
+ outputLabels: function (index) {
388
+ const batteryCount = Math.max(1, Number(this.batteryCount || 1));
389
+ const debugEnabled = !!this.enableDebugOutput;
390
+ const batteries = Array.isArray(this.batteries) ? this.batteries : [];
391
+
392
+ if (debugEnabled && index === batteryCount) {
393
+ return "diagnostic";
394
+ }
395
+
396
+ const battery = batteries[index] || {};
397
+ return battery.id || ("bat" + (index + 1));
398
+ },
399
+
400
+ oneditprepare: function () {
401
+
402
+ const node = this;
403
+ const countInput = $("#node-input-batteryCount");
404
+ const debugInput = $("#node-input-enableDebugOutput");
405
+ const expertModeInput = $("#node-input-expertMode");
406
+
407
+ function ensureDefaultValue(selector, value) {
408
+ const input = $(selector);
409
+ if (!String(input.val() || "").trim()) {
410
+ input.val(value);
411
+ }
412
+ }
413
+
414
+ function refreshOutputs() {
415
+ const batteryCount = Math.max(1, Number(countInput.val() || 1));
416
+ const debugOutputs = debugInput.prop("checked") ? 1 : 0;
417
+ node.outputs = batteryCount + debugOutputs;
418
+ $("#node-multi-battery-outputs-info").text(String(node.outputs));
419
+ }
420
+
421
+ function rebuild() {
422
+ const count = Math.max(1, Number(countInput.val() || 1));
423
+ buildBatteryRows(node, count);
424
+ refreshOutputs();
425
+ }
426
+
427
+ function refreshExpertMode() {
428
+ $("#node-multi-battery-expert-panel").toggle(expertModeInput.prop("checked"));
429
+ }
430
+
431
+ ensureDefaultValue("#node-input-batterySocPath", "payload.battery_soc");
432
+ ensureDefaultValue("#node-input-externalControlStatusPath", "payload.external_control_status");
433
+ ensureDefaultValue("#node-input-alarmLevelPath", "payload.battery_alarm_level");
434
+ ensureDefaultValue("#node-input-operatingStatusPath", "payload.battery_operating_status");
435
+ ensureDefaultValue("#node-input-deratingReasonPath", "payload.battery_derating_reason");
436
+
437
+ rebuild();
438
+ refreshExpertMode();
439
+ countInput.off(".multiBattery").on("change.multiBattery keyup.multiBattery", rebuild);
440
+ debugInput.off(".multiBattery").on("change.multiBattery", refreshOutputs);
441
+ expertModeInput.off(".multiBattery").on("change.multiBattery", refreshExpertMode);
442
+
443
+ },
444
+
445
+ oneditsave: function () {
446
+ const count = Math.max(1, Number($("#node-input-batteryCount").val() || 1));
447
+ const batteries = readBatteryRows();
448
+ const debugOutputs = $("#node-input-enableDebugOutput").prop("checked") ? 1 : 0;
449
+
450
+ this.batteryCount = count;
451
+ this.batteries = batteries;
452
+ this.outputs = count + debugOutputs;
453
+ }
454
+
455
+ });
456
+
457
+ })();
458
+
459
+ </script>
460
+
461
+
462
+ <script type="text/html" data-template-name="multi-battery">
463
+ <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>
464
+
465
+ <div class="multi-battery-editor">
466
+
467
+ <div class="mb-section">
468
+ <div class="mb-section-title"><i class="fa fa-sitemap"></i> Battery Dispatch</div>
469
+ <p class="mb-section-note">Nodul primeste comanda totala de la <code>bess-control</code> si o distribuie automat pe fiecare baterie, folosind numarul de baterii si procentele configurate. Daca <code>bess-control</code> trimite <code>soc_min_pct</code> si <code>soc_hyst_pct</code>, aceste valori suprascriu pragurile locale pentru toate bateriile.</p>
470
+
471
+ <div class="mb-grid">
472
+ <div class="form-row">
473
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
474
+ <input type="text" id="node-input-name" placeholder="multi-battery">
475
+ </div>
476
+
477
+ <div class="form-row">
478
+ <label for="node-input-batteryCount"><i class="fa fa-sitemap"></i> Batteries</label>
479
+ <input type="number" id="node-input-batteryCount" min="1">
480
+ </div>
481
+ </div>
482
+
483
+ <div class="form-row">
484
+ <label></label>
485
+ <div class="mb-summary">Iesiri active: <span id="node-multi-battery-outputs-info">2</span> <span class="mb-inline-note">cate una pentru fiecare baterie, plus diagnostic daca este activat</span></div>
486
+ </div>
487
+
488
+ <div id="node-input-batteries-container"></div>
489
+ <div class="mb-validation" id="node-multi-battery-validation"></div>
490
+ </div>
491
+
492
+ <div class="mb-section">
493
+ <div class="mb-section-title"><i class="fa fa-exchange"></i> Input Mapping</div>
494
+ <p class="mb-section-note">Configureaza strategia de distribuire si iesirea. Maparea path-urilor de telemetrie este ascunsa implicit ca sa ramana editorul mai curat.</p>
495
+
496
+ <div class="mb-grid">
497
+ <div class="form-row">
498
+ <label for="node-input-strategy"><i class="fa fa-sliders"></i> Strategy</label>
499
+ <select id="node-input-strategy">
500
+ <option value="fixed_share">Fixed Share</option>
501
+ <option value="by_soc">By SOC</option>
502
+ <option value="by_available_power">By Available Power</option>
503
+ <option value="hybrid">Hybrid</option>
504
+ </select>
505
+ </div>
506
+
507
+ <div class="form-row">
508
+ <label for="node-input-outputTopic"><i class="fa fa-sign-out"></i> Output topic</label>
509
+ <input type="text" id="node-input-outputTopic" placeholder="bess/setpoint">
510
+ </div>
511
+ </div>
512
+
513
+ <div class="form-row mb-checkbox-row">
514
+ <label for="node-input-expertMode"><i class="fa fa-wrench"></i> Custom fields</label>
515
+ <input type="checkbox" id="node-input-expertMode">
516
+ <span class="mb-inline-note">Afiseaza path-urile de telemetrie doar cand vrei sa schimbi valorile implicite.</span>
517
+ </div>
518
+
519
+ <div id="node-multi-battery-expert-panel" style="display:none;">
520
+ <p class="mb-section-note">Valorile sunt precompletate pentru payload-urile standard. Modifica-le doar daca telemetria bateriilor ajunge pe alte campuri.</p>
521
+
522
+ <div class="mb-grid">
523
+ <div class="form-row">
524
+ <label for="node-input-batterySocPath"><i class="fa fa-battery-three-quarters"></i> SOC path</label>
525
+ <input type="text" id="node-input-batterySocPath" placeholder="payload.battery_soc">
526
+ </div>
527
+
528
+ <div class="form-row">
529
+ <label for="node-input-externalControlStatusPath"><i class="fa fa-toggle-on"></i> Ext ctrl status path</label>
530
+ <input type="text" id="node-input-externalControlStatusPath" placeholder="payload.external_control_status">
531
+ </div>
532
+
533
+ <div class="form-row">
534
+ <label for="node-input-alarmLevelPath"><i class="fa fa-warning"></i> Alarm path</label>
535
+ <input type="text" id="node-input-alarmLevelPath" placeholder="payload.battery_alarm_level">
536
+ </div>
537
+
538
+ <div class="form-row">
539
+ <label for="node-input-operatingStatusPath"><i class="fa fa-heartbeat"></i> Op status path</label>
540
+ <input type="text" id="node-input-operatingStatusPath" placeholder="payload.battery_operating_status">
541
+ </div>
542
+
543
+ <div class="form-row">
544
+ <label for="node-input-deratingReasonPath"><i class="fa fa-exclamation-circle"></i> Derating path</label>
545
+ <input type="text" id="node-input-deratingReasonPath" placeholder="payload.battery_derating_reason">
546
+ </div>
547
+
548
+ <div class="form-row">
549
+ <label for="node-input-availableChargePath"><i class="fa fa-arrow-down"></i> Avail charge path</label>
550
+ <input type="text" id="node-input-availableChargePath" placeholder="optional">
551
+ </div>
552
+
553
+ <div class="form-row">
554
+ <label for="node-input-availableDischargePath"><i class="fa fa-arrow-up"></i> Avail discharge path</label>
555
+ <input type="text" id="node-input-availableDischargePath" placeholder="optional">
556
+ </div>
557
+ </div>
558
+ </div>
559
+ </div>
560
+
561
+ <div class="mb-section">
562
+ <div class="mb-section-title"><i class="fa fa-clock-o"></i> Timing And Debug</div>
563
+ <p class="mb-section-note">Setari pentru stale detection si iesirea suplimentara de diagnostic.</p>
564
+
565
+ <div class="mb-grid">
566
+ <div class="form-row">
567
+ <label for="node-input-staleTimeoutMs"><i class="fa fa-clock-o"></i> Telemetry stale (ms)</label>
568
+ <input type="text" id="node-input-staleTimeoutMs" placeholder="0">
569
+ </div>
570
+
571
+ <div class="form-row">
572
+ <label for="node-input-feedbackTimeoutMs"><i class="fa fa-clock-o"></i> Feedback stale (ms)</label>
573
+ <input type="text" id="node-input-feedbackTimeoutMs" placeholder="5000">
574
+ </div>
575
+ </div>
576
+
577
+ <div class="form-row mb-checkbox-row">
578
+ <label for="node-input-enableDebugOutput"><i class="fa fa-bug"></i> Debug output</label>
579
+ <input type="checkbox" id="node-input-enableDebugOutput">
580
+ <span class="mb-inline-note">Adauga o iesire suplimentara cu diagnostic complet pentru toate bateriile.</span>
581
+ </div>
582
+ </div>
583
+
584
+ </div>
585
+
586
+ </script>
587
+
588
+
589
+ <script type="text/html" data-help-name="multi-battery">
590
+
591
+ <p>
592
+ Nodul <b>multi-battery</b> primeste comanda totala de la <code>bess-control</code> si o distribuie pe fiecare baterie.
593
+ </p>
594
+
595
+ <h3>Flux recomandat</h3>
596
+ <pre>carlo-gavazzi -> bess-control -> multi-battery -> pylontech-write (x N)
597
+
598
+ pylontech-read ---^
599
+
600
+ pylontech-write feedback ---^</pre>
601
+
602
+ <h3>Rol</h3>
603
+ <ul>
604
+ <li>configurezi numarul de baterii si procentele de distributie</li>
605
+ <li>nodul creeaza automat cate o iesire pentru fiecare baterie</li>
606
+ <li>distribuie puterea dupa <code>fixed_share</code>, <code>by_soc</code>, <code>by_available_power</code> sau <code>hybrid</code></li>
607
+ <li>iesirea fiecarei baterii este compatibila direct cu <code>pylontech-write</code></li>
608
+ <li><code>SOC Hyst +%</code> este diferenta peste <code>SOC Min</code>, nu prag absolut; de exemplu 30 + 5 inseamna reluare descarcare la 35%</li>
609
+ <li>daca <code>bess-control</code> trimite <code>soc_min_pct</code> si <code>soc_hyst_pct</code>, aceste valori se aplica tuturor bateriilor pentru comanda curenta</li>
610
+ </ul>
611
+
612
+ <h3>Input asteptat</h3>
613
+ <pre>{
614
+ "payload": {
615
+ "external_control": true,
616
+ "power_w": 18000,
617
+ "soc_min_pct": 30,
618
+ "soc_hyst_pct": 5
619
+ }
620
+ }</pre>
621
+
622
+ <h3>Referinta completa parametri</h3>
623
+ <ul>
624
+ <li><code>batterySocPath</code>: calea catre SOC-ul bateriei.</li>
625
+ <li><code>externalControlStatusPath</code>: calea catre feedback-ul de control extern.</li>
626
+ <li><code>alarmLevelPath</code>: calea catre nivelul de alarma.</li>
627
+ <li><code>operatingStatusPath</code>: calea catre starea operationala.</li>
628
+ <li><code>deratingReasonPath</code>: calea catre motivul de derating.</li>
629
+ <li><code>availableChargePath</code> si <code>availableDischargePath</code>: cai optionale pentru puterea disponibila la incarcare/descarcare.</li>
630
+ <li><code>staleTimeoutMs</code>: varsta maxima a telemetriei de baterie.</li>
631
+ <li><code>feedbackTimeoutMs</code>: varsta maxima a feedback-ului de scriere.</li>
632
+ <li><code>outputTopic</code>: topic-ul de iesire pentru comenzile distribuite.</li>
633
+ <li><code>strategy</code>: <code>fixed_share</code>, <code>by_soc</code>, <code>by_available_power</code> sau <code>hybrid</code>.</li>
634
+ <li><code>enableDebugOutput</code>: adauga iesire de diagnostic.</li>
635
+ <li><code>batteryCount</code>: numarul de baterii configurate.</li>
636
+ <li><code>batteries</code>: lista bateriilor, cu ID, procent, limite si stare enabled.</li>
637
+ <li><code>batteries-container</code>: zona editorului in care sunt randate randurile din lista <code>batteries</code>.</li>
638
+ </ul>
639
+
640
+ <h3>Exemplu iesire catre doua baterii</h3>
641
+ <pre>{
642
+ "topic": "battery-1",
643
+ "payload": {
644
+ "external_control": true,
645
+ "power_w": 9000
646
+ }
647
+ }</pre>
648
+
649
+ </script>