node-red-contrib-uos-nats 0.1.70 → 0.2.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 +23 -0
- package/examples/README.md +63 -0
- package/examples/advanced-provider.json +87 -0
- package/examples/basic-read-write.json +102 -0
- package/icons/white/datahub-provider.svg +12 -0
- package/icons/white/datahub-read.svg +11 -0
- package/icons/white/datahub-write.svg +11 -0
- package/nodes/datahub-input.html +1 -1
- package/nodes/datahub-output.html +1 -1
- package/nodes/datahub-write.html +41 -5
- package/nodes/datahub-write.js +80 -6
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -37,6 +37,29 @@ Restart Node-RED. The nodes appear in the **"u-OS DataHub NATS"** category in th
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+
## What's New in v0.2.0
|
|
41
|
+
|
|
42
|
+
### 🎯 Variable Key Support
|
|
43
|
+
Write Node now supports **Variable Keys** (e.g., `machine.temp`) in addition to numeric IDs!
|
|
44
|
+
- Automatic Key→ID resolution via provider definition query
|
|
45
|
+
- Cached for performance (5 min TTL)
|
|
46
|
+
- More user-friendly than remembering IDs
|
|
47
|
+
|
|
48
|
+
### 🎨 Custom Icons
|
|
49
|
+
Each node now has a unique, meaningful icon:
|
|
50
|
+
- **Read:** Database with down arrow (data out)
|
|
51
|
+
- **Provider:** Broadcast antenna (publishing)
|
|
52
|
+
- **Write:** Database with up arrow (commands in)
|
|
53
|
+
|
|
54
|
+
### 📦 Example Flows
|
|
55
|
+
Ready-to-import example flows included in `examples/` directory:
|
|
56
|
+
- `basic-read-write.json` - Getting started with Read & Write
|
|
57
|
+
- `advanced-provider.json` - Creating your own provider
|
|
58
|
+
|
|
59
|
+
**Import:** Node-RED menu (☰) → Import → select file from `examples/`
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
40
63
|
## Why NATS Instead of REST API?
|
|
41
64
|
|
|
42
65
|
The u-OS Data Hub offers both **NATS** (this package) and **REST API** access. Here's why NATS is the better choice for Node-RED:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Example Flows for node-red-contrib-uos-nats
|
|
2
|
+
|
|
3
|
+
This directory contains ready-to-import example flows demonstrating the use of u-OS Data Hub nodes.
|
|
4
|
+
|
|
5
|
+
## How to Import
|
|
6
|
+
|
|
7
|
+
1. Open Node-RED
|
|
8
|
+
2. Click the menu (☰) → **Import**
|
|
9
|
+
3. Click **"select a file to import"**
|
|
10
|
+
4. Choose one of the `.json` files from this directory
|
|
11
|
+
5. Click **Import**
|
|
12
|
+
|
|
13
|
+
## Available Examples
|
|
14
|
+
|
|
15
|
+
### 1. `basic-read-write.json`
|
|
16
|
+
|
|
17
|
+
**basic-read-write.json** - Basic example showing:
|
|
18
|
+
- Reading variables from an existing provider (`u_os_adm`)
|
|
19
|
+
- Writing values to a provider variable
|
|
20
|
+
- Debug nodes to see the data flow
|
|
21
|
+
|
|
22
|
+
**What you'll learn:**
|
|
23
|
+
- How to configure the u-OS Config node
|
|
24
|
+
- How to use the DataHub - Read node
|
|
25
|
+
- How to use the DataHub - Write node
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 2. `advanced-provider.json`
|
|
30
|
+
|
|
31
|
+
**Advanced Provider Example** - Shows how to:
|
|
32
|
+
- Create your own Data Hub provider
|
|
33
|
+
- Auto-generate random data every 5 seconds
|
|
34
|
+
- Publish variables with nested structure
|
|
35
|
+
|
|
36
|
+
**What you'll learn:**
|
|
37
|
+
- How to use the DataHub - Provider node
|
|
38
|
+
- How to structure data for publishing
|
|
39
|
+
- How other apps can subscribe to your data in real-time
|
|
40
|
+
|
|
41
|
+
**Check your provider:**
|
|
42
|
+
After deploying, go to:
|
|
43
|
+
```
|
|
44
|
+
u-OS Web UI → Data Hub → Providers → "nodered"
|
|
45
|
+
```
|
|
46
|
+
You'll see all your published variables!
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Both examples use a placeholder config node. **You must update:**
|
|
53
|
+
|
|
54
|
+
1. **Host:** IP address of your u-OS device (e.g., `192.168.10.100`)
|
|
55
|
+
2. **Client ID & Secret:** Get from u-OS Control Center → Identity & access → Clients
|
|
56
|
+
3. **Provider IDs:** Change to match your system's providers
|
|
57
|
+
4. **Variable IDs/Keys:** Update to match your variables
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Need Help?
|
|
62
|
+
|
|
63
|
+
Check the main [README.md](../README.md) for detailed documentation on each node.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "example-flow-2",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "u-OS DataHub Example: Provider",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Advanced example showing how to create your own Data Hub provider.\n\n**What this does:**\n- Creates a provider named 'nodered' (or your clientName)\n- Publishes random temperature data every 5 seconds\n- Other apps can subscribe to this data in real-time!\n\n**Setup:**\n1. Configure the uos-config node\n2. Deploy\n3. Check u-OS Web UI → Data Hub → Providers → 'nodered' to see your variables!"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "inject-provider",
|
|
11
|
+
"type": "inject",
|
|
12
|
+
"z": "example-flow-2",
|
|
13
|
+
"name": "Every 5 seconds",
|
|
14
|
+
"repeat": "5",
|
|
15
|
+
"crontab": "",
|
|
16
|
+
"once": true,
|
|
17
|
+
"onceDelay": 0.1,
|
|
18
|
+
"topic": "",
|
|
19
|
+
"payload": "",
|
|
20
|
+
"payloadType": "date",
|
|
21
|
+
"x": 160,
|
|
22
|
+
"y": 100,
|
|
23
|
+
"wires": [
|
|
24
|
+
[
|
|
25
|
+
"function-random"
|
|
26
|
+
]
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "function-random",
|
|
31
|
+
"type": "function",
|
|
32
|
+
"z": "example-flow-2",
|
|
33
|
+
"name": "Generate Random Data",
|
|
34
|
+
"func": "function randomBetween(min, max) {\n return Math.random() * (max - min) + min;\n}\n\nmsg.payload = {\n machine: {\n status: \"running\",\n speed: Math.floor(randomBetween(1000, 2000)),\n details: {\n temp: randomBetween(30, 80),\n pressure: randomBetween(1.0, 5.0)\n }\n },\n timestamp: new Date().toISOString()\n};\n\nreturn msg;",
|
|
35
|
+
"outputs": 1,
|
|
36
|
+
"timeout": 0,
|
|
37
|
+
"noerr": 0,
|
|
38
|
+
"initialize": "",
|
|
39
|
+
"finalize": "",
|
|
40
|
+
"libs": [],
|
|
41
|
+
"x": 400,
|
|
42
|
+
"y": 100,
|
|
43
|
+
"wires": [
|
|
44
|
+
[
|
|
45
|
+
"provider-node-example"
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "provider-node-example",
|
|
51
|
+
"type": "datahub-output",
|
|
52
|
+
"z": "example-flow-2",
|
|
53
|
+
"name": "Publish to DataHub",
|
|
54
|
+
"connection": "config-example-2",
|
|
55
|
+
"providerId": "",
|
|
56
|
+
"x": 660,
|
|
57
|
+
"y": 100,
|
|
58
|
+
"wires": [
|
|
59
|
+
[
|
|
60
|
+
"debug-provider"
|
|
61
|
+
]
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "debug-provider",
|
|
66
|
+
"type": "debug",
|
|
67
|
+
"z": "example-flow-2",
|
|
68
|
+
"name": "Published Data",
|
|
69
|
+
"active": true,
|
|
70
|
+
"tosidebar": true,
|
|
71
|
+
"console": false,
|
|
72
|
+
"tostatus": false,
|
|
73
|
+
"complete": "payload",
|
|
74
|
+
"targetType": "msg",
|
|
75
|
+
"x": 890,
|
|
76
|
+
"y": 100,
|
|
77
|
+
"wires": []
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "config-example-2",
|
|
81
|
+
"type": "uos-config",
|
|
82
|
+
"host": "192.168.10.100",
|
|
83
|
+
"port": 49360,
|
|
84
|
+
"clientName": "nodered",
|
|
85
|
+
"scope": "hub.variables.provide hub.variables.readonly hub.variables.readwrite"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "example-flow-1",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "u-OS DataHub Example: Read & Write",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Basic example showing how to read from and write to the u-OS Data Hub.\n\n**Setup:**\n1. Configure the uos-config node with your u-OS device credentials\n2. Update Provider IDs to match your system\n3. Update Variable IDs/Keys to match your variables\n4. Deploy and test!"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "read-node-example",
|
|
11
|
+
"type": "datahub-input",
|
|
12
|
+
"z": "example-flow-1",
|
|
13
|
+
"name": "Read Zipcode",
|
|
14
|
+
"connection": "config-example",
|
|
15
|
+
"providerId": "u_os_adm",
|
|
16
|
+
"manualVariables": "digital_nameplate.address_information.zipcode:2",
|
|
17
|
+
"triggerMode": "event",
|
|
18
|
+
"pollingInterval": "1000",
|
|
19
|
+
"x": 150,
|
|
20
|
+
"y": 100,
|
|
21
|
+
"wires": [
|
|
22
|
+
[
|
|
23
|
+
"debug-read"
|
|
24
|
+
]
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "debug-read",
|
|
29
|
+
"type": "debug",
|
|
30
|
+
"z": "example-flow-1",
|
|
31
|
+
"name": "Display Read Data",
|
|
32
|
+
"active": true,
|
|
33
|
+
"tosidebar": true,
|
|
34
|
+
"console": false,
|
|
35
|
+
"tostatus": false,
|
|
36
|
+
"complete": "payload",
|
|
37
|
+
"targetType": "msg",
|
|
38
|
+
"x": 380,
|
|
39
|
+
"y": 100,
|
|
40
|
+
"wires": []
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "inject-write",
|
|
44
|
+
"type": "inject",
|
|
45
|
+
"z": "example-flow-1",
|
|
46
|
+
"name": "Click to Write",
|
|
47
|
+
"repeat": "",
|
|
48
|
+
"crontab": "",
|
|
49
|
+
"once": false,
|
|
50
|
+
"onceDelay": 0.1,
|
|
51
|
+
"topic": "",
|
|
52
|
+
"payload": "false",
|
|
53
|
+
"payloadType": "bool",
|
|
54
|
+
"x": 150,
|
|
55
|
+
"y": 200,
|
|
56
|
+
"wires": [
|
|
57
|
+
[
|
|
58
|
+
"write-node-example"
|
|
59
|
+
]
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "write-node-example",
|
|
64
|
+
"type": "datahub-write",
|
|
65
|
+
"z": "example-flow-1",
|
|
66
|
+
"name": "Write Value",
|
|
67
|
+
"connection": "config-example",
|
|
68
|
+
"providerId": "u_os_adm",
|
|
69
|
+
"variableId": "5",
|
|
70
|
+
"variableKey": "",
|
|
71
|
+
"x": 340,
|
|
72
|
+
"y": 200,
|
|
73
|
+
"wires": [
|
|
74
|
+
[
|
|
75
|
+
"debug-write"
|
|
76
|
+
]
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "debug-write",
|
|
81
|
+
"type": "debug",
|
|
82
|
+
"z": "example-flow-1",
|
|
83
|
+
"name": "Write Confirmation",
|
|
84
|
+
"active": true,
|
|
85
|
+
"tosidebar": true,
|
|
86
|
+
"console": false,
|
|
87
|
+
"tostatus": false,
|
|
88
|
+
"complete": "payload",
|
|
89
|
+
"targetType": "msg",
|
|
90
|
+
"x": 560,
|
|
91
|
+
"y": 200,
|
|
92
|
+
"wires": []
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "config-example",
|
|
96
|
+
"type": "uos-config",
|
|
97
|
+
"host": "192.168.10.100",
|
|
98
|
+
"port": 49360,
|
|
99
|
+
"clientName": "nodered",
|
|
100
|
+
"scope": "hub.variables.provide hub.variables.readonly hub.variables.readwrite"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 60">
|
|
2
|
+
<!-- Broadcast/antenna symbol -->
|
|
3
|
+
<circle cx="20" cy="30" r="3" fill="white"/>
|
|
4
|
+
|
|
5
|
+
<!-- Inner waves -->
|
|
6
|
+
<path d="M 12,22 Q 8,30 12,38" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
|
7
|
+
<path d="M 28,22 Q 32,30 28,38" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
|
8
|
+
|
|
9
|
+
<!-- Outer waves -->
|
|
10
|
+
<path d="M 7,14 Q 2,30 7,46" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
|
11
|
+
<path d="M 33,14 Q 38,30 33,46" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 60">
|
|
2
|
+
<!-- Database cylinder -->
|
|
3
|
+
<ellipse cx="20" cy="15" rx="14" ry="5" fill="none" stroke="white" stroke-width="2.5"/>
|
|
4
|
+
<line x1="6" y1="15" x2="6" y2="35" stroke="white" stroke-width="2.5"/>
|
|
5
|
+
<line x1="34" y1="15" x2="34" y2="35" stroke="white" stroke-width="2.5"/>
|
|
6
|
+
<ellipse cx="20" cy="35" rx="14" ry="5" fill="none" stroke="white" stroke-width="2.5"/>
|
|
7
|
+
|
|
8
|
+
<!-- Arrow pointing DOWN (data flowing out) -->
|
|
9
|
+
<line x1="20" y1="40" x2="20" y2="55" stroke="white" stroke-width="2.5"/>
|
|
10
|
+
<polyline points="15,50 20,55 25,50" fill="none" stroke="white" stroke-width="2.5" stroke-linejoin="round"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 60">
|
|
2
|
+
<!-- Database cylinder -->
|
|
3
|
+
<ellipse cx="20" cy="45" rx="14" ry="5" fill="none" stroke="white" stroke-width="2.5"/>
|
|
4
|
+
<line x1="6" y1="45" x2="6" y2="25" stroke="white" stroke-width="2.5"/>
|
|
5
|
+
<line x1="34" y1="45" x2="34" y2="25" stroke="white" stroke-width="2.5"/>
|
|
6
|
+
<ellipse cx="20" cy="25" rx="14" ry="5" fill="none" stroke="white" stroke-width="2.5"/>
|
|
7
|
+
|
|
8
|
+
<!-- Arrow pointing UP (data flowing in/command) -->
|
|
9
|
+
<line x1="20" y1="20" x2="20" y2="5" stroke="white" stroke-width="2.5"/>
|
|
10
|
+
<polyline points="15,10 20,5 25,10" fill="none" stroke="white" stroke-width="2.5" stroke-linejoin="round"/>
|
|
11
|
+
</svg>
|
package/nodes/datahub-input.html
CHANGED
package/nodes/datahub-write.html
CHANGED
|
@@ -7,18 +7,41 @@
|
|
|
7
7
|
connection: { type: "uos-config", required: true },
|
|
8
8
|
providerId: { value: "", required: true },
|
|
9
9
|
variableId: { value: "", required: false },
|
|
10
|
-
|
|
10
|
+
variableKey: { value: "", required: false }
|
|
11
11
|
},
|
|
12
12
|
inputs: 1,
|
|
13
13
|
outputs: 1,
|
|
14
|
-
icon: "white/datahub-
|
|
14
|
+
icon: "white/datahub-write.svg",
|
|
15
15
|
label: function () {
|
|
16
16
|
if (this.name) return this.name;
|
|
17
|
-
|
|
17
|
+
const target = this.variableKey || this.variableId || 'var';
|
|
18
|
+
return `Write → ${this.providerId}:${target}`;
|
|
18
19
|
},
|
|
19
20
|
paletteLabel: "DataHub - Write",
|
|
20
21
|
oneditprepare: function () {
|
|
21
|
-
//
|
|
22
|
+
// Validate: at least one of ID or Key required
|
|
23
|
+
const validateVar = () => {
|
|
24
|
+
const hasId = $('#node-input-variableId').val();
|
|
25
|
+
const hasKey = $('#node-input-variableKey').val();
|
|
26
|
+
if (!hasId && !hasKey) {
|
|
27
|
+
$('#var-validation-error').show();
|
|
28
|
+
return false;
|
|
29
|
+
} else {
|
|
30
|
+
$('#var-validation-error').hide();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
$('#node-input-variableId, #node-input-variableKey').on('change keyup', validateVar);
|
|
36
|
+
},
|
|
37
|
+
oneditsave: function () {
|
|
38
|
+
// Final validation before save
|
|
39
|
+
const hasId = this.variableId;
|
|
40
|
+
const hasKey = this.variableKey;
|
|
41
|
+
if (!hasId && !hasKey) {
|
|
42
|
+
alert('Please provide either Variable ID or Variable Key');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
22
45
|
}
|
|
23
46
|
});
|
|
24
47
|
</script>
|
|
@@ -41,10 +64,23 @@
|
|
|
41
64
|
|
|
42
65
|
<div class="form-row">
|
|
43
66
|
<label for="node-input-variableId"><i class="fa fa-hashtag"></i> Variable ID</label>
|
|
44
|
-
<input type="number" id="node-input-variableId" placeholder="e.g. 5">
|
|
67
|
+
<input type="number" id="node-input-variableId" placeholder="e.g. 5 (optional)">
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="form-row">
|
|
71
|
+
<label for="node-input-variableKey"><i class="fa fa-key"></i> Variable Key</label>
|
|
72
|
+
<input type="text" id="node-input-variableKey" placeholder="e.g. machine.temp (optional)">
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div id="var-validation-error" class="form-tips" style="display:none; color:#c00;">
|
|
76
|
+
<i class="fa fa-warning"></i> <b>Either Variable ID or Variable Key is required</b>
|
|
45
77
|
</div>
|
|
46
78
|
|
|
47
79
|
<div class="form-tips">
|
|
80
|
+
<i class="fa fa-info-circle"></i> Provide <b>either</b> Variable ID (numeric) <b>or</b> Variable Key (text). Key will be resolved to ID automatically.
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="form-tips" style="margin-top:10px;">
|
|
48
84
|
<i class="fa fa-info-circle"></i> Send <code>msg.payload</code> with the value to write (e.g., <code>true</code>, <code>42</code>, <code>"hello"</code>)
|
|
49
85
|
</div>
|
|
50
86
|
</script>
|
package/nodes/datahub-write.js
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
import { encodeWriteVariablesCommand } from '../lib/payloads.js';
|
|
2
|
+
import { buildReadProviderDefinitionQuery, decodeProviderDefinition } from '../lib/payloads.js';
|
|
3
|
+
|
|
4
|
+
// Simple cache for provider definitions (5 min TTL)
|
|
5
|
+
const providerCache = new Map();
|
|
6
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
7
|
+
|
|
8
|
+
async function resolveVariableKey(nc, providerId, key, node) {
|
|
9
|
+
const cacheKey = `${providerId}`;
|
|
10
|
+
const cached = providerCache.get(cacheKey);
|
|
11
|
+
|
|
12
|
+
// Check cache
|
|
13
|
+
if (cached && (Date.now() - cached.timestamp < CACHE_TTL)) {
|
|
14
|
+
const variable = cached.definition.variables.find(v => v.key === key);
|
|
15
|
+
if (variable) {
|
|
16
|
+
node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id} (cached)`);
|
|
17
|
+
return variable.id;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Query provider definition
|
|
22
|
+
try {
|
|
23
|
+
const query = buildReadProviderDefinitionQuery();
|
|
24
|
+
const subject = `v1.loc.${providerId}.def.query`;
|
|
25
|
+
|
|
26
|
+
const response = await nc.request(subject, query, { timeout: 3000 });
|
|
27
|
+
const definition = decodeProviderDefinition(response.data);
|
|
28
|
+
|
|
29
|
+
if (!definition) {
|
|
30
|
+
throw new Error(`Provider ${providerId} not found or no definition returned`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cache the definition
|
|
34
|
+
providerCache.set(cacheKey, {
|
|
35
|
+
definition,
|
|
36
|
+
timestamp: Date.now()
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Find variable by key
|
|
40
|
+
const variable = definition.variables.find(v => v.key === key);
|
|
41
|
+
if (!variable) {
|
|
42
|
+
throw new Error(`Variable key '${key}' not found in provider ${providerId}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id}`);
|
|
46
|
+
return variable.id;
|
|
47
|
+
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(`Failed to resolve key '${key}': ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
2
52
|
|
|
3
53
|
export default function (RED) {
|
|
4
54
|
function DataHubWriteNode(config) {
|
|
@@ -16,6 +66,8 @@ export default function (RED) {
|
|
|
16
66
|
// Store configuration
|
|
17
67
|
this.providerId = config.providerId?.trim();
|
|
18
68
|
this.variableId = config.variableId ? parseInt(config.variableId, 10) : null;
|
|
69
|
+
this.variableKey = config.variableKey?.trim();
|
|
70
|
+
this.resolvedId = null; // Cached resolved ID
|
|
19
71
|
|
|
20
72
|
if (!this.providerId) {
|
|
21
73
|
node.error('Provider ID is required');
|
|
@@ -23,13 +75,25 @@ export default function (RED) {
|
|
|
23
75
|
return;
|
|
24
76
|
}
|
|
25
77
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
node.
|
|
78
|
+
// Validate: either ID or Key required
|
|
79
|
+
if (!this.variableId && !this.variableKey) {
|
|
80
|
+
node.error('Either Variable ID or Variable Key is required');
|
|
81
|
+
node.status({ fill: 'red', shape: 'dot', text: 'missing variable' });
|
|
29
82
|
return;
|
|
30
83
|
}
|
|
31
84
|
|
|
32
|
-
|
|
85
|
+
// If ID provided and valid, use it
|
|
86
|
+
if (this.variableId && !isNaN(this.variableId)) {
|
|
87
|
+
this.resolvedId = this.variableId;
|
|
88
|
+
node.status({ fill: 'green', shape: 'ring', text: 'ready' });
|
|
89
|
+
} else if (this.variableKey) {
|
|
90
|
+
// Key provided - will resolve on first message
|
|
91
|
+
node.status({ fill: 'yellow', shape: 'ring', text: 'key needs resolution' });
|
|
92
|
+
} else {
|
|
93
|
+
node.error('Invalid Variable ID');
|
|
94
|
+
node.status({ fill: 'red', shape: 'dot', text: 'invalid ID' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
33
97
|
|
|
34
98
|
// Handle incoming messages
|
|
35
99
|
node.on('input', async function (msg) {
|
|
@@ -49,10 +113,19 @@ export default function (RED) {
|
|
|
49
113
|
return;
|
|
50
114
|
}
|
|
51
115
|
|
|
116
|
+
// Resolve variable ID if needed
|
|
117
|
+
let varId = node.resolvedId;
|
|
118
|
+
if (!varId && node.variableKey) {
|
|
119
|
+
node.status({ fill: 'yellow', shape: 'dot', text: 'resolving key...' });
|
|
120
|
+
varId = await resolveVariableKey(nc, node.providerId, node.variableKey, node);
|
|
121
|
+
node.resolvedId = varId; // Cache for future messages
|
|
122
|
+
node.status({ fill: 'green', shape: 'ring', text: 'ready' });
|
|
123
|
+
}
|
|
124
|
+
|
|
52
125
|
// Build write command
|
|
53
126
|
const writeCommand = encodeWriteVariablesCommand([
|
|
54
127
|
{
|
|
55
|
-
id:
|
|
128
|
+
id: varId,
|
|
56
129
|
value: value
|
|
57
130
|
}
|
|
58
131
|
]);
|
|
@@ -67,7 +140,8 @@ export default function (RED) {
|
|
|
67
140
|
msg.payload = {
|
|
68
141
|
success: true,
|
|
69
142
|
providerId: node.providerId,
|
|
70
|
-
variableId:
|
|
143
|
+
variableId: varId,
|
|
144
|
+
variableKey: node.variableKey || null,
|
|
71
145
|
value: value
|
|
72
146
|
};
|
|
73
147
|
node.send(msg);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-uos-nats",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read, write, and provide variables via NATS protocol with OAuth2 authentication.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read, write, and provide variables via NATS protocol with OAuth2 authentication. Features: Variable Key resolution, custom icons, example flows, and provider definition caching.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "IoTUeli",
|
|
7
7
|
"url": "https://www.linkedin.com/in/iotueli/"
|
|
@@ -44,6 +44,13 @@
|
|
|
44
44
|
"nats": "^2.19.0",
|
|
45
45
|
"node-fetch": "^2.6.7"
|
|
46
46
|
},
|
|
47
|
+
"files": [
|
|
48
|
+
"nodes/",
|
|
49
|
+
"lib/",
|
|
50
|
+
"icons/",
|
|
51
|
+
"examples/",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
47
54
|
"node-red": {
|
|
48
55
|
"version": ">=2.0.0",
|
|
49
56
|
"nodes": {
|