node-red-opcua-otmr 2.0.3 → 3.0.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.
package/README.md CHANGED
@@ -1,19 +1,49 @@
1
- Node-RED OPCUA-OTMR Node
2
- -------------------
1
+ # Node-RED OPCUA-OTMR Node
3
2
 
4
- The node-red-opcua-otmr node makes it easy to connect with OPC UA servers, allowing you to fetch data from specific Node IDs.
3
+ The `@omidteimoori/node-red-opcua` Node-RED node allows seamless communication with OPC UA servers. It enables you to read, write, and subscribe to values from specific Node IDs — ideal for industrial automation and IoT applications.
5
4
 
6
- Customise the Endpoint URLs, Ports, and Node IDs to integrate with industrial automation and IoT projects.
5
+ ## Features
7
6
 
7
+ - Connect to any OPC UA server via customizable endpoint URL and port
8
+ - Read data from specific Node IDs
9
+ - Write data to OPC UA tags
10
+ - Subscribe to live changes
11
+ - Suitable for use in SCADA, MES, and Industry 4.0 projects
8
12
 
13
+ ## Installation
9
14
 
10
- Install:
11
- ----------------
15
+ Run the following command in your Node-RED user directory (typically `~/.node-red`):
12
16
 
17
+ ```bash
18
+ npm install @omidteimoori/node-red-opcua
19
+ ```
13
20
 
14
- Execute the following command in your Node-RED directory (usually located at `~/.node-red`):
21
+ Then restart Node-RED. The node will appear in the **function** category as `opcua-otmr`.
15
22
 
16
- `npm i node-red-opcua-otmr`
23
+ ## Usage
17
24
 
18
- Find the `opcua-otmr` node under your function package.
25
+ 1. Drag the `opcua-otmr` node into your Node-RED workspace
26
+ 2. Configure:
27
+ - Endpoint (e.g., `opc.tcp://192.168.0.10:4840`)
28
+ - Node ID (e.g., `ns=2;s=Temperature`)
29
+ - Operation: Read, Write, or Subscribe
30
+ 3. Deploy the flow and monitor/debug results
19
31
 
32
+ ## Example Use Cases
33
+
34
+ - Real-time temperature monitoring from a PLC
35
+ - Writing setpoints to industrial devices
36
+ - Live dashboards for machine data
37
+
38
+ ## Author
39
+
40
+ Developed by [Omid Teimoori](https://omidteimoori.com)
41
+ [GitHub: omidteimoori](https://github.com/omidteimoori)
42
+
43
+ ## GitHub Repository
44
+
45
+ [GitHub: node-red-opcua-otmr](https://github.com/omidteimoori/node-red-opcua-otmr/)
46
+
47
+ ## NPM Package
48
+
49
+ [https://www.npmjs.com/package/node-red-opcua-otmr](https://www.npmjs.com/package/node-red-opcua-otmr)
package/opcua-otmr.html CHANGED
@@ -7,6 +7,7 @@
7
7
  endpoint: { value: "", required: true },
8
8
  port: { value: 4840, validate: RED.validators.number(), required: true },
9
9
  nodeIds: { value: "", required: true },
10
+ operation: { value: "read", required: true },
10
11
  dataType: { value: "" }
11
12
  },
12
13
  inputs: 1,
@@ -17,16 +18,15 @@
17
18
  },
18
19
  oneditprepare: function() {
19
20
  $('#node-input-name').val(this.name);
21
+ $('#node-input-operation').val(this.operation);
20
22
  $('#node-input-endpoint').val(this.endpoint);
21
23
  $('#node-input-port').val(this.port);
22
24
  $('#node-input-nodeIds').val(this.nodeIds);
23
25
  $('#node-input-dataType').val(this.dataType);
24
-
25
- $('#node-help').html("<p>This node communicates with an OPC UA server and reads values from various Node IDs.</p>");
26
-
27
26
  },
28
27
  oneditsave: function() {
29
28
  this.name = $('#node-input-name').val();
29
+ this.operation = $('#node-input-operation').val();
30
30
  this.endpoint = $('#node-input-endpoint').val();
31
31
  this.port = $('#node-input-port').val();
32
32
  this.nodeIds = $('#node-input-nodeIds').val();
@@ -34,7 +34,7 @@
34
34
 
35
35
  if (!this.endpoint || !this.port || !this.nodeIds) {
36
36
  RED.notify("Endpoint URL, Port, and Node IDs are required fields.", "error");
37
- return false; // Prevents the node from being saved
37
+ return false;
38
38
  }
39
39
  }
40
40
  });
@@ -42,35 +42,31 @@
42
42
 
43
43
  <script type="text/html" data-template-name="opcua-otmr">
44
44
  <div class="form-row">
45
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
45
+ <label for="node-input-name">Name</label>
46
46
  <input type="text" id="node-input-name" placeholder="Name">
47
47
  </div>
48
48
  <div class="form-row">
49
- <label for="node-input-endpoint"><i class="fa fa-wifi"></i> Endpoint</label>
49
+ <label for="node-input-operation">Operation</label>
50
+ <select id="node-input-operation">
51
+ <option value="read">Read</option>
52
+ <option value="write">Write</option>
53
+ </select>
54
+ </div>
55
+ <div class="form-row">
56
+ <label for="node-input-endpoint">Endpoint</label>
50
57
  <input type="text" id="node-input-endpoint" placeholder="opc.tcp://hostname">
51
58
  </div>
52
59
  <div class="form-row">
53
- <label for="node-input-port"><i class="fa fa-hashtag"></i> Port</label>
60
+ <label for="node-input-port">Port</label>
54
61
  <input type="text" id="node-input-port" placeholder="4840">
55
62
  </div>
56
63
  <div class="form-row">
57
- <label for="node-input-nodeIds"><i class="fa fa-list"></i> Node IDs</label>
64
+ <label for="node-input-nodeIds">Node IDs</label>
58
65
  <input type="text" id="node-input-nodeIds" placeholder="NodeID 1, NodeID 2, ...">
59
66
  </div>
60
- <!--
61
- <div class="form-row">
62
- <label for="node-input-dataType"><i class="fa fa-database"></i> Data Type</label>
63
- <input type="text" id="node-input-dataType" placeholder="Data type 1, Data type 2, ...">
64
- </div>
65
- -->
66
-
67
- <script type="text/html" data-help-name="opcua-otmr">
68
- <p>This node communicates with an OPC UA server and reads values from various Node IDs. Data type of each NodeID will be captured automatically</p>
69
- </script>
70
-
71
-
72
67
  </script>
73
68
 
74
69
  <script type="text/html" data-help-name="opcua-otmr">
75
- <p>This node communicates with an OPC UA server and reads values from specified Node IDs.</p>
76
- </script>
70
+ <p>This node communicates with an OPC UA server. You can choose to read or write values from specified Node IDs.</p>
71
+ <p>For write mode, send a simple value in <code>msg.payload</code> (e.g., <code>42</code> or <code>"ON"</code>), and the node will use the configured Node ID and Data Type.</p>
72
+ </script>
package/opcua-otmr.js CHANGED
@@ -1,10 +1,11 @@
1
- module.exports = function(RED) {
1
+ module.exports = function (RED) {
2
2
  function OpcuaOtmrNode(config) {
3
3
  RED.nodes.createNode(this, config);
4
- var node = this;
4
+ const node = this;
5
5
  node.endpoint = config.endpoint;
6
- node.port = parseInt(config.port); // Parse port as an integer
7
- node.nodeIds = config.nodeIds ? config.nodeIds.split(",").map(id => id.trim()) : []; // Split Node IDs by comma and trim whitespace, default to empty array
6
+ node.operation = config.operation || "read";
7
+ node.port = parseInt(config.port);
8
+ node.nodeIds = config.nodeIds ? config.nodeIds.split(",").map(id => id.trim()) : [];
8
9
  node.dataType = config.dataType;
9
10
 
10
11
  const opcua = require("node-opcua");
@@ -19,7 +20,7 @@ module.exports = function(RED) {
19
20
  session = await client.createSession();
20
21
  node.log("Session created");
21
22
  } catch (err) {
22
- node.error("Failed to connect to OPC UA server: " + err.message);
23
+ node.error("Failed to connect to OPC UA server", err);
23
24
  node.status({ fill: "red", shape: "ring", text: "connection failed" });
24
25
  }
25
26
  }
@@ -30,7 +31,7 @@ module.exports = function(RED) {
30
31
  await session.close();
31
32
  node.log("Session closed");
32
33
  } catch (closeError) {
33
- node.error("Failed to close session: " + closeError.message);
34
+ node.error("Failed to close session", closeError);
34
35
  }
35
36
  session = null;
36
37
  }
@@ -38,13 +39,22 @@ module.exports = function(RED) {
38
39
  node.log("Disconnected from OPC UA server");
39
40
  }
40
41
 
41
- node.on('input', async function(msg) {
42
- const nodeIds = node.nodeIds || msg.nodeIds || [];
43
- const dataType = node.dataType || msg.dataType;
42
+ function inferDataType(value) {
43
+ const type = typeof value;
44
+ if (type === "number") return "Double";
45
+ if (type === "boolean") return "Boolean";
46
+ if (type === "string") return "String";
47
+ return null;
48
+ }
49
+
50
+ node.on('input', async function (msg) {
51
+ const nodeId = node.nodeIds[0];
52
+ const configuredType = node.dataType;
53
+ const payload = msg.payload;
44
54
 
45
- if (!node.endpoint || isNaN(node.port) || node.port < 0 || node.port > 65535 || nodeIds.length === 0) {
46
- node.error("Endpoint URL, port, and Node IDs must be provided.");
47
- node.status({ fill: "red", shape: "ring", text: "missing required fields" });
55
+ if (!node.endpoint || isNaN(node.port) || node.port < 0 || node.port > 65535 || !nodeId) {
56
+ node.error("Endpoint URL, port, and a Node ID must be configured.");
57
+ node.status({ fill: "red", shape: "ring", text: "missing configuration" });
48
58
  return;
49
59
  }
50
60
 
@@ -53,29 +63,74 @@ module.exports = function(RED) {
53
63
  }
54
64
 
55
65
  try {
56
- const dataValues = await Promise.all(nodeIds.map(async (nodeId) => {
66
+ if (node.operation === "write") {
67
+ const resolvedType = configuredType || inferDataType(payload);
68
+ if (!resolvedType || !opcua.DataType.hasOwnProperty(resolvedType)) {
69
+ node.error(`Unsupported or missing data type: ${resolvedType}`);
70
+ node.status({ fill: "red", shape: "ring", text: "invalid data type" });
71
+ return;
72
+ }
73
+
74
+ const variant = new opcua.Variant({
75
+ dataType: opcua.DataType[resolvedType],
76
+ value: payload
77
+ });
78
+
79
+ const itemToWrite = {
80
+ nodeId: nodeId,
81
+ attributeId: opcua.AttributeIds.Value,
82
+ value: { value: variant }
83
+ };
84
+
85
+ const results = await session.write([itemToWrite]);
86
+ const statusCode = results?.[0]?.statusCode;
87
+
88
+ msg.payload = {
89
+ nodeId: nodeId,
90
+ value: payload,
91
+ };
92
+
93
+ if (statusCode && statusCode.name === "Good") {
94
+ node.log(`Wrote ${payload} (${resolvedType}) to ${nodeId} [${statusCode.name}]`);
95
+ node.status({ fill: "green", shape: "dot", text: "value written" });
96
+ } else if (statusCode && statusCode.name.startsWith("Bad")) {
97
+ const statusText = statusCode.toString();
98
+ node.warn(`Write failed for ${nodeId}: ${statusText}`);
99
+ node.status({ fill: "red", shape: "ring", text: statusText });
100
+ } else {
101
+ node.log(`Write completed with status: ${statusCode ? statusCode.toString() : "unknown"}`);
102
+ node.status({ fill: "grey", shape: "dot", text: "unknown status" });
103
+ }
104
+
105
+ node.send(msg);
106
+
107
+ } else {
57
108
  const dataValue = await session.readVariableValue(nodeId);
58
- return { nodeId, value: dataValue.value.value };
59
- }));
60
- node.log(`Read values from Node IDs: ${JSON.stringify(dataValues)}`);
61
- msg.payload = dataValues;
62
- node.send(msg);
63
- node.status({ fill: "green", shape: "dot", text: "data read successfully" });
109
+ msg.payload = {
110
+ nodeId: nodeId,
111
+ value: dataValue.value.value
112
+ };
113
+ node.send(msg);
114
+ node.status({ fill: "green", shape: "dot", text: "value read" });
115
+ }
64
116
  } catch (err) {
65
- node.error("Failed to read values from OPC UA server: " + err.message);
66
- node.status({ fill: "red", shape: "ring", text: "read error" });
117
+ const errorDetails = (typeof err === "object")
118
+ ? JSON.stringify(err, Object.getOwnPropertyNames(err))
119
+ : String(err);
120
+ node.error("OPC UA operation failed: " + errorDetails);
121
+ node.status({ fill: "red", shape: "ring", text: "operation error" });
67
122
  }
68
123
  });
69
124
 
70
- node.on('close', async function() {
125
+ node.on('close', async function () {
71
126
  await disconnectFromServer();
72
127
  });
73
128
 
74
- process.on('SIGINT', async function() {
129
+ process.on('SIGINT', async function () {
75
130
  await disconnectFromServer();
76
131
  process.exit();
77
132
  });
78
133
  }
79
134
 
80
135
  RED.nodes.registerType("opcua-otmr", OpcuaOtmrNode);
81
- };
136
+ };
package/package.json CHANGED
@@ -1,22 +1,35 @@
1
1
  {
2
2
  "name": "node-red-opcua-otmr",
3
- "version": "2.0.3",
3
+ "version": "3.0.0",
4
4
  "description": "OPCUA communication",
5
5
  "main": "opcua-otmr.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
9
  "keywords": [
10
- "OPCUA",
11
- "node-red"
10
+ "node-red",
11
+ "opcua",
12
+ "node-red-contrib",
13
+ "opc ua",
14
+ "iiot",
15
+ "industrial",
16
+ "automation"
12
17
  ],
13
- "author": "Omid Teimoori",
18
+ "author": "Omid Teimoori <omid@teimoori.com> (https://omidteimoori.com)",
14
19
  "license": "ISC",
20
+ "homepage": "https://omidteimoori.com/projects",
15
21
  "node-red": {
16
22
  "nodes": {
17
23
  "opcua-otmr": "opcua-otmr.js"
18
24
  }
19
25
  },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/omidteimoori/node-red-opcua-otmr.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/omidteimoori/node-red-opcua-otmr/issues"
32
+ },
20
33
  "dependencies": {
21
34
  "node-opcua": "^2.126.0"
22
35
  }