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 +39 -9
- package/opcua-otmr.html +18 -22
- package/opcua-otmr.js +79 -24
- package/package.json +17 -4
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-
|
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
|
-
|
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
|
-
|
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
|
-
|
21
|
+
Then restart Node-RED. The node will appear in the **function** category as `opcua-otmr`.
|
15
22
|
|
16
|
-
|
23
|
+
## Usage
|
17
24
|
|
18
|
-
|
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;
|
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"
|
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-
|
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"
|
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"
|
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
|
76
|
-
</
|
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
|
-
|
4
|
+
const node = this;
|
5
5
|
node.endpoint = config.endpoint;
|
6
|
-
node.
|
7
|
-
node.
|
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
|
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
|
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
|
-
|
42
|
-
const
|
43
|
-
|
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 ||
|
46
|
-
node.error("Endpoint URL, port, and Node
|
47
|
-
node.status({ fill: "red", shape: "ring", text: "missing
|
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
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
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": "
|
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
|
-
"
|
11
|
-
"
|
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
|
}
|