@bldgblocks/node-red-contrib-control 0.1.31 → 0.1.32

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.
@@ -12,7 +12,6 @@
12
12
  </button>
13
13
  </div>
14
14
 
15
- <!-- NEW: Trigger Mode Selection -->
16
15
  <div class="form-row">
17
16
  <label for="node-input-updates"><i class="fa fa-bolt"></i> Trigger</label>
18
17
  <select id="node-input-updates" style="width: 70%;">
@@ -26,6 +25,14 @@
26
25
  <input type="text" id="node-input-outputProperty" placeholder="payload" style="width:70%;">
27
26
  </div>
28
27
 
28
+ <div class="form-row">
29
+ <label for="node-input-detail"><i class="fa fa-bolt"></i> Trigger</label>
30
+ <select id="node-input-detail" style="width: 70%;">
31
+ <option value="getValue">Get Simple Value</option>
32
+ <option value="getObject">Get Full Object</option>
33
+ </select>
34
+ </div>
35
+
29
36
  <div class="form-tips">
30
37
  <b>Note:</b> Targeting by Node ID allows the source path to change without breaking this link.
31
38
  </div>
@@ -39,7 +46,8 @@
39
46
  name: { value: "" },
40
47
  targetNode: { value: "", required: true },
41
48
  outputProperty: { value: "payload", required: true },
42
- updates: { value: "always", required: true }
49
+ updates: { value: "always", required: true },
50
+ detail: {value: "getObject", required: true }
43
51
  },
44
52
  inputs: 1,
45
53
  outputs: 1,
@@ -5,14 +5,20 @@ module.exports = function(RED) {
5
5
  node.targetNodeId = config.targetNode;
6
6
  node.outputProperty = config.outputProperty || "payload";
7
7
  node.updates = config.updates;
8
+ node.detail = config.detail;
8
9
 
9
- const setterNode = RED.nodes.getNode(node.targetNodeId);
10
+ let setterNode = null;
11
+ let retryInterval = null;
12
+ let updateListener = null;
13
+ let retryCount = 0;
14
+ const retryDelays = [0, 100, 500, 1000, 2000, 4000, 8000, 16000];
15
+ const maxRetries = retryDelays.length - 1;
10
16
 
11
17
  // --- HELPER: Process Wrapper and Send Msg ---
12
18
  function sendValue(storedObject, msgToReuse) {
13
19
  const msg = msgToReuse || {};
14
20
 
15
- if (storedObject !== undefined) {
21
+ if (storedObject !== undefined && storedObject !== null) {
16
22
 
17
23
  // CHECK: Is this our Wrapper Format? (Created by Global Setter)
18
24
  if (storedObject && typeof storedObject === 'object' && storedObject.hasOwnProperty('value')) {
@@ -26,7 +32,9 @@ module.exports = function(RED) {
26
32
 
27
33
  // 3. Merge all attributes onto the msg root
28
34
  // This automatically handles priority, units, metadata, and any future fields
29
- Object.assign(msg, attributes);
35
+ if (node.detail === "getObject") {
36
+ Object.assign(msg, attributes);
37
+ }
30
38
 
31
39
  } else {
32
40
  // Handle Legacy/Raw values (not created by your Setter)
@@ -36,7 +44,7 @@ module.exports = function(RED) {
36
44
 
37
45
  // Visual Status
38
46
  const valDisplay = RED.util.getMessageProperty(msg, node.outputProperty);
39
- node.status({ fill: "blue", shape: "dot", text: `Get: ${valDisplay}` });
47
+ node.status({ fill: "blue", shape: "dot", text: `get: ${valDisplay}` });
40
48
 
41
49
  node.send(msg);
42
50
 
@@ -45,7 +53,37 @@ module.exports = function(RED) {
45
53
  }
46
54
  }
47
55
 
48
- // --- 1. HANDLE MANUAL INPUT ---
56
+ // --- HELPER: Manage Event Subscription ---
57
+ function establishListener() {
58
+ setterNode = RED.nodes.getNode(node.targetNodeId);
59
+
60
+ if (setterNode && setterNode.varName && node.updates === 'always') {
61
+ if (updateListener) {
62
+ // Remove existing listener if we're retrying
63
+ RED.events.removeListener("bldgblocks-global-update", updateListener);
64
+ }
65
+
66
+ updateListener = function(evt) {
67
+ if (evt.key === setterNode.varName && evt.store === setterNode.storeName) {
68
+ sendValue(evt.data, {});
69
+ }
70
+ };
71
+
72
+ RED.events.on("bldgblocks-global-update", updateListener);
73
+
74
+ // Clear retry interval once successful
75
+ if (retryInterval) {
76
+ clearInterval(retryInterval);
77
+ retryInterval = null;
78
+ }
79
+
80
+ node.status({ fill: "green", shape: "dot", text: "Connected" });
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ // --- HANDLE MANUAL INPUT ---
49
87
  node.on('input', function(msg, send, done) {
50
88
  send = send || function() { node.send.apply(node, arguments); };
51
89
 
@@ -61,24 +99,40 @@ module.exports = function(RED) {
61
99
  if (done) done();
62
100
  });
63
101
 
64
- // --- 2. HANDLE REACTIVE UPDATES ---
65
- let updateListener = null;
66
-
67
- if (node.updates === 'always' && setterNode && setterNode.varName) {
68
- updateListener = function(evt) {
69
- if (evt.key === setterNode.varName && evt.store === setterNode.storeName) {
70
- // Pass data directly from event
71
- sendValue(evt.data, {});
72
- }
73
- };
74
- RED.events.on("bldgblocks-global-update", updateListener);
102
+ // --- HANDLE REACTIVE UPDATES ---
103
+ if (node.updates === 'always') {
104
+ if (!establishListener()) {
105
+ // Recursive retry
106
+ const retry = () => {
107
+ if (retryCount >= maxRetries) {
108
+ node.error("Failed to connect to setter node after multiple attempts");
109
+ node.status({ fill: "red", shape: "ring", text: "Connection failed" });
110
+ return;
111
+ }
112
+
113
+ if (establishListener()) {
114
+ retryCount = 0;
115
+ return; // Success
116
+ }
117
+
118
+ retryCount++;
119
+ setTimeout(retry, retryDelays[Math.min(retryCount, maxRetries - 1)]);
120
+ };
121
+
122
+ // Try immediately
123
+ setTimeout(retry, retryDelays[0]);
124
+ }
75
125
  }
76
126
 
77
127
  // --- CLEANUP ---
78
- node.on('close', function() {
79
- if (updateListener) {
128
+ node.on('close', function(removed, done) {
129
+ if (retryInterval) {
130
+ clearInterval(retryInterval);
131
+ }
132
+ if (removed && updateListener) {
80
133
  RED.events.removeListener("bldgblocks-global-update", updateListener);
81
134
  }
135
+ done();
82
136
  });
83
137
  }
84
138
  RED.nodes.registerType("global-getter", GlobalGetterNode);
@@ -60,7 +60,7 @@ module.exports = function(RED) {
60
60
  evaluations.push(
61
61
  utils.requiresEvaluation(config.writePriorityType)
62
62
  ? utils.evaluateNodeProperty( config.writePriority, config.writePriorityType, node, msg )
63
- : Promise.resolve(config.writePriority),
63
+ : Promise.resolve(node.writePriority),
64
64
  );
65
65
 
66
66
  const results = await Promise.all(evaluations);
@@ -118,6 +118,10 @@ module.exports = function(RED) {
118
118
  if (done) done();
119
119
  return;
120
120
  }
121
+
122
+ if (inputValue !== undefined) {
123
+ state.priority[node.writePriority] = inputValue;
124
+ }
121
125
  }
122
126
 
123
127
  // Ensure defaultValue always has a value
@@ -125,11 +129,6 @@ module.exports = function(RED) {
125
129
  state.defaultValue = node.defaultValue;
126
130
  }
127
131
 
128
- // Update Specific Priority Slot
129
- if (inputValue !== undefined) {
130
- state.priority[node.writePriority] = inputValue;
131
- }
132
-
133
132
  // Calculate Winner
134
133
  state.value = calculateWinner(state);
135
134
 
@@ -166,6 +165,7 @@ module.exports = function(RED) {
166
165
 
167
166
  node.on('close', function(removed, done) {
168
167
  if (removed && node.varName) {
168
+ //RED.events.removeAllListeners("bldgblocks-global-update");
169
169
  const globalContext = node.context().global;
170
170
  globalContext.set(node.varName, undefined, node.storeName);
171
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bldgblocks/node-red-contrib-control",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Sedona-inspired control nodes for Node-RED",
5
5
  "keywords": [ "node-red", "sedona", "control", "hvac" ],
6
6
  "files": ["nodes/*.js", "nodes/*.html"],