@tier0/opcua-client 0.1.0 → 0.4.0

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 (49) hide show
  1. package/BENCHMARK.md +53 -2
  2. package/README.md +180 -14
  3. package/THIRD_PARTY_NOTICES.md +20 -0
  4. package/binding.gyp +1 -1
  5. package/lib/client.js +48 -7
  6. package/lib/connection-registry.js +68 -0
  7. package/lib/native.js +25 -10
  8. package/lib/points.js +187 -21
  9. package/lib/prebuild.js +42 -0
  10. package/lib/security-file-store.js +119 -0
  11. package/lib/security.js +96 -0
  12. package/node_modules/node-addon-api/LICENSE.md +9 -0
  13. package/node_modules/node-addon-api/README.md +95 -0
  14. package/node_modules/node-addon-api/common.gypi +21 -0
  15. package/node_modules/node-addon-api/except.gypi +25 -0
  16. package/node_modules/node-addon-api/index.js +14 -0
  17. package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  18. package/node_modules/node-addon-api/napi-inl.h +7244 -0
  19. package/node_modules/node-addon-api/napi.h +3420 -0
  20. package/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  21. package/node_modules/node-addon-api/node_api.gyp +9 -0
  22. package/node_modules/node-addon-api/noexcept.gypi +26 -0
  23. package/node_modules/node-addon-api/nothing.c +0 -0
  24. package/node_modules/node-addon-api/package-support.json +21 -0
  25. package/node_modules/node-addon-api/package.json +481 -0
  26. package/node_modules/node-addon-api/tools/README.md +73 -0
  27. package/node_modules/node-addon-api/tools/check-napi.js +99 -0
  28. package/node_modules/node-addon-api/tools/clang-format.js +71 -0
  29. package/node_modules/node-addon-api/tools/conversion.js +301 -0
  30. package/nodes/browse.html +27 -2
  31. package/nodes/connection.html +293 -6
  32. package/nodes/connection.js +160 -23
  33. package/nodes/read.html +42 -3
  34. package/nodes/read.js +51 -12
  35. package/nodes/subscribe.html +57 -5
  36. package/nodes/subscribe.js +386 -99
  37. package/nodes/write.html +29 -2
  38. package/nodes/write.js +2 -1
  39. package/package.json +15 -4
  40. package/prebuilds/linux-x64-musl/opcua_open62541.node +0 -0
  41. package/scripts/build-offline-prebuild.sh +15 -0
  42. package/scripts/build-open62541.sh +14 -4
  43. package/scripts/build-test-server.sh +23 -0
  44. package/scripts/install.js +28 -0
  45. package/scripts/verify-offline-package.js +20 -0
  46. package/scripts/verify-runtime.js +20 -0
  47. package/src/addon.cc +707 -119
  48. package/vendor/open62541-LICENSE +373 -0
  49. package/vendor/open62541-v1.4.16.tar.gz +0 -0
package/nodes/read.js CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  const { looksLikePointList, messagePoints } = require('../lib/points');
4
4
 
5
+ const COMPACT_RESULT_FIELDS = Object.freeze([
6
+ 'nodeId',
7
+ 'alias',
8
+ 'value',
9
+ 'statusCode',
10
+ 'sourceTimestamp',
11
+ ]);
12
+
13
+ function compactResult(result) {
14
+ const output = {};
15
+ for (const field of COMPACT_RESULT_FIELDS) {
16
+ if (Object.prototype.hasOwnProperty.call(result, field)) output[field] = result[field];
17
+ }
18
+ return output;
19
+ }
20
+
21
+ function valueOnly(result) {
22
+ return Object.prototype.hasOwnProperty.call(result, 'value') ? result.value : null;
23
+ }
24
+
5
25
  module.exports = function registerReadNode(RED) {
6
26
  function OpcUaReadNode(config) {
7
27
  RED.nodes.createNode(this, config);
@@ -12,7 +32,9 @@ module.exports = function registerReadNode(RED) {
12
32
  if (!node.connection) return done(new Error('OPC UA connection not configured'));
13
33
 
14
34
  try {
15
- const points = messagePoints(msg, config.nodeId, {}, {
35
+ const points = messagePoints(msg, config.nodeId, {
36
+ indexRange: config.indexRange,
37
+ }, {
16
38
  configuredPayload: config.pointList,
17
39
  format: 'auto',
18
40
  });
@@ -27,29 +49,46 @@ module.exports = function registerReadNode(RED) {
27
49
  ...value,
28
50
  }));
29
51
 
52
+ // Undefined preserves the original output for Flow JSON created before
53
+ // resultDetail was introduced. New editor nodes explicitly save compact.
54
+ const resultDetail = config.resultDetail || 'full';
55
+ const outputResults = resultDetail === 'compact'
56
+ ? results.map(compactResult)
57
+ : resultDetail === 'value'
58
+ ? results.map(valueOnly)
59
+ : results;
60
+
30
61
  if ((config.outputMode || 'batch') === 'split') {
31
- results.forEach((result) => send({
32
- ...msg,
33
- topic: result.nodeId,
34
- payload: result.value,
35
- opcua: result,
36
- }));
62
+ results.forEach((result, index) => {
63
+ const output = {
64
+ ...msg,
65
+ topic: result.nodeId,
66
+ payload: valueOnly(result),
67
+ };
68
+ if (resultDetail === 'value') delete output.opcua;
69
+ else output.opcua = outputResults[index];
70
+ send(output);
71
+ });
37
72
  } else if (points.length === 1 && !looksLikePointList(msg.payload) &&
38
73
  !Array.isArray(msg.items) && !config.pointList) {
39
74
  const result = results[0];
40
75
  msg.topic = result.nodeId;
41
- msg.payload = result.value;
42
- msg.opcua = result;
76
+ msg.payload = valueOnly(result);
77
+ if (resultDetail === 'value') delete msg.opcua;
78
+ else msg.opcua = outputResults[0];
43
79
  send(msg);
44
80
  } else {
45
- msg.payload = results;
81
+ msg.payload = outputResults;
46
82
  msg.opcua = {
47
- ...(msg.opcua || {}),
48
83
  operation: 'read',
49
84
  total: results.length,
50
85
  good: results.filter((item) => item.statusCode === 'Good').length,
51
- clientDiagnostics: node.connection.client.getDiagnostics(),
86
+ resultDetail,
52
87
  };
88
+ if (resultDetail === 'full') {
89
+ msg.opcua.clientDiagnostics = node.connection.getDiagnostics?.() ||
90
+ node.connection.client.getDiagnostics();
91
+ }
53
92
  send(msg);
54
93
  }
55
94
 
@@ -7,7 +7,7 @@
7
7
  <div class="form-row"><label for="node-input-connection">Connection</label><input type="text" id="node-input-connection"></div>
8
8
  <div class="form-row"><label for="node-input-nodeId">NodeId</label><input type="text" id="node-input-nodeId" placeholder="Optional when input supplies points"></div>
9
9
  <div class="form-row"><label for="node-input-samplingInterval">Sampling (ms)</label><input type="number" id="node-input-samplingInterval"></div>
10
- <div class="opcua-standard-note">Standard mode batches changes and automatically rebuilds the subscription after reconnect.</div>
10
+ <div class="opcua-standard-note">Standard mode keeps the latest pending value per point, shards large point sets, batches changes, and automatically restores them after reconnect.</div>
11
11
  <details class="opcua-advanced">
12
12
  <summary>Advanced subscription and queue settings</summary>
13
13
  <div class="form-row"><label for="node-input-publishingInterval">Publishing interval</label><input type="number" id="node-input-publishingInterval"></div>
@@ -15,27 +15,79 @@
15
15
  <div class="form-row"><label for="node-input-discardOldest">Discard oldest</label><input type="checkbox" id="node-input-discardOldest" style="width:auto"></div>
16
16
  <div class="form-row"><label for="node-input-deadbandType">Deadband</label><select id="node-input-deadbandType"><option value="none">None</option><option value="absolute">Absolute</option><option value="percent">Percent</option></select></div>
17
17
  <div class="form-row"><label for="node-input-deadbandValue">Deadband value</label><input type="number" id="node-input-deadbandValue"></div>
18
+ <div class="form-row"><label for="node-input-timestampMode">Timestamps</label><select id="node-input-timestampMode"><option value="source">Source</option><option value="both">Source and server</option><option value="server">Server</option><option value="neither">None</option></select></div>
18
19
  <div class="form-row"><label for="node-input-batchSize">Create batch size</label><input type="number" id="node-input-batchSize"></div>
20
+ <div class="form-row"><label for="node-input-maxItemsPerSubscription">Items / subscription</label><input type="number" id="node-input-maxItemsPerSubscription"></div>
19
21
  <div class="form-row"><label for="node-input-outputMode">Change output</label><select id="node-input-outputMode"><option value="batch">Native batch</option><option value="single">One message per change</option></select></div>
20
- <div class="form-row"><label for="node-input-callbackBatchSize">Native batch size</label><input type="number" id="node-input-callbackBatchSize"></div>
22
+ <div class="form-row"><label for="node-input-resultDetail">Result detail</label><select id="node-input-resultDetail"><option value="compact">Compact</option><option value="value">Value only</option><option value="full">Full metadata</option></select></div>
23
+ <div class="form-row"><label for="node-input-callbackBatchSize">Output batch size</label><input type="number" id="node-input-callbackBatchSize"></div>
24
+ <div class="form-row"><label for="node-input-callbackFlushIntervalMs">Batch flush (ms)</label><input type="number" id="node-input-callbackFlushIntervalMs"></div>
21
25
  <div class="form-row"><label for="node-input-callbackQueueSize">Native queue capacity</label><input type="number" id="node-input-callbackQueueSize"></div>
22
26
  <div class="form-row"><label for="node-input-queueMode">Queue mode</label><select id="node-input-queueMode"><option value="all">All changes</option><option value="latest">Latest value per point</option></select></div>
23
27
  <div class="form-row"><label for="node-input-overflowPolicy">Overflow policy</label><select id="node-input-overflowPolicy"><option value="drop-oldest">Drop oldest</option><option value="drop-newest">Drop newest</option></select></div>
24
28
  </details>
25
29
  <div class="form-row"><label for="node-input-name">Name</label><input type="text" id="node-input-name"></div>
26
30
  </script>
27
- <script type="text/html" data-help-name="tier0-opcua-subscribe"><p>Creates one OPC UA subscription with many monitored items. Input may be an array, JSON, CSV, or Browse output. Commands are <code>replace</code>, <code>add</code>, <code>remove</code>, and <code>clear</code> in <code>msg.opcua.command</code>.</p><p>Standard mode preserves all changes in bounded native batches and reports queue diagnostics in <code>msg.opcua.queue</code>.</p></script>
31
+ <script type="text/html" data-help-name="tier0-opcua-subscribe">
32
+ <section lang="zh-CN">
33
+ <h3>订阅(Subscribe)</h3>
34
+ <p>创建 OPC UA Subscription 和 MonitoredItems,由服务器在值变化时推送通知;它不是客户端定时 Read。大量点位会按照服务器限制自动拆分到多个 Subscription。</p>
35
+ <h4>输入与命令</h4>
36
+ <p>点表可来自数组、JSON、CSV 或 Browse 输出。通过 <code>msg.opcua.command</code> 使用 <code>replace</code>、<code>add</code>、<code>remove</code>、<code>clear</code> 动态管理点位。</p>
37
+ <h4>时间与队列</h4>
38
+ <ul>
39
+ <li><b>Sampling</b>:服务器检查 MonitoredItem 的请求间隔。</li>
40
+ <li><b>Publishing interval</b>:服务器发布已排队通知的请求周期。</li>
41
+ <li><b>Batch flush</b>:Node-RED 合并通知的最大等待时间,不影响服务器采样。</li>
42
+ <li>服务器可以把请求间隔修订为它支持的值。</li>
43
+ </ul>
44
+ <p>新节点默认 Server queue size=1、Discard oldest=true、Queue mode=latest,适合实时状态采集。脉冲、报警转换或完整历史不能丢失时,应改为 all 并增大服务器队列。</p>
45
+ <h4>输出</h4>
46
+ <ul>
47
+ <li><b>Compact</b>:NodeId/alias、值、状态和源时间戳。</li>
48
+ <li><b>Value only</b>:值在 <code>msg.payload</code>,同位置标识在 <code>msg.opcua.identifiers</code>。</li>
49
+ <li><b>Full metadata</b>:保留完整诊断字段。</li>
50
+ </ul>
51
+ <p>队列深度、丢弃数和延迟位于 <code>msg.opcua.queue</code>;发生溢出时节点会显示警告。</p>
52
+ </section>
53
+ <hr>
54
+ <section lang="en">
55
+ <h3>Subscribe</h3>
56
+ <p>Creates one or more OPC UA subscriptions and MonitoredItems so the server sends data-change notifications. It is not a periodic client Read. Large point sets are automatically sharded across Subscriptions according to server limits.</p>
57
+ <h4>Input and commands</h4>
58
+ <p>Point lists may come from arrays, JSON, CSV, or Browse output. Use <code>replace</code>, <code>add</code>, <code>remove</code>, or <code>clear</code> in <code>msg.opcua.command</code> to manage points dynamically.</p>
59
+ <h4>Timing and queues</h4>
60
+ <ul>
61
+ <li><b>Sampling</b>: Requested interval at which the server checks a MonitoredItem.</li>
62
+ <li><b>Publishing interval</b>: Requested cycle at which the server publishes queued notifications.</li>
63
+ <li><b>Batch flush</b>: Maximum Node-RED aggregation delay; it does not control server sampling.</li>
64
+ <li>The server may revise requested intervals to supported values.</li>
65
+ </ul>
66
+ <p>New nodes default to server queue size 1, discard-oldest, and local latest coalescing for real-time state. Select all and increase the server queue for pulses, alarm transitions, or history where every change must be retained.</p>
67
+ <h4>Output</h4>
68
+ <ul>
69
+ <li><b>Compact</b>: NodeId/alias, value, status, and source timestamp.</li>
70
+ <li><b>Value only</b>: Values are in <code>msg.payload</code>; matching identities are in <code>msg.opcua.identifiers</code>.</li>
71
+ <li><b>Full metadata</b>: Preserves full diagnostic fields.</li>
72
+ </ul>
73
+ <p>Queue depth, drops, and lag are reported in <code>msg.opcua.queue</code>. Queue overflow also raises a node warning.</p>
74
+ </section>
75
+ </script>
28
76
  <script type="text/javascript">
29
77
  RED.nodes.registerType('tier0-opcua-subscribe', {
30
78
  category: 'OPC UA', color: '#c7e9f1', inputs: 1, outputs: 1, icon: 'bridge.png',
31
79
  defaults: {
32
80
  connection: { value: '', type: 'tier0-opcua-connection', required: true }, nodeId: { value: '' },
33
81
  publishingInterval: { value: 1000, validate: RED.validators.number() }, samplingInterval: { value: 1000, validate: RED.validators.number() },
34
- queueSize: { value: 10, validate: RED.validators.number() }, discardOldest: { value: true },
82
+ queueSize: { value: 1, validate: RED.validators.number() }, discardOldest: { value: true },
35
83
  deadbandType: { value: 'none' }, deadbandValue: { value: 0, validate: RED.validators.number() },
84
+ timestampMode: { value: 'source' },
36
85
  batchSize: { value: 500, validate: RED.validators.number() }, outputMode: { value: 'batch' },
86
+ resultDetail: { value: 'compact' },
87
+ maxItemsPerSubscription: { value: 5000, validate: RED.validators.number() },
37
88
  callbackBatchSize: { value: 500, validate: RED.validators.number() }, callbackQueueSize: { value: 10000, validate: RED.validators.number() },
38
- queueMode: { value: 'all' }, overflowPolicy: { value: 'drop-oldest' },
89
+ callbackFlushIntervalMs: { value: 50, validate: RED.validators.number() },
90
+ queueMode: { value: 'latest' }, overflowPolicy: { value: 'drop-oldest' },
39
91
  lifetimeCount: { value: 120 }, maxKeepAliveCount: { value: 10 }, maxNotificationsPerPublish: { value: 0 },
40
92
  reconnectInterval: { value: 5000 }, name: { value: '' }
41
93
  },