@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,338 @@
1
+ module.exports = function (RED) {
2
+ "use strict";
3
+
4
+ const { runLicensedNode } = require("../lib/licensing/node-guard");
5
+
6
+ const ModbusRTU = require("modbus-serial");
7
+ const {
8
+ profileForModel,
9
+ normalizeModel
10
+ } = require("../lib/adam-6000-profiles");
11
+
12
+ function toInt(value, fallback) {
13
+ const n = Math.round(Number(value));
14
+ return Number.isFinite(n) ? n : fallback;
15
+ }
16
+
17
+ function toPositiveInt(value, fallback) {
18
+ const n = toInt(value, fallback);
19
+ return n > 0 ? n : fallback;
20
+ }
21
+
22
+ function toNonNegativeInt(value, fallback) {
23
+ const n = toInt(value, fallback);
24
+ return n >= 0 ? n : fallback;
25
+ }
26
+
27
+ function toFinite(value, fallback) {
28
+ const n = Number(value);
29
+ return Number.isFinite(n) ? n : fallback;
30
+ }
31
+
32
+ function manualAddressToOffset(address, area) {
33
+ const n = toInt(address, 0);
34
+ if (area === "holding" && n >= 40001) return n - 40001;
35
+ if (area === "coil" && n >= 1) return n - 1;
36
+ return Math.max(0, n);
37
+ }
38
+
39
+ function resolveAddress(address, area, addressMode) {
40
+ if (addressMode === "zero") {
41
+ return Math.max(0, toInt(address, 0));
42
+ }
43
+ return manualAddressToOffset(address, area);
44
+ }
45
+
46
+ function parseBooleanLike(value) {
47
+ if (typeof value === "boolean") return value;
48
+ if (typeof value === "number") {
49
+ if (value === 1) return true;
50
+ if (value === 0) return false;
51
+ }
52
+ if (typeof value === "string") {
53
+ const normalized = value.trim().toLowerCase();
54
+ if (["1", "true", "on", "enable", "enabled", "activ", "active"].includes(normalized)) return true;
55
+ if (["0", "false", "off", "disable", "disabled", "dezactiv", "inactive"].includes(normalized)) return false;
56
+ }
57
+ return null;
58
+ }
59
+
60
+ function toUnsigned16(value) {
61
+ let v = Math.round(Number(value) || 0);
62
+ if (v < 0) v = 0;
63
+ if (v > 65535) v = 65535;
64
+ return v;
65
+ }
66
+
67
+ function rawAoFromValue(value, scale, offset) {
68
+ const n = Number(value);
69
+ if (!Number.isFinite(n)) {
70
+ throw new Error("AO value trebuie sa fie numeric");
71
+ }
72
+ if (!Number.isFinite(scale) || scale === 0) {
73
+ throw new Error("AO scale trebuie sa fie diferit de 0");
74
+ }
75
+ return toUnsigned16((n - offset) / scale);
76
+ }
77
+
78
+ function normalizeDoCommands(payload, defaultChannel, allowMask) {
79
+ if (payload === null || payload === undefined) return [];
80
+
81
+ if (Array.isArray(payload)) {
82
+ return payload.map(function (value, index) {
83
+ const boolValue = parseBooleanLike(value);
84
+ if (boolValue === null) return null;
85
+ return { channel: index, value: boolValue };
86
+ }).filter(Boolean);
87
+ }
88
+
89
+ if (typeof payload === "object") {
90
+ if (Array.isArray(payload.do)) {
91
+ return normalizeDoCommands(payload.do, defaultChannel, allowMask);
92
+ }
93
+ if (payload.channel !== undefined || payload.value !== undefined) {
94
+ const boolValue = parseBooleanLike(payload.value);
95
+ if (boolValue === null) {
96
+ throw new Error("DO value invalid");
97
+ }
98
+ return [{ channel: toNonNegativeInt(payload.channel, defaultChannel), value: boolValue }];
99
+ }
100
+ if (payload.do_channel !== undefined || payload.do_value !== undefined) {
101
+ const boolValue = parseBooleanLike(payload.do_value);
102
+ if (boolValue === null) {
103
+ throw new Error("DO value invalid");
104
+ }
105
+ return [{ channel: toNonNegativeInt(payload.do_channel, defaultChannel), value: boolValue }];
106
+ }
107
+ if (allowMask && payload.do_mask !== undefined) {
108
+ return [{ mask: toUnsigned16(payload.do_mask) }];
109
+ }
110
+ }
111
+
112
+ const boolValue = parseBooleanLike(payload);
113
+ if (boolValue === null) {
114
+ throw new Error("Payload DO invalid");
115
+ }
116
+ return [{ channel: defaultChannel, value: boolValue }];
117
+ }
118
+
119
+ function normalizeAoCommands(payload, defaultChannel, scale, offset) {
120
+ if (!payload || typeof payload !== "object") return [];
121
+
122
+ if (Array.isArray(payload.ao)) {
123
+ return payload.ao.map(function (value, index) {
124
+ return { channel: index, raw: rawAoFromValue(value, scale, offset), value: Number(value) };
125
+ });
126
+ }
127
+
128
+ if (payload.ao_channel !== undefined || payload.ao_value !== undefined) {
129
+ return [{
130
+ channel: toNonNegativeInt(payload.ao_channel, defaultChannel),
131
+ raw: rawAoFromValue(payload.ao_value, scale, offset),
132
+ value: Number(payload.ao_value)
133
+ }];
134
+ }
135
+
136
+ if (payload.ao_raw !== undefined) {
137
+ return [{
138
+ channel: toNonNegativeInt(payload.ao_channel, defaultChannel),
139
+ raw: toUnsigned16(payload.ao_raw),
140
+ value: null
141
+ }];
142
+ }
143
+
144
+ return [];
145
+ }
146
+
147
+ function AdvantechWriteNode(config) {
148
+ RED.nodes.createNode(this, config);
149
+ const node = this;
150
+
151
+ runLicensedNode(node, RED, config, function startLicensedRuntime() {
152
+
153
+ node.name = String(config.name || "").trim();
154
+ node.host = String(config.host || "").trim();
155
+ node.port = toPositiveInt(config.port, 502);
156
+ node.unitId = toPositiveInt(config.unitId, 1);
157
+ node.addressMode = config.addressMode === "zero" ? "zero" : "manual";
158
+ node.moduleType = String(config.moduleType || "ADAM-6066").trim();
159
+ node.writeType = ["do", "ao", "both"].includes(config.writeType) ? config.writeType : "do";
160
+ node.doStart = toNonNegativeInt(config.doStart, 17);
161
+ node.aoStart = toNonNegativeInt(config.aoStart, 40011);
162
+ node.defaultChannel = toNonNegativeInt(config.defaultChannel, 0);
163
+ node.aoScale = toFinite(config.aoScale, 1);
164
+ node.aoOffset = toFinite(config.aoOffset, 0);
165
+ node.allowDoMask = !!config.allowDoMask;
166
+ node.doMaskRegister = toNonNegativeInt(config.doMaskRegister, 40303);
167
+ node.verifyWrite = config.verifyWrite !== false;
168
+
169
+ let client = new ModbusRTU();
170
+ let queue = Promise.resolve();
171
+
172
+ async function connect() {
173
+ if (!client.isOpen) {
174
+ await client.connectTCP(node.host, { port: node.port });
175
+ }
176
+ client.setID(node.unitId);
177
+ client.setTimeout(2500);
178
+ }
179
+
180
+ async function closeConnection() {
181
+ try {
182
+ if (client && client.isOpen) {
183
+ await client.close();
184
+ }
185
+ } catch (e) {
186
+ // ignore close errors
187
+ }
188
+ client = new ModbusRTU();
189
+ }
190
+
191
+ async function getProfile() {
192
+ const configured = profileForModel(node.moduleType);
193
+ if (configured) {
194
+ configured.detectedBy = "manual";
195
+ configured.modelCode = normalizeModel(node.moduleType);
196
+ return configured;
197
+ }
198
+
199
+ return {
200
+ model: node.moduleType && node.moduleType !== "auto" ? node.moduleType : "Manual",
201
+ modelCode: "",
202
+ diCount: 0,
203
+ doCount: 999,
204
+ aiCount: 0,
205
+ aoCount: 999,
206
+ detectedBy: "manual"
207
+ };
208
+ }
209
+
210
+ async function writeDo(command, profile) {
211
+ const doCount = toNonNegativeInt(profile.doCount, 0);
212
+ if (command.mask === undefined && command.channel >= doCount) {
213
+ throw new Error((profile.model || "ADAM") + " nu are DO channel " + command.channel + " (DO count: " + doCount + ")");
214
+ }
215
+
216
+ if (command.mask !== undefined && doCount <= 0) {
217
+ throw new Error((profile.model || "ADAM") + " nu are iesiri digitale detectate");
218
+ }
219
+
220
+ if (command.mask !== undefined) {
221
+ const addr = resolveAddress(node.doMaskRegister, "holding", node.addressMode);
222
+ await client.writeRegister(addr, command.mask);
223
+ return { type: "do_mask", register: node.doMaskRegister, raw: command.mask };
224
+ }
225
+
226
+ const manualAddress = node.doStart + command.channel;
227
+ const addr = resolveAddress(manualAddress, "coil", node.addressMode);
228
+ await client.writeCoil(addr, command.value);
229
+ if (node.verifyWrite) {
230
+ const readback = await client.readCoils(addr, 1);
231
+ const actual = !!(readback.data && readback.data[0]);
232
+ if (actual !== command.value) {
233
+ throw new Error("DO channel " + command.channel + " nu a fost confirmat");
234
+ }
235
+ }
236
+ return { type: "do", channel: command.channel, address: manualAddress, value: command.value };
237
+ }
238
+
239
+ async function writeAo(command, profile) {
240
+ const aoCount = toNonNegativeInt(profile.aoCount, 0);
241
+ if (command.channel >= aoCount) {
242
+ throw new Error((profile.model || "ADAM") + " nu are AO channel " + command.channel + " (AO count: " + aoCount + ")");
243
+ }
244
+
245
+ const manualAddress = node.aoStart + command.channel;
246
+ const addr = resolveAddress(manualAddress, "holding", node.addressMode);
247
+ await client.writeRegister(addr, command.raw);
248
+ if (node.verifyWrite) {
249
+ const readback = await client.readHoldingRegisters(addr, 1);
250
+ const actual = readback.data && readback.data[0];
251
+ if (actual !== command.raw) {
252
+ throw new Error("AO channel " + command.channel + " nu a fost confirmat");
253
+ }
254
+ }
255
+ return {
256
+ type: "ao",
257
+ channel: command.channel,
258
+ address: manualAddress,
259
+ value: command.value,
260
+ raw: command.raw
261
+ };
262
+ }
263
+
264
+ async function handleInput(msg) {
265
+ const payload = msg ? msg.payload : undefined;
266
+ const operations = [];
267
+
268
+ if (node.writeType === "do" || node.writeType === "both") {
269
+ normalizeDoCommands(payload, node.defaultChannel, node.allowDoMask).forEach(function (command) {
270
+ operations.push({ kind: "do", command: command });
271
+ });
272
+ }
273
+
274
+ if (node.writeType === "ao" || node.writeType === "both") {
275
+ normalizeAoCommands(payload, node.defaultChannel, node.aoScale, node.aoOffset).forEach(function (command) {
276
+ operations.push({ kind: "ao", command: command });
277
+ });
278
+ }
279
+
280
+ if (!operations.length) {
281
+ throw new Error("Nu exista comenzi DO/AO valide in payload");
282
+ }
283
+
284
+ await connect();
285
+ const profile = await getProfile();
286
+
287
+ const written = [];
288
+ for (let i = 0; i < operations.length; i += 1) {
289
+ const op = operations[i];
290
+ written.push(op.kind === "do" ? await writeDo(op.command, profile) : await writeAo(op.command, profile));
291
+ }
292
+
293
+ node.status({ fill: "green", shape: "dot", text: (profile.model || "ADAM") + " write ok" });
294
+ node.send({
295
+ topic: "advantech/write",
296
+ payload: {
297
+ status: "ok",
298
+ host: node.host,
299
+ port: node.port,
300
+ unit_id: node.unitId,
301
+ module_model: profile.model || null,
302
+ module_model_code: profile.modelCode || null,
303
+ detected_by: profile.detectedBy || null,
304
+ channel_counts: {
305
+ di: toNonNegativeInt(profile.diCount, 0),
306
+ do: toNonNegativeInt(profile.doCount, 0),
307
+ ai: toNonNegativeInt(profile.aiCount, 0),
308
+ ao: toNonNegativeInt(profile.aoCount, 0)
309
+ },
310
+ written: written,
311
+ ts: new Date().toISOString()
312
+ }
313
+ });
314
+ }
315
+
316
+ node.on("input", function (msg) {
317
+ queue = queue.then(function () {
318
+ return handleInput(msg);
319
+ }).catch(async function (error) {
320
+ node.status({ fill: "red", shape: "ring", text: "error" });
321
+ node.error(error.message || error, msg || {});
322
+ await closeConnection();
323
+ });
324
+ });
325
+
326
+ node.on("close", function (removed, done) {
327
+ closeConnection().then(function () {
328
+ done();
329
+ }).catch(function () {
330
+ done();
331
+ });
332
+ });
333
+
334
+ });
335
+ }
336
+
337
+ RED.nodes.registerType("advantech-write", AdvantechWriteNode);
338
+ };