@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,546 @@
1
+ module.exports = function (RED) {
2
+
3
+ "use strict";
4
+
5
+ const { runLicensedNode } = require("../lib/licensing/node-guard");
6
+
7
+
8
+ const ModbusRTU = require("modbus-serial");
9
+ const {
10
+ atLeast
11
+ } = require("../lib/numbers");
12
+
13
+
14
+ function toNumber(value) {
15
+
16
+ const n = Number(value);
17
+
18
+ return Number.isFinite(n) ? n : null;
19
+
20
+ }
21
+
22
+
23
+ function toPositiveInt(value, fallback) {
24
+
25
+ const n = Math.round(Number(value));
26
+
27
+ return Number.isFinite(n) && n > 0 ? n : fallback;
28
+
29
+ }
30
+
31
+
32
+ function toNonNegativeInt(value, fallback) {
33
+
34
+ const n = Math.round(Number(value));
35
+
36
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
37
+
38
+ }
39
+
40
+
41
+ function toUnsigned16FromSigned(value) {
42
+
43
+ let v = Math.round(Number(value) || 0);
44
+
45
+
46
+ if (v > 32767) v = 32767;
47
+
48
+ if (v < -32768) v = -32768;
49
+
50
+
51
+ return v < 0 ? (65536 + v) : v;
52
+
53
+ }
54
+
55
+
56
+ function firstDefined() {
57
+
58
+ for (let i = 0; i < arguments.length; i += 1) {
59
+
60
+ if (arguments[i] !== undefined) {
61
+
62
+ return arguments[i];
63
+
64
+ }
65
+
66
+ }
67
+
68
+ return undefined;
69
+
70
+ }
71
+
72
+
73
+ function parseBooleanLike(value) {
74
+
75
+ if (typeof value === "boolean") return value;
76
+
77
+
78
+ if (typeof value === "number") {
79
+
80
+ if (value === 85 || value === 1) return true;
81
+
82
+ if (value === 170 || value === 0) return false;
83
+
84
+ }
85
+
86
+
87
+ if (typeof value === "string") {
88
+
89
+ const normalized = value.trim().toLowerCase();
90
+
91
+
92
+ if (["1", "true", "on", "enable", "enabled", "activ", "active", "auto"].includes(normalized)) {
93
+
94
+ return true;
95
+
96
+ }
97
+
98
+
99
+ if (["0", "false", "off", "disable", "disabled", "dezactiv", "inactive", "manual"].includes(normalized)) {
100
+
101
+ return false;
102
+
103
+ }
104
+
105
+
106
+ if (normalized === "85") return true;
107
+
108
+ if (normalized === "170") return false;
109
+
110
+ }
111
+
112
+
113
+ return null;
114
+
115
+ }
116
+
117
+
118
+ function PylontechWriteNode(config) {
119
+
120
+ RED.nodes.createNode(this, config);
121
+
122
+ const node = this;
123
+
124
+ runLicensedNode(node, RED, config, function startLicensedRuntime() {
125
+
126
+
127
+ node.name = String(config.name || "").trim();
128
+
129
+ node.host = String(config.host || "").trim();
130
+
131
+ node.port = toPositiveInt(config.port, 502);
132
+
133
+ node.unitId = toPositiveInt(config.unitId, 1);
134
+
135
+
136
+ node.externalControlRegister = toNonNegativeInt(config.externalControlRegister, 3006);
137
+
138
+ node.externalControlEnabledValue = toNonNegativeInt(config.externalControlEnabledValue, 85);
139
+
140
+ node.externalControlDisabledValue = toNonNegativeInt(config.externalControlDisabledValue, 170);
141
+
142
+
143
+ node.powerRegister = toNonNegativeInt(config.powerRegister, 3007);
144
+
145
+ node.powerScaleKw = atLeast(config.powerScaleKw, 0.000001, 0.1);
146
+
147
+
148
+ let client = new ModbusRTU();
149
+ let operationQueue = Promise.resolve();
150
+
151
+
152
+ async function connect() {
153
+
154
+ if (!client.isOpen) {
155
+
156
+ await client.connectTCP(node.host, { port: node.port });
157
+
158
+ }
159
+
160
+
161
+ client.setID(node.unitId);
162
+
163
+ client.setTimeout(2500);
164
+
165
+ }
166
+
167
+
168
+ async function closeConnection() {
169
+
170
+ try {
171
+
172
+ if (client && client.isOpen) {
173
+
174
+ await client.close();
175
+
176
+ }
177
+
178
+ } catch (e) {
179
+
180
+ // ignore
181
+
182
+ }
183
+
184
+ client = new ModbusRTU();
185
+
186
+ }
187
+
188
+
189
+ function externalControlRaw(enabled) {
190
+
191
+ return enabled
192
+
193
+ ? node.externalControlEnabledValue
194
+
195
+ : node.externalControlDisabledValue;
196
+
197
+ }
198
+
199
+
200
+ function powerRawFromKw(powerKw) {
201
+
202
+ if (!Number.isFinite(powerKw)) {
203
+
204
+ throw new Error("Puterea trebuie să fie numerică");
205
+
206
+ }
207
+
208
+
209
+ if (!Number.isFinite(node.powerScaleKw) || node.powerScaleKw <= 0) {
210
+
211
+ throw new Error("Power Scale (kW/unitate) trebuie să fie > 0");
212
+
213
+ }
214
+
215
+
216
+ const signed = Math.round(powerKw / node.powerScaleKw);
217
+
218
+ return toUnsigned16FromSigned(signed);
219
+
220
+ }
221
+
222
+
223
+ function extractInput(msg) {
224
+
225
+ let powerKw = null;
226
+
227
+ let externalControlEnabled = null;
228
+
229
+ let batteryId = String(
230
+
231
+ firstDefined(
232
+
233
+ msg.batteryId,
234
+
235
+ msg.payload && msg.payload.batteryId,
236
+
237
+ msg.payload && msg.payload.battery_id
238
+
239
+ ) || ""
240
+
241
+ ).trim();
242
+
243
+
244
+ if (typeof msg.payload === "number" || (typeof msg.payload === "string" && String(msg.payload).trim() !== "")) {
245
+
246
+ const direct = Number(msg.payload);
247
+
248
+ if (Number.isFinite(direct)) {
249
+
250
+ powerKw = direct;
251
+
252
+ }
253
+
254
+ } else if (msg.payload && typeof msg.payload === "object") {
255
+
256
+ const p = msg.payload;
257
+
258
+
259
+ const externalRaw = firstDefined(
260
+
261
+ p.external_control,
262
+
263
+ p.externalControl,
264
+
265
+ p.enable_external_control,
266
+
267
+ p.enableExternalControl,
268
+
269
+ p.control_extern,
270
+
271
+ p.enabled,
272
+
273
+ p.enable
274
+
275
+ );
276
+
277
+
278
+ if (externalRaw !== undefined) {
279
+
280
+ externalControlEnabled = parseBooleanLike(externalRaw);
281
+
282
+ }
283
+
284
+
285
+ const powerKwRaw = firstDefined(
286
+
287
+ p.power_kw,
288
+
289
+ p.limit_kw,
290
+
291
+ p.value_kw,
292
+
293
+ p.kw,
294
+
295
+ p.power
296
+
297
+ );
298
+
299
+ const powerWRaw = firstDefined(
300
+
301
+ p.power_w,
302
+
303
+ p.limit_w,
304
+
305
+ p.value_w,
306
+
307
+ p.watts
308
+
309
+ );
310
+
311
+
312
+ if (powerKwRaw !== undefined) {
313
+
314
+ powerKw = toNumber(powerKwRaw);
315
+
316
+ } else if (powerWRaw !== undefined) {
317
+
318
+ const watts = toNumber(powerWRaw);
319
+
320
+ powerKw = Number.isFinite(watts) ? (watts / 1000) : null;
321
+
322
+ }
323
+
324
+ }
325
+
326
+
327
+ if (msg.externalControl !== undefined && externalControlEnabled === null) {
328
+
329
+ externalControlEnabled = parseBooleanLike(msg.externalControl);
330
+
331
+ }
332
+
333
+ if (msg.powerKw !== undefined && powerKw === null) {
334
+
335
+ powerKw = toNumber(msg.powerKw);
336
+
337
+ }
338
+
339
+ if (msg.powerW !== undefined && powerKw === null) {
340
+
341
+ const watts = toNumber(msg.powerW);
342
+
343
+ powerKw = Number.isFinite(watts) ? (watts / 1000) : null;
344
+
345
+ }
346
+
347
+
348
+ if (externalControlEnabled === null && powerKw === null) {
349
+
350
+ throw new Error("msg.payload trebuie să conțină power_kw/power_w sau external_control");
351
+
352
+ }
353
+
354
+
355
+ return {
356
+
357
+ powerKw: powerKw,
358
+
359
+ externalControlEnabled: externalControlEnabled,
360
+
361
+ batteryId: batteryId || null
362
+
363
+ };
364
+
365
+ }
366
+
367
+
368
+ function buildStatusText(powerKw, externalControlEnabled) {
369
+
370
+ const parts = [];
371
+
372
+
373
+ if (externalControlEnabled !== null) {
374
+
375
+ parts.push(externalControlEnabled ? "ext on" : "ext off");
376
+
377
+ }
378
+
379
+
380
+ if (Number.isFinite(powerKw)) {
381
+
382
+ parts.push("P " + powerKw.toFixed(1) + "kW");
383
+
384
+ }
385
+
386
+
387
+ return parts.length ? parts.join(" | ") : "write";
388
+
389
+ }
390
+
391
+
392
+ async function handleInput(msg) {
393
+
394
+ try {
395
+
396
+ await connect();
397
+
398
+
399
+ const input = extractInput(msg);
400
+
401
+ let externalRaw = null;
402
+
403
+ let powerRaw = null;
404
+
405
+
406
+ if (input.externalControlEnabled !== null) {
407
+
408
+ externalRaw = externalControlRaw(input.externalControlEnabled);
409
+
410
+ await client.writeRegister(node.externalControlRegister, externalRaw);
411
+
412
+ }
413
+
414
+
415
+ if (input.powerKw !== null) {
416
+
417
+ powerRaw = powerRawFromKw(input.powerKw);
418
+
419
+ await client.writeRegister(node.powerRegister, powerRaw);
420
+
421
+ }
422
+
423
+
424
+ node.status({
425
+
426
+ fill: "green",
427
+
428
+ shape: "dot",
429
+
430
+ text: buildStatusText(input.powerKw, input.externalControlEnabled)
431
+
432
+ });
433
+
434
+
435
+ node.send({
436
+
437
+ topic: "pylontech/write",
438
+
439
+ batteryId: input.batteryId,
440
+
441
+ payload: {
442
+
443
+ status: "ok",
444
+
445
+ host: node.host,
446
+
447
+ port: node.port,
448
+
449
+ unit_id: node.unitId,
450
+
451
+ battery_id: input.batteryId,
452
+
453
+ external_control_written: input.externalControlEnabled !== null,
454
+
455
+ external_control_enabled: input.externalControlEnabled,
456
+
457
+ external_control_register: input.externalControlEnabled !== null ? node.externalControlRegister : null,
458
+
459
+ external_control_raw: externalRaw,
460
+
461
+ power_written: input.powerKw !== null,
462
+
463
+ power_kw: input.powerKw,
464
+
465
+ power_register: input.powerKw !== null ? node.powerRegister : null,
466
+
467
+ power_raw: powerRaw,
468
+
469
+ power_scale_kw: node.powerScaleKw,
470
+
471
+ ts: new Date().toISOString()
472
+
473
+ }
474
+
475
+ });
476
+
477
+ } catch (err) {
478
+
479
+ node.status({ fill: "red", shape: "ring", text: "error" });
480
+
481
+ node.error(err.message, msg);
482
+
483
+
484
+ node.send({
485
+
486
+ topic: "pylontech/error",
487
+
488
+ batteryId: msg && msg.batteryId ? msg.batteryId : (msg && msg.payload ? (msg.payload.batteryId || msg.payload.battery_id || null) : null),
489
+
490
+ payload: {
491
+
492
+ status: "error",
493
+
494
+ host: node.host,
495
+
496
+ port: node.port,
497
+
498
+ unit_id: node.unitId,
499
+
500
+ battery_id: msg && msg.batteryId ? msg.batteryId : (msg && msg.payload ? (msg.payload.batteryId || msg.payload.battery_id || null) : null),
501
+
502
+ error: err.message,
503
+
504
+ ts: new Date().toISOString()
505
+
506
+ }
507
+
508
+ });
509
+
510
+
511
+ await closeConnection();
512
+
513
+ }
514
+
515
+ }
516
+
517
+ node.on("input", function (msg, send, done) {
518
+ operationQueue = operationQueue.catch(function () {}).then(function () {
519
+ return handleInput(msg);
520
+ });
521
+ operationQueue.then(function () {
522
+ if (done) done();
523
+ }).catch(function (error) {
524
+ if (done) done(error);
525
+ else node.error(error, msg);
526
+ });
527
+ });
528
+
529
+
530
+ node.on("close", async function (done) {
531
+ await operationQueue.catch(function () {});
532
+
533
+ await closeConnection();
534
+
535
+ done();
536
+
537
+ });
538
+
539
+
540
+ });
541
+ }
542
+
543
+
544
+ RED.nodes.registerType("pylontech-write", PylontechWriteNode);
545
+
546
+ };
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ const SIGNALS = ["p", "q", "u_l1n", "u_l2n", "u_l3n", "u_ln_avg", "u_l12", "u_l23", "u_l31", "u_ll_avg", "f"];
4
+ const SUM_SIGNALS = new Set(["p", "q"]);
5
+
6
+ function average(values) {
7
+ const contributors = values.filter(function (value) { return value !== 0; });
8
+ if (!contributors.length) return null;
9
+ return contributors.reduce(function (sum, value) { return sum + value; }, 0) / contributors.length;
10
+ }
11
+
12
+ function finite(value) {
13
+ const number = Number(value);
14
+ return Number.isFinite(number) ? number : null;
15
+ }
16
+
17
+ function firstValue(message, paths) {
18
+ for (const path of paths) {
19
+ const parts = path.split(".");
20
+ let value = message;
21
+ for (const part of parts) value = value == null ? undefined : value[part];
22
+ const number = finite(value);
23
+ if (number !== null) return number;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ const PATHS = {
29
+ p: ["payload.p", "payload.power_w", "payload.active_power_w", "payload.power_total_w", "payload.summary.ac.power_w", "payload.summary.meter.power_w", "payload.summary.power.total_w"],
30
+ q: ["payload.q", "payload.reactive_power_var", "payload.reactive_total_var", "payload.summary.ac.reactive_power_var", "payload.summary.meter.reactive_power_var", "payload.summary.reactive.total_var"],
31
+ f: ["payload.f", "payload.frequency_hz", "payload.summary.ac.frequency_hz", "payload.summary.meter.frequency_hz", "payload.summary.frequency_hz"],
32
+ u_l1n: ["payload.u_l1n", "payload.voltage_l1_n_v", "payload.voltage_l1_v", "payload.summary.ac.voltage_l1_v", "payload.summary.meter.voltage_l1_v"],
33
+ u_l2n: ["payload.u_l2n", "payload.voltage_l2_n_v", "payload.voltage_l2_v", "payload.summary.ac.voltage_l2_v", "payload.summary.meter.voltage_l2_v"],
34
+ u_l3n: ["payload.u_l3n", "payload.voltage_l3_n_v", "payload.voltage_l3_v", "payload.summary.ac.voltage_l3_v", "payload.summary.meter.voltage_l3_v"],
35
+ u_ln_avg: ["payload.u_ln_avg", "payload.voltage_ln_avg_v", "payload.summary.meter.voltage_ln_avg_v"],
36
+ u_l12: ["payload.u_l12", "payload.voltage_l1_l2_v", "payload.summary.ac.voltage_ll_ab_v", "payload.summary.meter.voltage_ll_ab_v", "payload.summary.voltage.l1_l2_v"],
37
+ u_l23: ["payload.u_l23", "payload.voltage_l2_l3_v", "payload.summary.ac.voltage_ll_bc_v", "payload.summary.meter.voltage_ll_bc_v", "payload.summary.voltage.l2_l3_v"],
38
+ u_l31: ["payload.u_l31", "payload.voltage_l3_l1_v", "payload.voltage_l1_l3_v", "payload.summary.ac.voltage_ll_ca_v", "payload.summary.meter.voltage_ll_ca_v", "payload.summary.voltage.l3_l1_v"],
39
+ u_ll_avg: ["payload.u_ll_avg", "payload.voltage_ll_avg_v", "payload.summary.meter.voltage_ll_avg_v"]
40
+ };
41
+
42
+ function normalizeMessage(message) {
43
+ const input = message && message.payload === undefined ? { payload: message } : message;
44
+ const result = {};
45
+ SIGNALS.forEach(function (signal) { result[signal] = firstValue(input, PATHS[signal]); });
46
+ if (result.u_ln_avg === null) {
47
+ const values = [result.u_l1n, result.u_l2n, result.u_l3n].filter(function (value) { return value !== null; });
48
+ result.u_ln_avg = average(values);
49
+ }
50
+ if (result.u_ll_avg === null) {
51
+ const values = [result.u_l12, result.u_l23, result.u_l31].filter(function (value) { return value !== null; });
52
+ result.u_ll_avg = average(values);
53
+ }
54
+ return result;
55
+ }
56
+
57
+ function summarize(items) {
58
+ const result = { active: items.length };
59
+ SIGNALS.forEach(function (signal) {
60
+ const values = items.map(function (item) { return item.values[signal]; }).filter(function (value) { return value !== null && value !== undefined; });
61
+ if (!values.length) return;
62
+ if (SUM_SIGNALS.has(signal)) {
63
+ result[signal] = values.reduce(function (sum, value) { return sum + value; }, 0);
64
+ return;
65
+ }
66
+ const mean = average(values);
67
+ if (mean !== null) result[signal] = mean;
68
+ });
69
+ return result;
70
+ }
71
+
72
+ module.exports = { SIGNALS, average, normalizeMessage, summarize };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ const DEFAULT_GROUPS = [
4
+ { id: "grid_meter", label: "Grid meter", count: 3 },
5
+ { id: "pv_inverter", label: "PV inverter", count: 13 },
6
+ { id: "bess", label: "BESS", count: 1 }
7
+ ];
8
+
9
+ function slug(value, fallback) {
10
+ return String(value || "").toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || fallback;
11
+ }
12
+
13
+ function count(value, fallback) {
14
+ const number = Number(value);
15
+ return Number.isFinite(number) ? Math.max(0, Math.min(99, Math.round(number))) : fallback;
16
+ }
17
+
18
+ function groupList(config) {
19
+ let parsed;
20
+ try {
21
+ parsed = typeof config.aggregatorGroups === "string" ? JSON.parse(config.aggregatorGroups) : config.aggregatorGroups;
22
+ } catch (_) {
23
+ parsed = null;
24
+ }
25
+ if (!Array.isArray(parsed) || !parsed.length) return DEFAULT_GROUPS.map(function (group) { return Object.assign({}, group); });
26
+ const used = new Set();
27
+ return parsed.map(function (group, index) {
28
+ let id = slug(group && group.id, "group_" + (index + 1));
29
+ while (used.has(id)) id += "_" + (index + 1);
30
+ used.add(id);
31
+ return {
32
+ id: id,
33
+ label: String(group && group.label || id).trim() || id,
34
+ count: count(group && group.count, 1)
35
+ };
36
+ });
37
+ }
38
+
39
+ module.exports = { DEFAULT_GROUPS, groupList };