node-red-contrib-uos-nats 0.1.65 → 0.1.67

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
@@ -2,20 +2,21 @@
2
2
 
3
3
  **Unofficial Node-RED Package for u-OS Data Hub**
4
4
 
5
- Built and maintained by [IoTUeli](https://www.linkedin.com/in/iotueli/). This is **not** an official Weidmüller product.
5
+ Built and maintained by [IoTUeli](https://iotueli.ch). This is **not** an official Weidmüller product.
6
6
  Repository: <https://github.com/uiff/nats-NodeRed-Node-uc20>
7
7
 
8
8
  ---
9
9
 
10
10
  ## What is this?
11
11
 
12
- Node-RED nodes to **read** and **write** variables from the **u-OS Data Hub** via NATS protocol.
12
+ Node-RED nodes to **read**, **write**, and **provide** variables for the **Weidmüller u-OS Data Hub** via NATS protocol.
13
13
 
14
- ### The Three Nodes:
14
+ ### The Four Nodes
15
15
 
16
16
  1. **u-OS Config** – Connection settings (Host, OAuth credentials, NATS connection)
17
- 2. **DataHub - IN** – Read variables from Data Hub providers
18
- 3. **DataHub - OUT** – Write variables to the Data Hub (creates your own provider)
17
+ 2. **DataHub - Read** – Read variables from Data Hub providers
18
+ 3. **DataHub - Provider** – Create your own provider (publish variables)
19
+ 4. **DataHub - Write** – Send write commands to other providers
19
20
 
20
21
  ---
21
22
 
@@ -71,14 +72,14 @@ Process & forward to other systems
71
72
  - Delayed reactions
72
73
  - Higher CPU usage
73
74
 
74
- ### Use NATS when:
75
+ ### Use NATS when
75
76
 
76
77
  ✅ You need **real-time reactions** to value changes
77
78
  ✅ You want to **create providers** (publish data to Data Hub)
78
79
  ✅ You need **event subscriptions** (get notified on changes)
79
80
  ✅ You're building **scalable industrial workflows**
80
81
 
81
- ### Use REST API when:
82
+ ### Use REST API when
82
83
 
83
84
  ⚠️ You only need **occasional manual reads**
84
85
  ⚠️ You're debugging or doing one-time queries
@@ -86,164 +87,174 @@ Process & forward to other systems
86
87
 
87
88
  ---
88
89
 
89
- ## 1. u-OS Config Node
90
+ ## Quick Start Guide
90
91
 
91
- ### Purpose
92
- Stores connection details and OAuth credentials. All DataHub nodes share this configuration.
92
+ ### Step 1: Create OAuth Client in u-OS
93
+
94
+ Before configuring Node-RED, create an OAuth client on your u-OS device:
93
95
 
94
- ### Setup Steps
96
+ 1. Open the **u-OS Web Interface** (e.g., `http://192.168.10.100`)
97
+ 2. Go to **System** → **Access Control** → **OAuth Clients**
98
+ 3. Click **"Add Client"**
99
+ 4. Enter:
100
+ - **Name:** `nodered`
101
+ - **Scopes:** Select **all** `hub.variables.*` scopes:
102
+ - `hub.variables.provide` (for creating providers)
103
+ - `hub.variables.readonly` (for reading)
104
+ - `hub.variables.readwrite` (for writing)
105
+ 5. **Save** and copy the **Client ID** and **Client Secret**
95
106
 
96
- 1. **Add a Config Node:**
97
- - Open any DataHub node (IN or OUT)
98
- - Click the pencil icon next to "Config"
99
- - Click "Add new uos-config..."
107
+ ### Step 2: Configure u-OS Config Node in Node-RED
100
108
 
101
- 2. **Fill in the Fields:**
109
+ 1. Drag any **DataHub - IN** or **DataHub - OUT** node onto the canvas
110
+ 2. Double-click it to open settings
111
+ 3. Click the **pencil icon** next to "Config"
112
+ 4. Select **"Add new uos-config..."**
113
+ 5. Fill in:
102
114
 
103
115
  | Field | Example | Description |
104
116
  |-------|---------|-------------|
105
- | **Host** | `192.168.10.100` | IP address of your u-Control device |
106
- | **Port** | `49360` | NATS port (default: 49360) |
107
- | **Client Name** | `nodered` | Unique name for this Node-RED instance |
108
- | **Client ID** | `my-oauth-client` | OAuth2 Client ID (from Control Center) |
109
- | **Client Secret** | `****************` | OAuth2 Client Secret (from Control Center) |
110
- | **Scopes** | `hub.variables.*` | Leave default or customize (see below) |
111
-
112
- 3. **Create OAuth Client in Control Center:**
113
- - Open the u-Control Web Interface
114
- - Go to **System** → **Access Control** → **OAuth Clients**
115
- - Click **"Add Client"**
116
- - **Name:** `nodered`
117
- - **Scopes:** Select all `hub.variables.*` (provide, readonly, readwrite)
118
- - **Copy the Client ID and Secret** into Node-RED
117
+ | **Host** | `192.168.10.100` | IP of your u-OS device |
118
+ | **Port** | `49360` | NATS port (default) |
119
+ | **Client Name** | `nodered` | Unique name for this instance |
120
+ | **Client ID** | `my-oauth-client` | From Step 1 |
121
+ | **Client Secret** | `****************` | From Step 1 |
119
122
 
120
- 4. **Test Connection:**
121
- - Click **"Test Connection"** button
122
- - ✅ Success: Shows "Connected" + granted scopes
123
- - ❌ Error: Check Host/Port/Credentials
123
+ 6. Click **"Test Connection"** to verify
124
+ 7. On success, click **"Add"** then **"Done"**
124
125
 
125
- ---
126
+ ### Step 3: Deploy Your First Flow
126
127
 
127
- ## 2. DataHub - IN Node (Read Variables)
128
+ Import this example flow to test both reading and writing:
128
129
 
129
- ### Purpose
130
- Subscribe to variables from a Data Hub provider and output their values as JSON messages.
130
+ ```json
131
+ [{"id":"cdad2fa96dc6eeec","type":"datahub-input","z":"c221537c994b056a","name":"","connection":"a0ba0e15c8dad779","providerId":"u_os_adm","manualVariables":"digital_nameplate.address_information.zipcode:2","triggerMode":"poll","pollingInterval":"100","x":110,"y":40,"wires":[["315d179d66bf9b93"]]},{"id":"315d179d66bf9b93","type":"debug","z":"c221537c994b056a","name":"debug 7","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":40,"wires":[]},{"id":"09f29f6bfc4e1be2","type":"inject","z":"c221537c994b056a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":140,"wires":[["43b2fcf73c370f7c"]]},{"id":"43b2fcf73c370f7c","type":"function","z":"c221537c994b056a","name":"Random Data","func":"function randomBetween(min, max) {\n return Math.random() * (max - min) + min;\n}\n\nmsg.payload = {\n machine: {\n status: \"running\",\n details: {\n temp: randomBetween(30, 80)\n }\n }\n};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":140,"wires":[["a90304487fe19b3a"]]},{"id":"a90304487fe19b3a","type":"datahub-output","z":"c221537c994b056a","name":"","connection":"a0ba0e15c8dad779","providerId":"","x":500,"y":140,"wires":[["e53fa58e4c1987ba"]]},{"id":"e53fa58e4c1987ba","type":"debug","z":"c221537c994b056a","name":"debug 6","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":140,"wires":[]},{"id":"a0ba0e15c8dad779","type":"uos-config","host":"127.0.0.1","port":49360,"clientName":"hub","scope":"hub.variables.provide hub.variables.readwrite hub.variables.readonly"}]
132
+ ```
131
133
 
132
- ### Setup Steps
134
+ **What this flow does:**
133
135
 
134
- #### Step 1: Select Config
135
- - Choose your **u-OS Config** node
136
+ **Top Row** (Reading):
137
+ - **DataHub - IN** reads `zipcode` variable from provider `u_os_adm`
138
+ - Polls every 100ms
139
+ - Outputs to Debug node
136
140
 
137
- #### Step 2: Enter Provider ID
138
- - **What is it?** The name of the data source (e.g., `u_os_sbm`, `hub`, `custom-provider`)
139
- - **Where to find it?**
140
- - u-Control Web Interface **Data Hub** **Providers**
141
- - Or use the Python sample's `PROVIDER_ID`
141
+ **Bottom Row** (Writing):
142
+ - **Inject** node triggers data generation
143
+ - **Function** node creates random temperature data
144
+ - **DataHub - OUT** publishes to Data Hub as provider `hub`
145
+ - Creates variables: `machine.status` and `machine.details.temp`
142
146
 
143
- **Example:** `u_os_sbm`
147
+ **To customize:**
148
+ 1. Edit the **DataHub - IN** node:
149
+ - Change `Provider ID` to match your system
150
+ - Update variable mappings in the table
151
+ 2. Edit the **Function** node to generate your data structure
152
+ 3. Click **Deploy**
144
153
 
145
- #### Step 3: Add Variables (Manual Table)
146
- Since auto-discovery often fails due to permissions, you **manually map** variable names to their IDs.
154
+ ---
147
155
 
148
- **How to find Variable IDs:**
156
+ ## Finding Variable IDs
149
157
 
150
- **Option A: From Python Config**
151
- ```python
152
- # Your working Python config.py
153
- VARIABLE_DEFINITIONS = [
154
- {"id": 0, "key": "manufacturer_name", ...},
155
- {"id": 2, "key": "machine.details.temp", ...}
156
- ]
157
- ```
158
- → Use these IDs!
158
+ The **DataHub - IN** node requires variable IDs (numbers). Here's how to find them:
159
+
160
+ ### Option 1: u-OS Web Interface
161
+
162
+ 1. Open **Data Hub** → **Providers**
163
+ 2. Click on your target provider (e.g., `u_os_adm`)
164
+ 3. Click **Variables** tab
165
+ 4. Note the **ID** column (e.g., `0`, `1`, `2`)
159
166
 
160
- **Option B: From u-Control Web UI**
161
- 1. Open **Data Hub** → **Providers** → Select your provider
162
- 2. Click on **Variables**
163
- 3. Note the **ID** column (usually 0, 1, 2, ...)
167
+ ### Option 2: REST API Query
164
168
 
165
- **Option C: From REST API**
166
169
  ```bash
167
170
  curl -H "Authorization: Bearer $TOKEN" \
168
- http://192.168.10.100/datahub/v1/providers/u_os_sbm/variables
171
+ http://192.168.10.100/datahub/v1/providers/u_os_adm/variables
172
+ ```
173
+
174
+ Response shows variable definitions with IDs:
175
+ ```json
176
+ {
177
+ "variables": [
178
+ {"id": 0, "key": "manufacturer_name", ...},
179
+ {"id": 2, "key": "digital_nameplate.address_information.zipcode", ...}
180
+ ]
181
+ }
169
182
  ```
170
183
 
171
- **Fill the Table:**
184
+ ### Option 3: Check Other Apps
185
+
186
+ If you have other Data Hub clients (apps, PLCs), check their variable definitions to find the IDs.
187
+
188
+ ---
189
+
190
+ ## DataHub - Read Node
191
+
192
+ ### Purpose
193
+ Subscribe to variables from a Data Hub provider and output their values as JSON messages.
194
+
195
+ ### Configuration
196
+
197
+ 1. **Config Node:** Select your u-OS connection
198
+ 2. **Provider ID:** Enter the provider name (e.g., `u_os_adm`, `hub`, `u_os_sbm`)
199
+ 3. **Variables Table:** Manually map variable names to IDs
172
200
 
173
201
  | Variable Name | ID |
174
202
  |--------------|-----|
175
203
  | `manufacturer_name` | `0` |
176
- | `machine.details.temp` | `2` |
204
+ | `zipcode` | `2` |
177
205
 
178
206
  Click **"Add Variable"** for each entry.
179
207
 
180
- #### Step 4: Choose Trigger Mode
181
-
182
- | Mode | When to Use |
183
- |------|-------------|
184
- | **Event (on change)** | Default. Efficient. Outputs only when values change. |
185
- | **Poll (interval)** | Forces periodic reads (e.g., every 1000ms). Less efficient. |
186
-
187
- #### Step 5: Deploy & Test
188
- 1. Click **Deploy**
189
- 2. Connect a **Debug** node to the output
190
- 3. Send a message to the **Input Port** (using an **Inject** node) to trigger a read
191
- 4. Check the Debug panel for output:
192
- ```json
193
- {
194
- "type": "snapshot",
195
- "variables": [
196
- {"providerId": "u_os_sbm", "id": 0, "key": "manufacturer_name", "value": "Weidmüller", ...}
197
- ]
198
- }
199
- ```
200
-
201
- ### Troubleshooting
202
-
203
- | Problem | Solution |
204
- |---------|----------|
205
- | No output | Check: (1) Config deployed? (2) Provider ID correct? (3) Variable IDs correct? (4) Inject signal sent? |
206
- | "Variable not found" | Double-check IDs in the table. Use Python config or Web UI to verify. |
207
- | Permission errors | This is expected! That's why we use the manual table. |
208
+ 4. **Trigger Mode:**
209
+ - **Event (on change):** Efficient. Outputs only when values change.
210
+ - **Poll (interval):** Forces periodic reads (e.g., every 100ms).
211
+
212
+ ### Output Format
213
+
214
+ ```json
215
+ {
216
+ "type": "snapshot",
217
+ "variables": [
218
+ {
219
+ "providerId": "u_os_adm",
220
+ "id": 2,
221
+ "key": "zipcode",
222
+ "value": "12345",
223
+ "quality": "GOOD",
224
+ "timestampNs": 1234567890000000000
225
+ }
226
+ ]
227
+ }
228
+ ```
229
+
230
+ ### Triggering Reads
231
+
232
+ Connect an **Inject** node to the input port to trigger manual reads.
208
233
 
209
234
  ---
210
235
 
211
- ## 3. DataHub - OUT Node (Write Variables)
236
+ ## DataHub - Provider Node
212
237
 
213
238
  ### Purpose
214
- **Creates a real Data Hub provider** that publishes variables to the u-OS Data Hub. Other applications, devices, or even other Node-RED instances can **subscribe to your data in real-time**.
239
+ Creates a real Data Hub provider that publishes variables. Other applications can subscribe to your data in real-time.
215
240
 
216
241
  ### How It Works
217
242
 
218
243
  The OUT node:
219
- 1. **Registers as a Provider** on the Data Hub (e.g., provider ID: `nodered`)
244
+ 1. **Registers as a Provider** on the Data Hub (uses `Client Name` from Config)
220
245
  2. **Publishes variable definitions** automatically when new variables are sent
221
246
  3. **Sends value updates** via NATS when you send JSON messages
222
- 4. **Answers read requests** from other consumers (apps can query your latest values)
223
- 5. **Supports event-driven subscriptions** - other apps get updates **instantly** when values change
224
-
225
- **Important:** This provider only exists **while Node-RED is running**. When you restart Node-RED, the provider re-registers automatically.
226
-
227
- ### Real-World Use Cases
228
-
229
- ✅ **IoT Data Collection:** Node-RED reads sensor data (Modbus, MQTT, etc.) and publishes it to the Data Hub
230
- ✅ **Edge Processing:** Process data locally in Node-RED, then share results with other apps
231
- ✅ **System Integration:** Bridge between different protocols (e.g., OPC UA → Data Hub)
232
- ✅ **Custom Dashboards:** Other apps can subscribe to Node-RED's variables for visualization
233
-
234
- ### Setup Steps
247
+ 4. **Answers read requests** from other consumers
248
+ 5. **Supports event-driven subscriptions** - other apps get updates **instantly**
235
249
 
236
- #### Step 1: Select Config
237
- - Choose your **u-OS Config** node
250
+ ### Configuration
238
251
 
239
- #### Step 2: Provider ID (Optional)
240
- - **Leave EMPTY** to use the `Client Name` from your Config (recommended)
241
- - Or enter a custom provider ID (e.g., `my-machine-data`)
252
+ 1. **Config Node:** Select your u-OS connection
253
+ 2. **Provider ID:** Leave **empty** to use `Client Name` (recommended)
242
254
 
243
- **Example:** If your Config's Client Name is `nodered`, the provider will be `nodered`
255
+ ### Send Data
244
256
 
245
- #### Step 3: Send JSON Messages
246
- Send a JSON object with your data:
257
+ Send JSON to the input:
247
258
 
248
259
  ```json
249
260
  {
@@ -255,91 +266,167 @@ Send a JSON object with your data:
255
266
  }
256
267
  ```
257
268
 
258
- This creates variables:
259
- - `temperature` → ID 0
260
- - `machine.status` → ID 1
261
- - `machine.speed` → ID 2
269
+ This creates:
270
+ - `temperature` → Variable ID 0
271
+ - `machine.status` → Variable ID 1
272
+ - `machine.speed` → Variable ID 2
262
273
 
263
- **The variables are INSTANTLY available to other apps via:**
264
- - **Event subscriptions** (other apps get updates when values change)
265
- - **Read queries** (other apps can request current values)
266
- - **u-Control Web UI** (visible in Data Hub → Providers → `nodered`)
274
+ **Other apps can now:**
275
+ - Subscribe to value changes (event-driven, instant updates)
276
+ - Query current values (on-demand reads)
277
+ - View in u-OS Web UI (**Data Hub****Providers** → `nodered`)
267
278
 
268
279
  ### Event-Driven Communication
269
280
 
270
- When you send a message to the OUT node:
271
-
272
281
  ```
273
- [Function: {"temp": 22.5}] → [DataHub - OUT]
274
-
275
- ┌───────────────────────────────┐
276
- │ u-OS Data Hub (NATS) │
277
- └───────────────────────────────┘
278
- ↓ ↓ ↓
279
- [Python App] [Dashboard] [Other Node-RED]
280
- (subscribes) (subscribes) (subscribes)
282
+ [Node-RED: DataHub - OUT] → [u-OS Data Hub (NATS)]
283
+
284
+ ┌─────────────┼─────────────┐
285
+ ↓ ↓ ↓
286
+ [Other Apps] [Dashboards] [PLCs]
287
+ (subscribe) (subscribe) (subscribe)
281
288
  ```
282
289
 
283
- **All subscribers receive the update IMMEDIATELY** - no polling needed!
284
-
285
- #### Step 4: Deploy & Test
286
- 1. Connect a **Function** or **Inject** node
287
- 2. Click **Deploy**
288
- 3. Check the u-Control Web Interface → **Data Hub** → Your Provider
290
+ **All subscribers receive updates IMMEDIATELY** - no polling needed!
289
291
 
290
292
  ---
291
293
 
292
- ## Example Flow
294
+ ## DataHub - Write Node
295
+
296
+ ### Purpose
297
+ Send **write commands** to **external Data Hub providers** to change variable values. This is different from the Provider node - you're controlling **other** systems, not creating your own provider.
298
+
299
+ ### Use Cases
300
+
301
+ ✅ **Control external devices** - Toggle flags, update setpoints
302
+ ✅ **Machine control** - Start/stop operations via Data Hub
303
+ ✅ **Configuration updates** - Change parameters in other apps
304
+ ✅ **Integration scenarios** - Write to PLCs, other Node-RED instances, etc.
305
+
306
+ ### Configuration
307
+
308
+ 1. **Config Node:** Select your u-OS connection
309
+ 2. **Provider ID:** Target provider to write to (e.g., `u_os_adm`, `u_os_sbm`)
310
+ 3. **Variable ID:** Numeric ID of the variable to write
311
+
312
+ ### Finding Variable IDs
313
+
314
+ Same as Read node - check u-OS Web UI:
315
+
316
+ 1. Go to **Data Hub** → **Providers** → Select target provider
317
+ 2. Click **Variables** tab
318
+ 3. Note the **ID** column
293
319
 
320
+ ### Input Format
321
+
322
+ Send the new value as `msg.payload`:
323
+
324
+ **Toggle Boolean:**
325
+ ```javascript
326
+ msg.payload = false;
327
+ return msg;
294
328
  ```
295
- [Inject] → [DataHub - IN] → [Debug]
296
- (u_os_sbm)
297
329
 
298
- [Inject: {"temp": 22}] → [DataHub - OUT]
299
- (nodered)
330
+ **Update Number:**
331
+ ```javascript
332
+ msg.payload = 42.5;
333
+ return msg;
300
334
  ```
301
335
 
302
- **Copy this flow:**
336
+ **Change String:**
337
+ ```javascript
338
+ msg.payload = "stopped";
339
+ return msg;
340
+ ```
341
+
342
+ ### Output
343
+
344
+ Outputs confirmation when write command is sent:
345
+
303
346
  ```json
304
- [{"id":"inject1","type":"inject","name":"Trigger Read"},
305
- {"id":"datahub-in","type":"datahub-input","connection":"config1","providerId":"u_os_sbm","manualVariables":"manufacturer_name:0"},
306
- {"id":"debug1","type":"debug"}]
347
+ {
348
+ "success": true,
349
+ "providerId": "u_os_adm",
350
+ "variableId": 5,
351
+ "value": false
352
+ }
307
353
  ```
308
354
 
355
+ ### Example Flow
356
+
357
+ ```
358
+ [Inject: false] → [DataHub - Write] → [Debug]
359
+ (Provider: u_os_adm,
360
+ Variable ID: 5)
361
+ ```
362
+
363
+ ### Important Notes
364
+
365
+ ⚠️ **Requires write permissions:** OAuth client must have `hub.variables.readwrite` scope
366
+ ⚠️ **Provider must accept writes:** Some providers are read-only
367
+ ⚠️ **Variable must exist:** The variable ID must be valid
368
+
369
+ ---
370
+
371
+ ## Troubleshooting
372
+
373
+ ### No Output from IN Node
374
+
375
+ ✓ Config node deployed?
376
+ ✓ Provider ID correct? (check u-OS Web UI → Data Hub → Providers)
377
+ ✓ Variable IDs correct? (check u-OS Web UI → Variables)
378
+ ✓ Inject signal sent to input port?
379
+
380
+ ### "Variable not found" Error
381
+
382
+ - Double-check IDs in the Variables table
383
+ - Verify IDs match those in u-OS Web UI
384
+
385
+ ### Connection Test Fails
386
+
387
+ - Check Host/Port are correct and device is reachable
388
+ - Verify Client ID/Secret match exactly
389
+ - Ensure OAuth client exists in u-OS
390
+ - Verify all `hub.variables.*` scopes are granted
391
+
392
+ ### Why Manual IDs?
393
+
394
+ Auto-discovery requires special permissions on the provider definition endpoint, which are often restricted for security. The manual table works **without** this permission by querying specific IDs directly.
395
+
309
396
  ---
310
397
 
311
398
  ## FAQ
312
399
 
313
- ### Q: Why manual IDs? Can't it auto-discover?
314
- **A:** Auto-discovery requires `hub.variables.readonly` permission on the **provider definition** endpoint, which is often restricted. The manual table works **without** this permission because it queries specific IDs directly.
400
+ ### Q: Can I use the provider created by Provider node in the Read node?
401
+ **A:** **No, don't do this!** The Provider node's provider only exists while Node-RED runs. On restart, it disappears and the Read node fails. Read from **system providers** (`u_os_sbm`, `u_os_adm`) or other persistent apps instead.
402
+
403
+ ### Q: What's the difference between Provider and Write nodes?
404
+ **A:**
405
+ - **DataHub - Provider:** Creates your **own** provider. Other apps read **from** you.
406
+ - **DataHub - Write:** Sends commands **to other** providers. You write **to** them.
315
407
 
316
408
  ### Q: Where do I get Client ID/Secret?
317
- **A:** u-Control Web Interface → **System** → **Access Control** → **OAuth Clients** → **Add Client**
409
+ **A:** u-OS Web Interface → **System** → **Access Control** → **OAuth Clients** → **Add Client**
318
410
 
319
411
  ### Q: What are the required OAuth scopes?
320
412
  **A:**
321
- - **Read (IN Node):** `hub.variables.readonly`
322
- - **Write (OUT Node):** `hub.variables.provide` + `hub.variables.readwrite`
413
+ - **Read Node:** `hub.variables.readonly`
414
+ - **Provider Node:** `hub.variables.provide` + `hub.variables.readwrite`
415
+ - **Write Node:** `hub.variables.readwrite`
323
416
  - **Recommended:** Select all `hub.variables.*` scopes when creating the client
324
417
 
325
418
  ### Q: Can I use this outside the local network?
326
- **A:** Yes, if your u-Control device is reachable over the network and you configure the correct Host/Port.
419
+ **A:** Yes, if your u-OS device is reachable over the network and you configure the correct Host/Port.
327
420
 
328
421
  ### Q: Event vs Poll - which is better?
329
422
  **A:** **Event** (default) is more efficient. Use **Poll** only if you need guaranteed periodic readings regardless of value changes.
330
423
 
331
424
  ---
332
425
 
333
- ## Changelog
334
-
335
- See [GitHub Releases](https://github.com/uiff/nats-NodeRed-Node-uc20/releases)
336
-
337
- ---
338
-
339
426
  ## Support
340
427
 
341
428
  **Issues, Questions, or Feature Requests:**
342
- Contact [IoTUeli](https://www.linkedin.com/in/iotueli/) or open an issue on [GitHub](https://github.com/uiff/nats-NodeRed-Node-uc20)
429
+ Contact [IoTUeli](https://iotueli.ch) or open an issue on [GitHub](https://github.com/uiff/nats-NodeRed-Node-uc20)
343
430
 
344
431
  ---
345
432
 
package/lib/payloads.js CHANGED
@@ -17,6 +17,10 @@ import { VariableValueBoolean, VariableValueBooleanT } from './fbs/weidmueller/u
17
17
  import { TimestampT } from './fbs/weidmueller/ucontrol/hub/timestamp.js';
18
18
  import { ReadVariablesQueryResponseT } from './fbs/weidmueller/ucontrol/hub/read-variables-query-response.js';
19
19
  import { ReadVariablesQueryRequestT } from './fbs/weidmueller/ucontrol/hub/read-variables-query-request.js';
20
+ import { ReadVariablesQueryRequest } from './fbs/weidmueller/ucontrol/hub/read-variables-query-request.js';
21
+ import { ReadProviderDefinitionQueryRequest } from './fbs/weidmueller/ucontrol/hub/read-provider-definition-query-request.js';
22
+ import { WriteVariablesCommandT } from './fbs/weidmueller/ucontrol/hub/write-variables-command.js';
23
+
20
24
  const DEFAULT_QUALITY = 'GOOD';
21
25
  export function buildProviderDefinitionEvent(defs) {
22
26
  const fingerprint = computeFingerprint(defs);
@@ -44,7 +48,6 @@ export function buildReadVariablesResponse(defs, states, fingerprint) {
44
48
  return builder.asUint8Array();
45
49
  }
46
50
  // Helper to omit field if null/empty
47
- import { ReadVariablesQueryRequest } from './fbs/weidmueller/ucontrol/hub/read-variables-query-request.js';
48
51
 
49
52
  export function buildReadVariablesQuery(ids) {
50
53
  const builder = new flatbuffers.Builder(128);
@@ -64,7 +67,6 @@ export function buildReadVariablesQuery(ids) {
64
67
  return builder.asUint8Array();
65
68
  }
66
69
  // Helper to build definition query
67
- import { ReadProviderDefinitionQueryRequest } from './fbs/weidmueller/ucontrol/hub/read-provider-definition-query-request.js';
68
70
 
69
71
  export function buildReadProviderDefinitionQuery() {
70
72
  const builder = new flatbuffers.Builder(32);
@@ -73,6 +75,52 @@ export function buildReadProviderDefinitionQuery() {
73
75
  builder.finish(offset);
74
76
  return builder.asUint8Array();
75
77
  }
78
+
79
+ // Encode write variables command
80
+ export function encodeWriteVariablesCommand(variables) {
81
+ // variables: [{id: number, value: any}, ...]
82
+ const varList = new VariableListT();
83
+ varList.items = variables.map(v => {
84
+ const varT = new VariableT();
85
+ varT.id = v.id;
86
+ varT.quality = VariableQuality.GOOD;
87
+ varT.timestamp = nowNs();
88
+
89
+ // Encode value based on type
90
+ const val = v.value;
91
+ if (typeof val === 'boolean') {
92
+ const boolVal = new VariableValueBooleanT();
93
+ boolVal.value = val;
94
+ varT.value = boolVal;
95
+ varT.valueType = VariableValue.Boolean;
96
+ } else if (Number.isInteger(val)) {
97
+ const intVal = new VariableValueInt64T();
98
+ intVal.value = BigInt(val);
99
+ varT.value = intVal;
100
+ varT.valueType = VariableValue.Int64;
101
+ } else if (typeof val === 'number') {
102
+ const floatVal = new VariableValueFloat64T();
103
+ floatVal.value = val;
104
+ varT.value = floatVal;
105
+ varT.valueType = VariableValue.Float64;
106
+ } else if (typeof val === 'string') {
107
+ const strVal = new VariableValueStringT();
108
+ strVal.value = val;
109
+ varT.value = strVal;
110
+ varT.valueType = VariableValue.String;
111
+ } else {
112
+ throw new Error(`Unsupported value type: ${typeof val}`);
113
+ }
114
+
115
+ return varT;
116
+ });
117
+
118
+ const cmd = new WriteVariablesCommandT(varList);
119
+ const builder = new flatbuffers.Builder(512);
120
+ builder.finish(cmd.pack(builder));
121
+ return builder.asUint8Array();
122
+ }
123
+
76
124
  export function decodeVariableList(list) {
77
125
  if (!list)
78
126
  return [];
@@ -98,121 +146,176 @@ export function decodeVariableList(list) {
98
146
  case VariableValue.Boolean: {
99
147
  const holder = new VariableValueBoolean();
100
148
  item.value(holder);
101
- decoded = !!holder.value();
149
+ decoded = holder.value();
102
150
  break;
103
151
  }
104
- case VariableValue.String:
105
- default: {
152
+ case VariableValue.String: {
106
153
  const holder = new VariableValueString();
107
154
  item.value(holder);
108
- decoded = holder.value()?.toString() ?? '';
155
+ decoded = holder.value();
109
156
  break;
110
157
  }
158
+ default:
159
+ decoded = null;
111
160
  }
112
- const ts = item.timestamp();
113
- const seconds = ts ? Number(ts.seconds()) : 0;
114
- const nanos = ts ? ts.nanos() : 0;
161
+ const quality = decodeQuality(item.quality());
162
+ const timestampNs = decodeTimestamp(item.timestamp());
115
163
  result.push({
116
164
  id: item.id(),
117
165
  value: decoded,
118
- timestampNs: seconds * 1000000000 + nanos,
119
- quality: DEFAULT_QUALITY,
166
+ quality,
167
+ timestampNs,
120
168
  });
121
169
  }
122
170
  return result;
123
171
  }
124
- // Added to support NATS-based definition fetching
172
+ import { ProviderList } from './fbs/weidmueller/ucontrol/hub/provider-list.js';
173
+ import { ReadProvidersQueryResponse } from './fbs/weidmueller/ucontrol/hub/read-providers-query-response.js';
125
174
  import { ReadProviderDefinitionQueryResponse } from './fbs/weidmueller/ucontrol/hub/read-provider-definition-query-response.js';
126
-
127
- export function decodeProviderDefinition(buffer) {
128
- const bb = new flatbuffers.ByteBuffer(buffer);
129
- const response = ReadProviderDefinitionQueryResponse.getRootAsReadProviderDefinitionQueryResponse(bb);
130
- const providerDef = response.providerDefinition();
131
-
132
- if (!providerDef) return [];
133
-
175
+ import { VariableList } from './fbs/weidmueller/ucontrol/hub/variable-list.js';
176
+ export function decodeProviderList(bb) {
177
+ const response = ReadProvidersQueryResponse.getRootAsReadProvidersQueryResponse(bb);
178
+ const list = response.providers();
179
+ if (!list)
180
+ return [];
134
181
  const result = [];
135
- const len = providerDef.variableDefinitionsLength();
136
- for (let i = 0; i < len; i++) {
137
- const item = providerDef.variableDefinitions(i);
138
- if (!item) continue;
139
-
140
- // Inverse mapping of DataType/AccessType needed if we want full fidelity,
141
- // but for now we mainly need ID and Key.
142
- result.push({
143
- id: item.id(),
144
- key: item.key(),
145
- dataType: 'UNKNOWN', // Mapping back from Enum to String if strictly needed
146
- access: item.accessType() === VariableAccessType.READ_WRITE ? 'READ_WRITE' : 'READ_ONLY'
147
- });
182
+ for (let i = 0; i < list.itemsLength(); i += 1) {
183
+ const prov = list.items(i);
184
+ if (!prov)
185
+ continue;
186
+ result.push({ id: prov.id() || '' });
148
187
  }
149
188
  return result;
150
189
  }
151
-
152
- function toFlatDefinition(def) {
153
- const result = new VariableDefinitionT();
154
- result.id = def.id;
155
- result.key = def.key;
156
- result.experimental = Boolean(def.experimental);
157
- result.dataType = mapDataType(def.dataType);
158
- result.accessType = def.access === 'READ_WRITE' ? VariableAccessType.READ_WRITE : VariableAccessType.READ_ONLY;
159
- return result;
160
- }
161
- function buildVariableList(defs, states, fingerprint) {
162
- const statesMap = new Map(states.map((s) => [s.id, s]));
163
- const items = [];
164
- for (const def of defs) {
165
- const state = statesMap.get(def.id);
166
- if (!state)
190
+ export function decodeProviderDefinition(bb) {
191
+ const response = ReadProviderDefinitionQueryResponse.getRootAsReadProviderDefinitionQueryResponse(bb);
192
+ const def = response.providerDefinition();
193
+ if (!def)
194
+ return null;
195
+ const variableDefs = [];
196
+ for (let i = 0; i < def.variableDefinitionsLength(); i += 1) {
197
+ const varDef = def.variableDefinitions(i);
198
+ if (!varDef)
167
199
  continue;
168
- const variable = new VariableT();
169
- variable.id = def.id;
170
- const [valueType, value] = toValueUnion(def.dataType, state.value);
171
- variable.valueType = valueType;
172
- variable.value = value;
173
- variable.timestamp = toTimestamp(state.timestampNs);
174
- variable.quality = VariableQuality.GOOD;
175
- items.push(variable);
200
+ variableDefs.push({
201
+ id: varDef.id(),
202
+ key: varDef.key() || '',
203
+ dataType: decodeDataType(varDef.dataType()),
204
+ access: decodeAccessType(varDef.accessType()),
205
+ });
176
206
  }
177
- const list = new VariableListT();
178
- list.providerDefinitionFingerprint = fingerprint;
179
- list.baseTimestamp = items.length ? (items[0].timestamp ?? toTimestamp(Date.now() * 1000000)) : toTimestamp(Date.now() * 1000000);
180
- list.items = items;
181
- return list;
207
+ return {
208
+ fingerprint: def.fingerprint(),
209
+ variables: variableDefs,
210
+ };
182
211
  }
183
- function toTimestamp(ns) {
184
- const ts = new TimestampT();
185
- ts.seconds = BigInt(Math.floor(ns / 1000000000));
186
- ts.nanos = ns % 1000000000;
187
- return ts;
212
+ function buildVariableList(defs, states, fingerprint) {
213
+ const variables = defs.map((def) => encodeVariableState(def, states[def.id]));
214
+ const list = new VariableListT(fingerprint, variables);
215
+ return list;
188
216
  }
189
- function toValueUnion(type, value) {
190
- switch (type) {
217
+ function encodeVariableState(def, state) {
218
+ const varT = new VariableT();
219
+ varT.id = def.id;
220
+ varT.quality = VariableQuality[state?.quality?.toUpperCase?.()] ?? VariableQuality[DEFAULT_QUALITY];
221
+ varT.timestamp = state?.timestamp !== undefined ? new TimestampT(BigInt(state.timestamp)) : nowNs();
222
+ const value = state?.value;
223
+ switch (def.dataType) {
191
224
  case 'INT64': {
192
- const holder = new VariableValueInt64T();
193
- holder.value = BigInt(Math.trunc(Number(value)));
194
- return [VariableValue.Int64, holder];
225
+ const intVal = new VariableValueInt64T();
226
+ intVal.value = (value !== null && value !== undefined) ? BigInt(value) : BigInt(0);
227
+ varT.value = intVal;
228
+ varT.valueType = VariableValue.Int64;
229
+ break;
195
230
  }
196
231
  case 'FLOAT64': {
197
- const holder = new VariableValueFloat64T();
198
- holder.value = Number(value);
199
- return [VariableValue.Float64, holder];
232
+ const floatVal = new VariableValueFloat64T();
233
+ floatVal.value = (value !== null && value !== undefined) ? Number(value) : 0.0;
234
+ varT.value = floatVal;
235
+ varT.valueType = VariableValue.Float64;
236
+ break;
200
237
  }
201
238
  case 'BOOLEAN': {
202
- const holder = new VariableValueBooleanT();
203
- holder.value = Boolean(value);
204
- return [VariableValue.Boolean, holder];
239
+ const boolVal = new VariableValueBooleanT();
240
+ boolVal.value = (value !== null && value !== undefined) ? Boolean(value) : false;
241
+ varT.value = boolVal;
242
+ varT.valueType = VariableValue.Boolean;
243
+ break;
205
244
  }
206
245
  case 'STRING':
207
246
  default: {
208
- const holder = new VariableValueStringT();
209
- holder.value = String(value);
210
- return [VariableValue.String, holder];
247
+ const strVal = new VariableValueStringT();
248
+ strVal.value = (value !== null && value !== undefined) ? String(value) : '';
249
+ varT.value = strVal;
250
+ varT.valueType = VariableValue.String;
251
+ break;
211
252
  }
212
253
  }
254
+ return varT;
255
+ }
256
+ function nowNs() {
257
+ return new TimestampT(BigInt(Date.now() * 1_000_000));
258
+ }
259
+ function decodeTimestamp(ts) {
260
+ return ts ? Number(ts.value()) : 0;
261
+ }
262
+ function decodeDataType(dt) {
263
+ switch (dt) {
264
+ case VariableDataType.INT64:
265
+ return 'INT64';
266
+ case VariableDataType.FLOAT64:
267
+ return 'FLOAT64';
268
+ case VariableDataType.BOOLEAN:
269
+ return 'BOOLEAN';
270
+ case VariableDataType.STRING:
271
+ default:
272
+ return 'STRING';
273
+ }
274
+ }
275
+ function decodeAccessType(dt) {
276
+ switch (dt) {
277
+ case VariableAccessType.READWRITE:
278
+ return 'READWRITE';
279
+ case VariableAccessType.READONLY:
280
+ default:
281
+ return 'READONLY';
282
+ }
283
+ }
284
+ function decodeQuality(q) {
285
+ switch (q) {
286
+ case VariableQuality.BAD:
287
+ return 'BAD';
288
+ case VariableQuality.UNCERTAIN:
289
+ return 'UNCERTAIN';
290
+ case VariableQuality.GOOD_LOCAL_OVERRIDE:
291
+ return 'GOOD_LOCAL_OVERRIDE';
292
+ case VariableQuality.GOOD:
293
+ default:
294
+ return 'GOOD';
295
+ }
296
+ }
297
+ function toFlatDefinition(def) {
298
+ const varDef = new VariableDefinitionT();
299
+ varDef.id = def.id;
300
+ varDef.key = def.key;
301
+ varDef.dataType = mapDataType(def.dataType);
302
+ varDef.accessType = mapAccessType(def.access);
303
+ if (def.experimental !== undefined && def.experimental !== null) {
304
+ varDef.experimental = Boolean(def.experimental);
305
+ }
306
+ return varDef;
307
+ }
308
+ function mapAccessType(access) {
309
+ switch (access?.toUpperCase?.()) {
310
+ case 'READWRITE':
311
+ return VariableAccessType.READWRITE;
312
+ case 'READONLY':
313
+ default:
314
+ return VariableAccessType.READONLY;
315
+ }
213
316
  }
214
- function mapDataType(type) {
215
- switch (type) {
317
+ function mapDataType(dataType) {
318
+ switch (dataType?.toUpperCase?.()) {
216
319
  case 'INT64':
217
320
  return VariableDataType.INT64;
218
321
  case 'FLOAT64':
@@ -17,7 +17,7 @@
17
17
  if (this.name) return this.name;
18
18
  return "DataHub - IN";
19
19
  },
20
- paletteLabel: "DataHub - IN",
20
+ paletteLabel: "DataHub - Read",
21
21
  oneditprepare: function () {
22
22
  const node = this;
23
23
 
@@ -131,7 +131,7 @@
131
131
  </script>
132
132
 
133
133
  <script type="text/html" data-help-name="datahub-input">
134
- <p>Reads variables from the u-OS Data Hub via NATS and outputs their values as JSON messages.</p>
134
+ <p><b>DataHub - Read</b> reads variables from u-OS Data Hub providers and outputs their values as JSON messages.</p>
135
135
 
136
136
  <h3>Quick Start</h3>
137
137
  <ol>
@@ -14,7 +14,7 @@
14
14
  label: function () {
15
15
  return this.name || `DataHub - OUT ${this.providerId || ''}`;
16
16
  },
17
- paletteLabel: "DataHub - OUT",
17
+ paletteLabel: "DataHub - Provider",
18
18
  labelStyle: function () {
19
19
  return this.name ? 'node_label_italic' : '';
20
20
  }
@@ -37,7 +37,7 @@
37
37
  </script>
38
38
 
39
39
  <script type="text/html" data-help-name="datahub-output">
40
- <p><strong>DataHub Output</strong> registers (if needed) and writes variables for the provider you specify.</p>
40
+ <p><b>DataHub - Provider</b> creates your own Data Hub provider that other applications can read from in real-time.</p>
41
41
  <dl>
42
42
  <dt>u-OS Config</dt>
43
43
  <dd>Connection node with host/port and OAuth credentials.</dd>
@@ -0,0 +1,127 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('datahub-write', {
3
+ category: 'u-OS DataHub NATS',
4
+ color: '#e74c3c',
5
+ defaults: {
6
+ name: { value: "" },
7
+ connection: { type: "uos-config", required: true },
8
+ providerId: { value: "", required: true },
9
+ variableId: { value: "", required: false },
10
+ variableName: { value: "", required: false }
11
+ },
12
+ inputs: 1,
13
+ outputs: 1,
14
+ icon: "white/datahub-output.svg",
15
+ label: function () {
16
+ if (this.name) return this.name;
17
+ return `Write → ${this.providerId || 'provider'}`;
18
+ },
19
+ paletteLabel: "DataHub - Write",
20
+ oneditprepare: function () {
21
+ // Nothing special needed for now
22
+ }
23
+ });
24
+ </script>
25
+
26
+ <script type="text/html" data-template-name="datahub-write">
27
+ <div class="form-row">
28
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
29
+ <input type="text" id="node-input-name" placeholder="Optional">
30
+ </div>
31
+
32
+ <div class="form-row">
33
+ <label for="node-input-connection"><i class="fa fa-globe"></i> Config</label>
34
+ <input type="text" id="node-input-connection">
35
+ </div>
36
+
37
+ <div class="form-row">
38
+ <label for="node-input-providerId"><i class="fa fa-server"></i> Provider ID</label>
39
+ <input type="text" id="node-input-providerId" placeholder="e.g. u_os_adm">
40
+ </div>
41
+
42
+ <div class="form-row">
43
+ <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">
45
+ </div>
46
+
47
+ <div class="form-tips">
48
+ <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
+ </div>
50
+ </script>
51
+
52
+ <script type="text/html" data-help-name="datahub-write">
53
+ <p><b>DataHub - Write</b> sends write commands to external Data Hub providers to change variable values.</p>
54
+
55
+ <h3>Purpose</h3>
56
+ <p>Control variables in <b>other providers</b> (not your own). Use this to:</p>
57
+ <ul>
58
+ <li>Toggle flags (e.g., <code>enable_mode = true</code>)</li>
59
+ <li>Update setpoints (e.g., <code>target_temp = 25.5</code>)</li>
60
+ <li>Control external devices via Data Hub</li>
61
+ </ul>
62
+
63
+ <h3>Configuration</h3>
64
+
65
+ <h4>Provider ID</h4>
66
+ <p>The target provider to write to (e.g., <code>u_os_adm</code>, <code>u_os_sbm</code>).</p>
67
+ <p><b>Find it:</b> u-OS Web UI → Data Hub → Providers</p>
68
+
69
+ <h4>Variable ID</h4>
70
+ <p>The numeric ID of the variable to write.</p>
71
+ <p><b>Find it:</b> u-OS Web UI → Data Hub → Providers → (Select Provider) → Variables tab</p>
72
+
73
+ <h3>Input Format</h3>
74
+ <p>Send the new value as <code>msg.payload</code>:</p>
75
+
76
+ <h4>Example: Toggle Boolean</h4>
77
+ <pre><code>msg.payload = false;
78
+ return msg;</code></pre>
79
+
80
+ <h4>Example: Update Number</h4>
81
+ <pre><code>msg.payload = 42.5;
82
+ return msg;</code></pre>
83
+
84
+ <h4>Example: Change String</h4>
85
+ <pre><code>msg.payload = "stopped";
86
+ return msg;</code></pre>
87
+
88
+ <h3>Output</h3>
89
+ <p>Outputs a confirmation message when the write command is sent:</p>
90
+ <pre><code>{
91
+ "success": true,
92
+ "providerId": "u_os_adm",
93
+ "variableId": 5,
94
+ "value": false
95
+ }</code></pre>
96
+
97
+ <h3>Example Flow</h3>
98
+ <pre><code>[Inject: false] → [DataHub - Write] → [Debug]
99
+ (Provider: u_os_adm, Variable ID: 5)</code></pre>
100
+
101
+ <h3>Important Notes</h3>
102
+ <ul>
103
+ <li>⚠️ <b>Requires write permissions:</b> Your OAuth client must have <code>hub.variables.readwrite</code> scope</li>
104
+ <li>⚠️ <b>Provider must accept writes:</b> Some providers are read-only</li>
105
+ <li>⚠️ <b>Variable must exist:</b> The variable ID must be valid in the target provider</li>
106
+ </ul>
107
+
108
+ <h3>Troubleshooting</h3>
109
+
110
+ <h4>No Response / Error</h4>
111
+ <ul>
112
+ <li>Check Provider ID is correct (u-OS Web UI → Data Hub → Providers)</li>
113
+ <li>Verify Variable ID exists (Providers → Variables tab)</li>
114
+ <li>Ensure OAuth client has <code>hub.variables.readwrite</code> scope</li>
115
+ </ul>
116
+
117
+ <h4>Permission Denied</h4>
118
+ <ul>
119
+ <li>Check OAuth client scopes in u-OS (System → Access Control → OAuth Clients)</li>
120
+ <li>Verify provider allows writes (some system providers are read-only)</li>
121
+ </ul>
122
+
123
+ <div style="margin-top:15px; padding-top:10px; border-top:1px solid #ddd; font-size:0.85em; color:#666;">
124
+ <p><b>Developed by <a href="https://iotueli.ch" target="_blank">IoTUeli</a></b></p>
125
+ <p>Not an official Weidmüller product. Community contribution.</p>
126
+ </div>
127
+ </script>
@@ -0,0 +1,87 @@
1
+ import { encodeWriteVariablesCommand } from '../lib/payloads.js';
2
+
3
+ export default function (RED) {
4
+ function DataHubWriteNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+
8
+ // Get config node
9
+ const configNode = RED.nodes.getNode(config.connection);
10
+ if (!configNode) {
11
+ node.error('Missing u-OS config');
12
+ node.status({ fill: 'red', shape: 'dot', text: 'no config' });
13
+ return;
14
+ }
15
+
16
+ // Store configuration
17
+ this.providerId = config.providerId?.trim();
18
+ this.variableId = config.variableId ? parseInt(config.variableId, 10) : null;
19
+
20
+ if (!this.providerId) {
21
+ node.error('Provider ID is required');
22
+ node.status({ fill: 'red', shape: 'dot', text: 'no provider ID' });
23
+ return;
24
+ }
25
+
26
+ if (this.variableId === null || isNaN(this.variableId)) {
27
+ node.error('Variable ID is required and must be a number');
28
+ node.status({ fill: 'red', shape: 'dot', text: 'invalid variable ID' });
29
+ return;
30
+ }
31
+
32
+ node.status({ fill: 'green', shape: 'ring', text: 'ready' });
33
+
34
+ // Handle incoming messages
35
+ node.on('input', async function (msg) {
36
+ const value = msg.payload;
37
+
38
+ if (value === undefined || value === null) {
39
+ node.warn('msg.payload is empty, nothing to write');
40
+ return;
41
+ }
42
+
43
+ try {
44
+ // Get NATS connection from config node
45
+ const nc = await configNode.getNatsConnection();
46
+ if (!nc) {
47
+ node.error('NATS connection not available');
48
+ node.status({ fill: 'red', shape: 'dot', text: 'no connection' });
49
+ return;
50
+ }
51
+
52
+ // Build write command
53
+ const writeCommand = encodeWriteVariablesCommand([
54
+ {
55
+ id: node.variableId,
56
+ value: value
57
+ }
58
+ ]);
59
+
60
+ // Publish write command
61
+ const subject = `v1.loc.${node.providerId}.vars.cmd.write`;
62
+ nc.publish(subject, writeCommand);
63
+
64
+ node.status({ fill: 'green', shape: 'dot', text: `wrote: ${value}` });
65
+
66
+ // Output confirmation
67
+ msg.payload = {
68
+ success: true,
69
+ providerId: node.providerId,
70
+ variableId: node.variableId,
71
+ value: value
72
+ };
73
+ node.send(msg);
74
+
75
+ } catch (err) {
76
+ node.error(`Write failed: ${err.message}`, msg);
77
+ node.status({ fill: 'red', shape: 'dot', text: 'write error' });
78
+ }
79
+ });
80
+
81
+ node.on('close', function () {
82
+ node.status({});
83
+ });
84
+ }
85
+
86
+ RED.nodes.registerType('datahub-write', DataHubWriteNode);
87
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.1.65",
4
- "description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read and write variables via NATS protocol with OAuth2 authentication. Supports event-based subscriptions and manual variable mapping.",
3
+ "version": "0.1.67",
4
+ "description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read, write, and provide variables via NATS protocol with OAuth2 authentication. Supports event-based subscriptions and manual variable mapping.",
5
5
  "author": {
6
6
  "name": "IoTUeli",
7
7
  "url": "https://www.linkedin.com/in/iotueli/"
@@ -31,7 +31,10 @@
31
31
  "plc",
32
32
  "variables",
33
33
  "oauth2",
34
- "flatbuffers"
34
+ "flatbuffers",
35
+ "write",
36
+ "read",
37
+ "provider"
35
38
  ],
36
39
  "engines": {
37
40
  "node": ">=14.0.0"
@@ -46,7 +49,8 @@
46
49
  "nodes": {
47
50
  "uos-config": "nodes/uos-config.js",
48
51
  "datahub-input": "nodes/datahub-input.js",
49
- "datahub-output": "nodes/datahub-output.js"
52
+ "datahub-output": "nodes/datahub-output.js",
53
+ "datahub-write": "nodes/datahub-write.js"
50
54
  }
51
55
  }
52
56
  }