@vitormnm/node-red-simple-opcua 1.4.1 → 1.4.3

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 (32) hide show
  1. package/README.md +5 -0
  2. package/client/icons/opcua.svg +132 -132
  3. package/client/lib/opcua-client-browser.js +330 -330
  4. package/client/lib/opcua-client-method-service.js +88 -88
  5. package/client/lib/opcua-client-read-service.js +15 -15
  6. package/client/lib/opcua-client-subscription-id-service.js +24 -24
  7. package/client/lib/opcua-client-subscription-service.js +170 -170
  8. package/client/lib/opcua-client-write-service.js +146 -146
  9. package/client/opcua-client-config.html +80 -80
  10. package/client/opcua-client.html +140 -140
  11. package/client/opcua-client.js +1 -7
  12. package/client/view/opcua-client.js +1140 -1140
  13. package/icons/opcua.svg +132 -132
  14. package/icons/opcua2.svg +132 -132
  15. package/package.json +42 -42
  16. package/resources/bmc-button.svg +22 -0
  17. package/server/icons/opcua.svg +132 -132
  18. package/server/lib/opcua-address-space-alarm.js +341 -341
  19. package/server/lib/opcua-address-space-builder.js +1484 -1484
  20. package/server/lib/opcua-config.js +546 -546
  21. package/server/lib/opcua-constants.js +109 -109
  22. package/server/lib/opcua-server-events-child.js +139 -139
  23. package/server/lib/opcua-server-runtime-child.js +819 -819
  24. package/server/lib/opcua-server-runtime.js +311 -311
  25. package/server/lib/opcua-server-status-child.js +187 -187
  26. package/server/lib/server-node-utils.js +16 -16
  27. package/server/opcua-server-io.html +346 -346
  28. package/server/opcua-server-io.js +496 -496
  29. package/server/opcua-server-registry.js +270 -270
  30. package/server/opcua-server.css +265 -265
  31. package/server/opcua-server.html +1643 -1643
  32. package/client/testClient.json +0 -18
@@ -1,147 +1,147 @@
1
- "use strict";
2
-
3
- const { AttributeIds, DataType, coerceNodeId } = require("node-opcua");
4
- const {
5
- buildVariantFromItem,
6
- normalizeTypeName,
7
- resolveNodeId,
8
- statusCodeToString
9
- } = require("../opcua-client-utils");
10
-
11
- // Máximo de tags por chamada session.write (ajuste conforme limite do servidor)
12
- const WRITE_BATCH_SIZE = 100;
13
-
14
- // Batches em paralelo simultâneos
15
- const CONCURRENCY = 5;
16
-
17
- // Cede o event loop a cada N itens para não travar o Node-RED
18
- const YIELD_EVERY = 50;
19
-
20
- class OpcUaClientWriteService {
21
- async execute(node, msg, session, itemsResolver) {
22
- const items = itemsResolver.ensureWriteItems(node, msg);
23
-
24
- // 1. Resolve variantes (tipo + valor) — consulta servidor só para quem não tem tipo explícito
25
- const variants = await resolveVariants(session, items);
26
-
27
- // 2. Escreve todos os nós em batches paralelos
28
- const statusCodes = await writeBatches(session, items, variants);
29
-
30
- // 3. Monta resultados — statusCode Good já confirma a escrita, sem round-trip extra
31
- return buildResults(items, variants, statusCodes);
32
- }
33
- }
34
-
35
- // ─── Resolução de tipos ──────────────────────────────────────────────────────
36
-
37
- async function resolveVariants(session, items) {
38
- // Separa quais itens precisam consultar o tipo no servidor
39
- const needsLookup = items
40
- .map((item, index) => ({ item, index }))
41
- .filter(({ item }) => !normalizeTypeName(item.type));
42
-
43
- // Busca tipos desconhecidos em paralelo
44
- const resolvedTypes = new Map();
45
-
46
- await mapConcurrent(needsLookup, CONCURRENCY * 2, async ({ item, index }) => {
47
- try {
48
- const builtInType = await session.getBuiltInDataType(coerceNodeId(resolveNodeId(item)));
49
- resolvedTypes.set(index, DataType[builtInType]);
50
- } catch {
51
- resolvedTypes.set(index, "String");
52
- }
53
- });
54
-
55
- return items.map((item, index) => {
56
- const typeName = normalizeTypeName(item.type) || resolvedTypes.get(index) || "String";
57
- return buildVariantFromItem(item, typeName);
58
- });
59
- }
60
-
61
- // ─── Escrita em batches paralelos ────────────────────────────────────────────
62
-
63
- async function writeBatches(session, items, variants) {
64
- const allStatusCodes = new Array(items.length);
65
-
66
- // Divide em batches de WRITE_BATCH_SIZE
67
- const batches = [];
68
- for (let i = 0; i < items.length; i += WRITE_BATCH_SIZE) {
69
- batches.push({ start: i, end: Math.min(i + WRITE_BATCH_SIZE, items.length) });
70
- }
71
-
72
- let processed = 0;
73
-
74
- await mapConcurrent(batches, CONCURRENCY, async ({ start, end }) => {
75
- const nodesToWrite = items.slice(start, end).map((item, i) => ({
76
- nodeId: coerceNodeId(resolveNodeId(item)),
77
- attributeId: AttributeIds.Value,
78
- value: { value: variants[start + i] }
79
- }));
80
-
81
- try {
82
- const statusCodes = await session.write(nodesToWrite);
83
- statusCodes.forEach((sc, i) => {
84
- allStatusCodes[start + i] = sc;
85
- });
86
- } catch (batchError) {
87
- // Se o batch falhar por completo, marca todos com erro
88
- for (let i = start; i < end; i++) {
89
- allStatusCodes[i] = { name: batchError.message, value: -1 };
90
- }
91
- }
92
-
93
- // Cede o event loop a cada YIELD_EVERY itens para não travar o Node-RED
94
- processed += end - start;
95
- if (processed % YIELD_EVERY === 0) {
96
- await yieldEventLoop();
97
- }
98
- });
99
-
100
- return allStatusCodes;
101
- }
102
-
103
- // ─── Montagem dos resultados ─────────────────────────────────────────────────
104
-
105
- function buildResults(items, variants, statusCodes) {
106
- return items.map((item, index) => {
107
- const nodeId = resolveNodeId(item);
108
- const sc = statusCodes[index];
109
- const scName = sc && sc.name ? sc.name : "Good";
110
- const typeName = DataType[variants[index].dataType] || null;
111
-
112
- return {
113
- name: item.name || nodeId,
114
- nodeID: nodeId,
115
- value: variants[index].value,
116
- type: typeName,
117
- status: scName,
118
- sourceTimestamp: null,
119
- serverTimestamp: null
120
- };
121
- });
122
- }
123
-
124
- // ─── Utilitários ─────────────────────────────────────────────────────────────
125
-
126
- async function mapConcurrent(items, concurrency, fn) {
127
- let index = 0;
128
-
129
- async function worker() {
130
- while (index < items.length) {
131
- const i = index++;
132
- await fn(items[i], i);
133
- }
134
- }
135
-
136
- await Promise.all(
137
- Array.from({ length: Math.min(concurrency, items.length) }, worker)
138
- );
139
- }
140
-
141
- function yieldEventLoop() {
142
- return new Promise(resolve => setImmediate(resolve));
143
- }
144
-
145
- module.exports = {
146
- OpcUaClientWriteService
1
+ "use strict";
2
+
3
+ const { AttributeIds, DataType, coerceNodeId } = require("node-opcua");
4
+ const {
5
+ buildVariantFromItem,
6
+ normalizeTypeName,
7
+ resolveNodeId,
8
+ statusCodeToString
9
+ } = require("../opcua-client-utils");
10
+
11
+ // Máximo de tags por chamada session.write (ajuste conforme limite do servidor)
12
+ const WRITE_BATCH_SIZE = 100;
13
+
14
+ // Batches em paralelo simultâneos
15
+ const CONCURRENCY = 5;
16
+
17
+ // Cede o event loop a cada N itens para não travar o Node-RED
18
+ const YIELD_EVERY = 50;
19
+
20
+ class OpcUaClientWriteService {
21
+ async execute(node, msg, session, itemsResolver) {
22
+ const items = itemsResolver.ensureWriteItems(node, msg);
23
+
24
+ // 1. Resolve variantes (tipo + valor) — consulta servidor só para quem não tem tipo explícito
25
+ const variants = await resolveVariants(session, items);
26
+
27
+ // 2. Escreve todos os nós em batches paralelos
28
+ const statusCodes = await writeBatches(session, items, variants);
29
+
30
+ // 3. Monta resultados — statusCode Good já confirma a escrita, sem round-trip extra
31
+ return buildResults(items, variants, statusCodes);
32
+ }
33
+ }
34
+
35
+ // ─── Resolução de tipos ──────────────────────────────────────────────────────
36
+
37
+ async function resolveVariants(session, items) {
38
+ // Separa quais itens precisam consultar o tipo no servidor
39
+ const needsLookup = items
40
+ .map((item, index) => ({ item, index }))
41
+ .filter(({ item }) => !normalizeTypeName(item.type));
42
+
43
+ // Busca tipos desconhecidos em paralelo
44
+ const resolvedTypes = new Map();
45
+
46
+ await mapConcurrent(needsLookup, CONCURRENCY * 2, async ({ item, index }) => {
47
+ try {
48
+ const builtInType = await session.getBuiltInDataType(coerceNodeId(resolveNodeId(item)));
49
+ resolvedTypes.set(index, DataType[builtInType]);
50
+ } catch {
51
+ resolvedTypes.set(index, "String");
52
+ }
53
+ });
54
+
55
+ return items.map((item, index) => {
56
+ const typeName = normalizeTypeName(item.type) || resolvedTypes.get(index) || "String";
57
+ return buildVariantFromItem(item, typeName);
58
+ });
59
+ }
60
+
61
+ // ─── Escrita em batches paralelos ────────────────────────────────────────────
62
+
63
+ async function writeBatches(session, items, variants) {
64
+ const allStatusCodes = new Array(items.length);
65
+
66
+ // Divide em batches de WRITE_BATCH_SIZE
67
+ const batches = [];
68
+ for (let i = 0; i < items.length; i += WRITE_BATCH_SIZE) {
69
+ batches.push({ start: i, end: Math.min(i + WRITE_BATCH_SIZE, items.length) });
70
+ }
71
+
72
+ let processed = 0;
73
+
74
+ await mapConcurrent(batches, CONCURRENCY, async ({ start, end }) => {
75
+ const nodesToWrite = items.slice(start, end).map((item, i) => ({
76
+ nodeId: coerceNodeId(resolveNodeId(item)),
77
+ attributeId: AttributeIds.Value,
78
+ value: { value: variants[start + i] }
79
+ }));
80
+
81
+ try {
82
+ const statusCodes = await session.write(nodesToWrite);
83
+ statusCodes.forEach((sc, i) => {
84
+ allStatusCodes[start + i] = sc;
85
+ });
86
+ } catch (batchError) {
87
+ // Se o batch falhar por completo, marca todos com erro
88
+ for (let i = start; i < end; i++) {
89
+ allStatusCodes[i] = { name: batchError.message, value: -1 };
90
+ }
91
+ }
92
+
93
+ // Cede o event loop a cada YIELD_EVERY itens para não travar o Node-RED
94
+ processed += end - start;
95
+ if (processed % YIELD_EVERY === 0) {
96
+ await yieldEventLoop();
97
+ }
98
+ });
99
+
100
+ return allStatusCodes;
101
+ }
102
+
103
+ // ─── Montagem dos resultados ─────────────────────────────────────────────────
104
+
105
+ function buildResults(items, variants, statusCodes) {
106
+ return items.map((item, index) => {
107
+ const nodeId = resolveNodeId(item);
108
+ const sc = statusCodes[index];
109
+ const scName = sc && sc.name ? sc.name : "Good";
110
+ const typeName = DataType[variants[index].dataType] || null;
111
+
112
+ return {
113
+ name: item.name || nodeId,
114
+ nodeID: nodeId,
115
+ value: variants[index].value,
116
+ type: typeName,
117
+ status: scName,
118
+ sourceTimestamp: null,
119
+ serverTimestamp: null
120
+ };
121
+ });
122
+ }
123
+
124
+ // ─── Utilitários ─────────────────────────────────────────────────────────────
125
+
126
+ async function mapConcurrent(items, concurrency, fn) {
127
+ let index = 0;
128
+
129
+ async function worker() {
130
+ while (index < items.length) {
131
+ const i = index++;
132
+ await fn(items[i], i);
133
+ }
134
+ }
135
+
136
+ await Promise.all(
137
+ Array.from({ length: Math.min(concurrency, items.length) }, worker)
138
+ );
139
+ }
140
+
141
+ function yieldEventLoop() {
142
+ return new Promise(resolve => setImmediate(resolve));
143
+ }
144
+
145
+ module.exports = {
146
+ OpcUaClientWriteService
147
147
  };
@@ -1,80 +1,80 @@
1
- <script type="text/javascript">
2
- (function () {
3
- function toggleCredentials() {
4
- var authType = $("#node-config-input-authType").val();
5
- $(".opcua-client-auth-row").toggle(authType === "username");
6
- }
7
-
8
- RED.nodes.registerType("opcua-client-config", {
9
- category: "config",
10
- defaults: {
11
- name: { value: "" },
12
- endpoint: { value: "opc.tcp://localhost:4840", required: true },
13
- securityPolicy: { value: "None", required: true },
14
- securityMode: { value: "None", required: true },
15
- authType: { value: "anonymous", required: true }
16
- },
17
- credentials: {
18
- username: { type: "text" },
19
- password: { type: "password" }
20
- },
21
- label: function () {
22
- return this.name || this.endpoint || "opcua-client-config";
23
- },
24
- oneditprepare: function () {
25
- $("#node-config-input-authType").on("change", toggleCredentials);
26
- toggleCredentials();
27
- }
28
- });
29
- })();
30
- </script>
31
-
32
- <script type="text/html" data-template-name="opcua-client-config">
33
- <div class="form-row">
34
- <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
35
- <input type="text" id="node-config-input-name" placeholder="OPC UA Client">
36
- </div>
37
- <div class="form-row">
38
- <label for="node-config-input-endpoint"><i class="fa fa-plug"></i> Endpoint</label>
39
- <input type="text" id="node-config-input-endpoint" placeholder="opc.tcp://localhost:4840">
40
- </div>
41
- <div class="form-row">
42
- <label for="node-config-input-securityPolicy"><i class="fa fa-lock"></i> Security Policy</label>
43
- <select id="node-config-input-securityPolicy">
44
- <option value="None">None</option>
45
- <option value="Basic128Rsa15">Basic128Rsa15</option>
46
- <option value="Basic256">Basic256</option>
47
- <option value="Basic256Sha256">Basic256Sha256</option>
48
- <option value="Aes128_Sha256_RsaOaep">Aes128_Sha256_RsaOaep</option>
49
- <option value="Aes256_Sha256_RsaPss">Aes256_Sha256_RsaPss</option>
50
- </select>
51
- </div>
52
- <div class="form-row">
53
- <label for="node-config-input-securityMode"><i class="fa fa-shield"></i> Security Mode</label>
54
- <select id="node-config-input-securityMode">
55
- <option value="None">None</option>
56
- <option value="Sign">Sign</option>
57
- <option value="SignAndEncrypt">SignAndEncrypt</option>
58
- </select>
59
- </div>
60
- <div class="form-row">
61
- <label for="node-config-input-authType"><i class="fa fa-user"></i> Auth</label>
62
- <select id="node-config-input-authType">
63
- <option value="anonymous">Anonymous</option>
64
- <option value="username">Username/Password</option>
65
- </select>
66
- </div>
67
- <div class="form-row opcua-client-auth-row">
68
- <label for="node-config-input-username"><i class="fa fa-user-circle"></i> Username</label>
69
- <input type="text" id="node-config-input-username">
70
- </div>
71
- <div class="form-row opcua-client-auth-row">
72
- <label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
73
- <input type="password" id="node-config-input-password">
74
- </div>
75
- </script>
76
-
77
- <script type="text/html" data-help-name="opcua-client-config">
78
- <p>Shared OPC UA client connection for read, write and method nodes.</p>
79
- <p>This is a Node-RED configuration node, using <code>category: "config"</code> and the <code>node-config-input-*</code> field pattern described in the official docs.</p>
80
- </script>
1
+ <script type="text/javascript">
2
+ (function () {
3
+ function toggleCredentials() {
4
+ var authType = $("#node-config-input-authType").val();
5
+ $(".opcua-client-auth-row").toggle(authType === "username");
6
+ }
7
+
8
+ RED.nodes.registerType("opcua-client-config", {
9
+ category: "config",
10
+ defaults: {
11
+ name: { value: "" },
12
+ endpoint: { value: "opc.tcp://localhost:4840", required: true },
13
+ securityPolicy: { value: "None", required: true },
14
+ securityMode: { value: "None", required: true },
15
+ authType: { value: "anonymous", required: true }
16
+ },
17
+ credentials: {
18
+ username: { type: "text" },
19
+ password: { type: "password" }
20
+ },
21
+ label: function () {
22
+ return this.name || this.endpoint || "opcua-client-config";
23
+ },
24
+ oneditprepare: function () {
25
+ $("#node-config-input-authType").on("change", toggleCredentials);
26
+ toggleCredentials();
27
+ }
28
+ });
29
+ })();
30
+ </script>
31
+
32
+ <script type="text/html" data-template-name="opcua-client-config">
33
+ <div class="form-row">
34
+ <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
35
+ <input type="text" id="node-config-input-name" placeholder="OPC UA Client">
36
+ </div>
37
+ <div class="form-row">
38
+ <label for="node-config-input-endpoint"><i class="fa fa-plug"></i> Endpoint</label>
39
+ <input type="text" id="node-config-input-endpoint" placeholder="opc.tcp://localhost:4840">
40
+ </div>
41
+ <div class="form-row">
42
+ <label for="node-config-input-securityPolicy"><i class="fa fa-lock"></i> Security Policy</label>
43
+ <select id="node-config-input-securityPolicy">
44
+ <option value="None">None</option>
45
+ <option value="Basic128Rsa15">Basic128Rsa15</option>
46
+ <option value="Basic256">Basic256</option>
47
+ <option value="Basic256Sha256">Basic256Sha256</option>
48
+ <option value="Aes128_Sha256_RsaOaep">Aes128_Sha256_RsaOaep</option>
49
+ <option value="Aes256_Sha256_RsaPss">Aes256_Sha256_RsaPss</option>
50
+ </select>
51
+ </div>
52
+ <div class="form-row">
53
+ <label for="node-config-input-securityMode"><i class="fa fa-shield"></i> Security Mode</label>
54
+ <select id="node-config-input-securityMode">
55
+ <option value="None">None</option>
56
+ <option value="Sign">Sign</option>
57
+ <option value="SignAndEncrypt">SignAndEncrypt</option>
58
+ </select>
59
+ </div>
60
+ <div class="form-row">
61
+ <label for="node-config-input-authType"><i class="fa fa-user"></i> Auth</label>
62
+ <select id="node-config-input-authType">
63
+ <option value="anonymous">Anonymous</option>
64
+ <option value="username">Username/Password</option>
65
+ </select>
66
+ </div>
67
+ <div class="form-row opcua-client-auth-row">
68
+ <label for="node-config-input-username"><i class="fa fa-user-circle"></i> Username</label>
69
+ <input type="text" id="node-config-input-username">
70
+ </div>
71
+ <div class="form-row opcua-client-auth-row">
72
+ <label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
73
+ <input type="password" id="node-config-input-password">
74
+ </div>
75
+ </script>
76
+
77
+ <script type="text/html" data-help-name="opcua-client-config">
78
+ <p>Shared OPC UA client connection for read, write and method nodes.</p>
79
+ <p>This is a Node-RED configuration node, using <code>category: "config"</code> and the <code>node-config-input-*</code> field pattern described in the official docs.</p>
80
+ </script>