node-red-contrib-uos-nats 0.1.8 → 0.1.10

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
@@ -31,38 +31,38 @@ Fields:
31
31
  - **Host / Port** – IP address of your controller (e.g. `192.168.10.100`) and the NATS port `49360`.
32
32
  - **Client Name** – used for the NATS inbox prefix (`_INBOX.<name>`).
33
33
  - **Client ID / Secret** – OAuth2 client credentials created in the Control Center.
34
- - **Scope** – fixed to `hub.variables.provide hub.variables.readwrite hub.variables.readonly` so the nodes can register providers and query the REST metadata; it is not editable in the UI.
34
+ - **Scope** – defaults to `hub.variables.provide hub.variables.readwrite hub.variables.readonly`. Note: The API does not require a specific "read providers" scope; listing providers is covered by the standard variable scopes.
35
35
  - **Granted scopes** – click *Refresh* to query the token endpoint and show the scopes currently granted to that client.
36
36
 
37
- The config node automatically fetches tokens via Client Credentials flow and exposes helper endpoints so other nodes can list providers and variables. The token endpoint is derived from the configured host (`https://<host>/oauth2/token`), so there is no additional field to maintain.
37
+ The config node automatically fetches tokens via Client Credentials flow.
38
38
 
39
39
  ## DataHub Input Node
40
40
 
41
- - Select the u-OS config node, then choose one of the discovered providers from the dropdown (the node queries `/datahub/v1/providers` for you). If your OAuth client lacks read-only scope, the dropdown is disabled and you can type the provider ID manually.
42
- - Pick the variables you need from the multi-select list. Leave it empty to receive all variables from the provider. (When manual provider input is used the list may stay empty, because it also requires the read-only permission.)
43
- - The node outputs messages with the structure:
41
+ - Select the u-OS config node, then choose one of the discovered providers from the dropdown.
42
+ - **Troubleshooting**: If the dropdown remains empty, check the Node-RED debug tab. The node logs the API response count. Ensure your OAuth client has `hub.variables.readonly` permission.
43
+ - Pick the variables you need from the multi-select list. Leave it empty to receive all variables.
44
+
45
+ ## DataHub Output Node
46
+
47
+ - Reuses the u-OS config node. The provider ID defaults to `nodered` and is created automatically upon the first message.
48
+ - Send a JSON object to the input pin. **Nested objects are supported** and create subcategories automatically:
44
49
  ```json
45
50
  {
46
- "type": "snapshot" | "change",
47
- "variables": [
48
- {
49
- "providerId": "sampleprovider",
50
- "id": 5,
51
- "key": "diagnostics.status_text",
52
- "value": "running",
53
- "quality": "GOOD",
54
- "timestampNs": 1700000000000
51
+ "machine": {
52
+ "temperature": 45.2,
53
+ "status": {
54
+ "active": true,
55
+ "mode": "remote"
55
56
  }
56
- ]
57
+ }
57
58
  }
58
59
  ```
59
- - Deploy multiple nodes if you want to monitor different providers.
60
-
61
- ## DataHub Output Node
60
+ This creates/updates the following variables:
61
+ - `machine.temperature` (FLOAT64)
62
+ - `machine.status.active` (BOOLEAN)
63
+ - `machine.status.mode` (STRING)
62
64
 
63
- - Reuses the u-OS config node. The provider ID defaults to `nodered` and is created automatically.
64
- - Send a JSON object to the input pin. Nested objects become dot-separated keys (e.g. `{ "line1": { "status": "ok" } }` ⇒ `line1.status`).
65
- - The node infers data types (INT64/FLOAT64/BOOLEAN/STRING) and publishes `VariablesChangedEvent`s. New keys trigger an automatic provider definition update.
65
+ - The node infers data types (INT64/FLOAT64/BOOLEAN/STRING) and automatically publishes definition updates when new keys are seen.
66
66
  - Read requests (`v1.loc.<provider>.vars.qry.read`) are answered using the most recent values, so other consumers can subscribe to your Node-RED provider.
67
67
 
68
68
  ## Example Flow
@@ -11,7 +11,7 @@
11
11
  inputs: 0,
12
12
  outputs: 1,
13
13
  icon: "white/datahub-input.svg",
14
- oneditprepare: function() {
14
+ oneditprepare: function () {
15
15
  const node = this;
16
16
  const $config = $('#node-input-connection');
17
17
  const $providerHidden = $('#node-input-providerId');
@@ -29,6 +29,7 @@
29
29
  .filter((entry) => entry.length > 0);
30
30
 
31
31
  const normalizeProviders = (payload) => {
32
+ console.log('normalizeProviders payload:', payload);
32
33
  if (!payload) {
33
34
  return [];
34
35
  }
@@ -42,6 +43,7 @@
42
43
  };
43
44
 
44
45
  const normalizeVariables = (payload) => {
46
+ console.log('normalizeVariables payload:', payload);
45
47
  if (!payload) {
46
48
  return [];
47
49
  }
@@ -54,8 +56,11 @@
54
56
  return [];
55
57
  };
56
58
 
57
- const providerLabel = (provider) => provider.name || provider.displayName || provider.providerId || provider.id || 'Unnamed provider';
58
- const providerValue = (provider) => provider.providerId || provider.id || provider.name || '';
59
+ // API returns [{ "id": "..." }] for providers
60
+ const providerLabel = (provider) => provider.displayName || provider.name || provider.id || provider.providerId || 'Unnamed provider';
61
+ const providerValue = (provider) => provider.id || provider.providerId || provider.name || '';
62
+
63
+ // API returns [{ "key": "..." }] for variables
59
64
  const variableValue = (variable) => variable.key || variable.name || variable.path || variable.id || '';
60
65
  const variableLabel = (variable) => variable.tagName || variable.key || variable.name || variable.path || variable.id || 'Unnamed variable';
61
66
 
@@ -130,6 +135,7 @@
130
135
  setVariableStatus('Select one or multiple variables (Ctrl/Cmd + click). Leave empty to receive all.');
131
136
  })
132
137
  .fail((xhr) => {
138
+ console.error('Variable load failed', xhr);
133
139
  const msg = xhr?.responseJSON?.error || xhr.statusText || 'Failed to load variables.';
134
140
  setVariableStatus(msg);
135
141
  $variables.prop('disabled', true);
@@ -191,6 +197,7 @@
191
197
  loadVariables(selected);
192
198
  })
193
199
  .fail((xhr) => {
200
+ console.error('Provider load failed', xhr);
194
201
  const msg = xhr?.responseJSON?.error || xhr.statusText || 'Failed to load providers.';
195
202
  showManualProvider(`Provider list failed (${msg}). Enter the provider ID manually or grant hub.variables.readonly.`);
196
203
  });
@@ -286,4 +293,4 @@
286
293
  { "name": "diagnostics.status_text", "value": "ready" }
287
294
  ]
288
295
  }</pre>
289
- </script>
296
+ </script>
@@ -10,7 +10,7 @@
10
10
  inputs: 1,
11
11
  outputs: 1,
12
12
  icon: "white/datahub-output.svg",
13
- oneditprepare: function() {},
13
+ oneditprepare: function () { },
14
14
  label: function () {
15
15
  return this.name || `Output ${this.providerId || ''}`;
16
16
  },
@@ -43,12 +43,14 @@
43
43
  <dt>Provider ID</dt>
44
44
  <dd>Defaults to <code>nodered</code>; change it if you want to publish under another provider name.</dd>
45
45
  </dl>
46
- <p>Send a JSON object as <code>msg.payload</code>. Nested objects are flattened using dot-notation:</p>
46
+ <p>Send a JSON object as <code>msg.payload</code>. Nested objects are flattened using dot-notation to create <strong>subcategories</strong> automatically:</p>
47
47
  <pre>{
48
- "diagnostics": {
49
- "status_text": "running",
50
- "error_count": 0
48
+ "machine": {
49
+ "status": "running",
50
+ "details": {
51
+ "temp": 45.2
52
+ }
51
53
  }
52
54
  }</pre>
53
- <p>becomes variables <code>diagnostics.status_text</code> and <code>diagnostics.error_count</code> in the Data Hub.</p>
54
- </script>
55
+ <p>becomes variables <code>machine.status</code> and <code>machine.details.temp</code> in the Data Hub.</p>
56
+ </script>
@@ -35,12 +35,16 @@ const flattenPayload = (value, prefix = '') => {
35
35
  const path = (key) => (prefix ? `${prefix}.${key}` : key);
36
36
  if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
37
37
  Object.entries(value).forEach(([key, val]) => {
38
- entries.push(...flattenPayload(val, path(key)));
38
+ if (val !== undefined) {
39
+ entries.push(...flattenPayload(val, path(key)));
40
+ }
39
41
  });
40
42
  }
41
43
  else if (Array.isArray(value)) {
42
44
  value.forEach((val, idx) => {
43
- entries.push(...flattenPayload(val, prefix ? `${prefix}[${idx}]` : `[${idx}]`));
45
+ if (val !== undefined) {
46
+ entries.push(...flattenPayload(val, prefix ? `${prefix}[${idx}]` : `[${idx}]`));
47
+ }
44
48
  });
45
49
  }
46
50
  else {
@@ -129,14 +133,21 @@ module.exports = function (RED) {
129
133
  return;
130
134
  }
131
135
  const entries = flattenPayload(msg.payload);
136
+
137
+ // Optimization: If payload is empty after flattening (e.g. only undefined values), stop here
132
138
  if (!entries.length) {
133
139
  done();
134
140
  return;
135
141
  }
142
+
136
143
  const [payloadsMod, subjectsMod] = await loadModules();
137
144
  let definitionsChanged = false;
138
145
  const states = [];
146
+
139
147
  entries.forEach(({ key, value }) => {
148
+ // Ensure we don't accidentally send undefined/null as value if logic slipped through
149
+ if (value === undefined || value === null) return;
150
+
140
151
  const { def, created } = ensureDefinition(key, inferType(value));
141
152
  if (created) {
142
153
  definitionsChanged = true;
@@ -1,6 +1,6 @@
1
1
  const fetch = require('node-fetch');
2
2
  const { connect } = require('nats');
3
- const DEFAULT_SCOPE = 'hub.variables.provide hub.variables.readwrite hub.variables.readonly';
3
+ const DEFAULT_SCOPE = 'hub.variables.provide hub.variables.readwrite hub.variables.readonly'; // hub.providers.read removed as it does not exist
4
4
 
5
5
  if (!process.env.NODE_TLS_REJECT_UNAUTHORIZED) {
6
6
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
@@ -98,7 +98,9 @@ module.exports = function (RED) {
98
98
  if (!res.ok) {
99
99
  throw new Error(`Provider list failed: ${res.status}`);
100
100
  }
101
- return res.json();
101
+ const json = await res.json();
102
+ this.log(`Fetched ${Array.isArray(json) ? json.length : 'unknown'} providers from API`);
103
+ return json;
102
104
  };
103
105
 
104
106
  this.fetchProviderVariables = async (providerId) => {
@@ -112,7 +114,9 @@ module.exports = function (RED) {
112
114
  if (!res.ok) {
113
115
  throw new Error(`Variable list failed: ${res.status}`);
114
116
  }
115
- return res.json();
117
+ const json = await res.json();
118
+ this.log(`Fetched ${Array.isArray(json) ? json.length : 'unknown'} variables via API for provider ${providerId}`);
119
+ return json;
116
120
  };
117
121
 
118
122
  this.acquire = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Node-RED nodes for u-OS Data Hub via NATS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,4 +31,4 @@
31
31
  "datahub-output": "nodes/datahub-output.js"
32
32
  }
33
33
  }
34
- }
34
+ }