@bldgblocks/node-red-contrib-control 0.1.34 → 0.1.36

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.
Files changed (110) hide show
  1. package/nodes/accumulate-block.html +18 -8
  2. package/nodes/accumulate-block.js +39 -44
  3. package/nodes/add-block.html +1 -1
  4. package/nodes/add-block.js +18 -11
  5. package/nodes/alarm-collector.html +260 -0
  6. package/nodes/alarm-collector.js +292 -0
  7. package/nodes/alarm-config.html +129 -0
  8. package/nodes/alarm-config.js +126 -0
  9. package/nodes/alarm-service.html +96 -0
  10. package/nodes/alarm-service.js +142 -0
  11. package/nodes/analog-switch-block.js +25 -36
  12. package/nodes/and-block.js +44 -15
  13. package/nodes/average-block.js +46 -41
  14. package/nodes/boolean-switch-block.js +10 -28
  15. package/nodes/boolean-to-number-block.html +18 -5
  16. package/nodes/boolean-to-number-block.js +24 -16
  17. package/nodes/cache-block.js +24 -37
  18. package/nodes/call-status-block.html +91 -32
  19. package/nodes/call-status-block.js +398 -115
  20. package/nodes/changeover-block.html +5 -0
  21. package/nodes/changeover-block.js +167 -162
  22. package/nodes/comment-block.html +1 -1
  23. package/nodes/comment-block.js +14 -9
  24. package/nodes/compare-block.html +14 -4
  25. package/nodes/compare-block.js +23 -18
  26. package/nodes/contextual-label-block.html +5 -0
  27. package/nodes/contextual-label-block.js +6 -16
  28. package/nodes/convert-block.html +25 -39
  29. package/nodes/convert-block.js +31 -16
  30. package/nodes/count-block.html +11 -5
  31. package/nodes/count-block.js +34 -32
  32. package/nodes/delay-block.js +58 -53
  33. package/nodes/divide-block.js +43 -45
  34. package/nodes/edge-block.html +17 -10
  35. package/nodes/edge-block.js +43 -41
  36. package/nodes/enum-switch-block.js +6 -6
  37. package/nodes/frequency-block.html +6 -1
  38. package/nodes/frequency-block.js +64 -74
  39. package/nodes/global-getter.html +51 -15
  40. package/nodes/global-getter.js +43 -13
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +40 -12
  43. package/nodes/history-buffer.html +96 -0
  44. package/nodes/history-buffer.js +461 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +37 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +52 -0
  50. package/nodes/hysteresis-block.html +5 -0
  51. package/nodes/hysteresis-block.js +13 -16
  52. package/nodes/interpolate-block.html +20 -2
  53. package/nodes/interpolate-block.js +39 -50
  54. package/nodes/join.html +78 -0
  55. package/nodes/join.js +78 -0
  56. package/nodes/latch-block.js +12 -14
  57. package/nodes/load-sequence-block.js +102 -110
  58. package/nodes/max-block.js +26 -26
  59. package/nodes/memory-block.js +57 -58
  60. package/nodes/min-block.js +26 -25
  61. package/nodes/minmax-block.js +35 -34
  62. package/nodes/modulo-block.js +45 -43
  63. package/nodes/multiply-block.js +43 -41
  64. package/nodes/negate-block.html +17 -7
  65. package/nodes/negate-block.js +25 -19
  66. package/nodes/network-point-read.html +128 -0
  67. package/nodes/network-point-read.js +230 -0
  68. package/nodes/{network-register.html → network-point-register.html} +94 -7
  69. package/nodes/{network-register.js → network-point-register.js} +18 -4
  70. package/nodes/network-point-write.html +149 -0
  71. package/nodes/network-point-write.js +222 -0
  72. package/nodes/network-service-bridge.html +131 -0
  73. package/nodes/network-service-bridge.js +376 -0
  74. package/nodes/network-service-read.html +81 -0
  75. package/nodes/{network-read.js → network-service-read.js} +4 -3
  76. package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
  77. package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
  78. package/nodes/network-service-write.html +89 -0
  79. package/nodes/{network-write.js → network-service-write.js} +3 -3
  80. package/nodes/nullify-block.js +13 -15
  81. package/nodes/on-change-block.html +17 -9
  82. package/nodes/on-change-block.js +49 -46
  83. package/nodes/oneshot-block.html +13 -10
  84. package/nodes/oneshot-block.js +57 -75
  85. package/nodes/or-block.js +44 -15
  86. package/nodes/pid-block.html +54 -4
  87. package/nodes/pid-block.js +459 -248
  88. package/nodes/priority-block.js +24 -35
  89. package/nodes/rate-limit-block.js +70 -72
  90. package/nodes/rate-of-change-block.html +33 -14
  91. package/nodes/rate-of-change-block.js +74 -62
  92. package/nodes/round-block.html +14 -9
  93. package/nodes/round-block.js +32 -25
  94. package/nodes/saw-tooth-wave-block.js +49 -76
  95. package/nodes/scale-range-block.html +12 -6
  96. package/nodes/scale-range-block.js +46 -39
  97. package/nodes/sine-wave-block.js +49 -57
  98. package/nodes/string-builder-block.js +6 -6
  99. package/nodes/subtract-block.js +38 -34
  100. package/nodes/thermistor-block.js +44 -44
  101. package/nodes/tick-tock-block.js +32 -32
  102. package/nodes/time-sequence-block.js +30 -42
  103. package/nodes/triangle-wave-block.js +49 -69
  104. package/nodes/tstat-block.js +34 -44
  105. package/nodes/units-block.html +90 -69
  106. package/nodes/units-block.js +22 -30
  107. package/nodes/utils.js +206 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-write.html +0 -65
@@ -5,13 +5,12 @@ module.exports = function(RED) {
5
5
  RED.nodes.createNode(this, config);
6
6
  const node = this;
7
7
 
8
- node.runtime = {
9
- name: config.name,
10
- state: false,
11
- desired: false,
12
- delayOn: parseFloat(config.delayOn) * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1),
13
- delayOff: parseFloat(config.delayOff) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1)
14
- };
8
+ // Initialize state
9
+ node.name = config.name;
10
+ node.state = false;
11
+ node.desired = false;
12
+ node.delayOn = parseFloat(config.delayOn) * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
13
+ node.delayOff = parseFloat(config.delayOff) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
15
14
 
16
15
  let timeoutId = null;
17
16
  node.isBusy = false;
@@ -21,7 +20,7 @@ module.exports = function(RED) {
21
20
 
22
21
  // Guard against invalid msg
23
22
  if (!msg) {
24
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
23
+ utils.setStatusError(node, "invalid message");
25
24
  if (done) done();
26
25
  return;
27
26
  }
@@ -32,7 +31,7 @@ module.exports = function(RED) {
32
31
  // Check busy lock
33
32
  if (node.isBusy) {
34
33
  // Update status to let user know they are pushing too fast
35
- node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
34
+ utils.setStatusBusy(node, "busy - dropped msg");
36
35
  if (done) done();
37
36
  return;
38
37
  }
@@ -47,21 +46,21 @@ module.exports = function(RED) {
47
46
  utils.requiresEvaluation(config.delayOnType)
48
47
  ? utils.evaluateNodeProperty(config.delayOn, config.delayOnType, node, msg)
49
48
  .then(val => parseFloat(val))
50
- : Promise.resolve(node.runtime.delayOn),
49
+ : Promise.resolve(node.delayOn),
51
50
  );
52
51
 
53
52
  evaluations.push(
54
53
  utils.requiresEvaluation(config.delayOffType)
55
54
  ? utils.evaluateNodeProperty(config.delayOff, config.delayOffType, node, msg)
56
55
  .then(val => parseFloat(val))
57
- : Promise.resolve(node.runtime.delayOff),
56
+ : Promise.resolve(node.delayOff),
58
57
  );
59
58
 
60
59
  const results = await Promise.all(evaluations);
61
60
 
62
61
  // Update runtime with evaluated values
63
- if (!isNaN(results[0])) node.runtime.delayOn = results[0] * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
64
- if (!isNaN(results[1])) node.runtime.delayOff = results[1] * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
62
+ if (!isNaN(results[0])) node.delayOn = results[0] * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
63
+ if (!isNaN(results[1])) node.delayOff = results[1] * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
65
64
  } catch (err) {
66
65
  node.error(`Error evaluating properties: ${err.message}`);
67
66
  if (done) done();
@@ -72,35 +71,41 @@ module.exports = function(RED) {
72
71
  }
73
72
 
74
73
  // Acceptable fallbacks
75
- if (isNaN(node.runtime.delayOn) || node.runtime.delayOn < 0) {
76
- node.runtime.delayOn = 1000;
77
- node.status({ fill: "red", shape: "ring", text: "invalid delayOn" });
74
+ if (isNaN(node.delayOn) || node.delayOn < 0) {
75
+ node.delayOn = 1000;
76
+ utils.setStatusError(node, "invalid delayOn");
78
77
  }
79
- if (isNaN(node.runtime.delayOff) || node.runtime.delayOff < 0) {
80
- node.runtime.delayOff = 1000;
81
- node.status({ fill: "red", shape: "ring", text: "invalid delayOff" });
78
+ if (isNaN(node.delayOff) || node.delayOff < 0) {
79
+ node.delayOff = 1000;
80
+ utils.setStatusError(node, "invalid delayOff");
82
81
  }
83
82
 
84
83
  if (msg.hasOwnProperty("context")) {
85
84
  if (msg.context === "reset") {
86
- if (!msg.hasOwnProperty("payload") || typeof msg.payload !== "boolean") {
87
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
85
+ if (!msg.hasOwnProperty("payload")) {
86
+ utils.setStatusError(node, "missing payload");
87
+ if (done) done();
88
+ return;
89
+ }
90
+ const boolVal = utils.validateBoolean(msg.payload);
91
+ if (!boolVal.valid) {
92
+ utils.setStatusError(node, boolVal.error);
88
93
  if (done) done();
89
94
  return;
90
95
  }
91
- if (msg.payload === true) {
96
+ if (boolVal.value === true) {
92
97
  if (timeoutId) {
93
98
  clearTimeout(timeoutId);
94
99
  timeoutId = null;
95
100
  }
96
- node.runtime.state = false;
97
- node.status({ fill: "green", shape: "dot", text: "reset" });
101
+ node.state = false;
102
+ utils.setStatusOK(node, "reset");
98
103
  }
99
104
  if (done) done();
100
105
  return;
101
106
  } else if (msg.context === "delayOn") {
102
107
  if (!msg.hasOwnProperty("payload")) {
103
- node.status({ fill: "red", shape: "ring", text: "missing payload for delayOn" });
108
+ utils.setStatusError(node, "missing payload for delayOn");
104
109
  if (done) done();
105
110
  return;
106
111
  }
@@ -108,17 +113,17 @@ module.exports = function(RED) {
108
113
  const newDelayOnMultiplier = msg.units === "seconds" ? 1000 : msg.units === "minutes" ? 60000 : 1;
109
114
  newDelayOn *= newDelayOnMultiplier;
110
115
  if (isNaN(newDelayOn) || newDelayOn < 0) {
111
- node.status({ fill: "red", shape: "ring", text: "invalid delayOn" });
116
+ utils.setStatusError(node, "invalid delayOn");
112
117
  if (done) done();
113
118
  return;
114
119
  }
115
- node.runtime.delayOn = newDelayOn;
116
- node.status({ fill: "green", shape: "dot", text: `delayOn: ${newDelayOn.toFixed(0)} ms` });
120
+ node.delayOn = newDelayOn;
121
+ utils.setStatusOK(node, `delayOn: ${newDelayOn.toFixed(0)} ms`);
117
122
  if (done) done();
118
123
  return;
119
124
  } else if (msg.context === "delayOff") {
120
125
  if (!msg.hasOwnProperty("payload")) {
121
- node.status({ fill: "red", shape: "ring", text: "missing payload for delayOff" });
126
+ utils.setStatusError(node, "missing payload for delayOff");
122
127
  if (done) done();
123
128
  return;
124
129
  }
@@ -126,81 +131,81 @@ module.exports = function(RED) {
126
131
  const newDelayOffMultiplier = msg.units === "seconds" ? 1000 : msg.units === "minutes" ? 60000 : 1;
127
132
  newDelayOff *= newDelayOffMultiplier;
128
133
  if (isNaN(newDelayOff) || newDelayOff < 0) {
129
- node.status({ fill: "red", shape: "ring", text: "invalid delayOff" });
134
+ utils.setStatusError(node, "invalid delayOff");
130
135
  if (done) done();
131
136
  return;
132
137
  }
133
- node.runtime.delayOff = newDelayOff;
134
- node.status({ fill: "green", shape: "dot", text: `delayOff: ${newDelayOff.toFixed(0)} ms` });
138
+ node.delayOff = newDelayOff;
139
+ utils.setStatusOK(node, `delayOff: ${newDelayOff.toFixed(0)} ms`);
135
140
  if (done) done();
136
141
  return;
137
142
  }
138
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
143
+ utils.setStatusWarn(node, "unknown context");
139
144
  if (done) done();
140
145
  return;
141
146
  }
142
147
 
143
148
  if (!msg.hasOwnProperty("payload")) {
144
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
149
+ utils.setStatusError(node, "missing payload");
145
150
  if (done) done();
146
151
  return;
147
152
  }
148
153
 
149
154
  const inputValue = msg.payload;
150
155
  if (typeof inputValue !== "boolean") {
151
- node.status({ fill: "red", shape: "ring", text: "invalid payload" });
156
+ utils.setStatusError(node, "invalid payload");
152
157
  if (done) done();
153
158
  return;
154
159
  }
155
160
 
156
- if (!node.runtime.state && inputValue === true) {
157
- if (node.runtime.desired) {
161
+ if (!node.state && inputValue === true) {
162
+ if (node.desired) {
158
163
  if (done) done();
159
164
  return;
160
165
  }
161
166
  if (timeoutId) {
162
167
  clearTimeout(timeoutId);
163
168
  }
164
- node.status({ fill: "blue", shape: "ring", text: `awaiting true` });
165
- node.runtime.desired = true;
169
+ utils.setStatusUnchanged(node, "awaiting true");
170
+ node.desired = true;
166
171
  timeoutId = setTimeout(() => {
167
- node.runtime.state = true;
172
+ node.state = true;
168
173
  msg.payload = true;
169
174
  delete msg.context;
170
- node.status({ fill: "blue", shape: "dot", text: `in: true, out: true` });
175
+ utils.setStatusChanged(node, "in: true, out: true");
171
176
  send(msg);
172
177
  timeoutId = null;
173
- }, node.runtime.delayOn);
174
- } else if (node.runtime.state && inputValue === false) {
175
- if (node.runtime.desired === false) {
178
+ }, node.delayOn);
179
+ } else if (node.state && inputValue === false) {
180
+ if (node.desired === false) {
176
181
  if (done) done();
177
182
  return;
178
183
  }
179
184
  if (timeoutId) {
180
185
  clearTimeout(timeoutId);
181
186
  }
182
- node.status({ fill: "blue", shape: "ring", text: `awaiting false` });
183
- node.runtime.desired = false;
187
+ utils.setStatusUnchanged(node, "awaiting false");
188
+ node.desired = false;
184
189
  timeoutId = setTimeout(() => {
185
- node.runtime.state = false;
190
+ node.state = false;
186
191
  msg.payload = false;
187
192
  delete msg.context;
188
- node.status({ fill: "blue", shape: "dot", text: `in: false, out: false` });
193
+ utils.setStatusChanged(node, "in: false, out: false");
189
194
  send(msg);
190
195
  timeoutId = null;
191
- }, node.runtime.delayOff);
196
+ }, node.delayOff);
192
197
  } else {
193
198
  if (timeoutId) {
194
199
  clearTimeout(timeoutId);
195
200
  timeoutId = null;
196
- node.status({ fill: "blue", shape: "ring", text: `canceled awaiting ${node.runtime.state}` });
201
+ utils.setStatusUnchanged(node, `canceled awaiting ${node.state}`);
197
202
  } else {
198
- node.status({ fill: "blue", shape: "ring", text: `no change` });
203
+ utils.setStatusUnchanged(node, "no change");
199
204
  }
200
205
 
201
206
  // No state change, pass the message through
202
- node.runtime.state = inputValue;
203
- desired = inputValue;
207
+ node.state = inputValue;
208
+ node.desired = inputValue;
204
209
  send(msg);
205
210
  }
206
211
 
@@ -1,114 +1,112 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function DivideBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
 
5
7
  const node = this;
6
8
 
7
9
  // Initialize runtime state
8
- node.runtime = {
9
- name: config.name,
10
- slots: parseInt(config.slots),
11
- inputs: Array(config.slots).fill(1).map(x => parseFloat(x)),
12
- lastResult: null
13
- };
10
+ // Initialize state
11
+ node.name = config.name;
12
+ node.slots = parseInt(config.slots);
13
+ node.inputs = Array(config.slots).fill(1).map(x => parseFloat(x));
14
+ node.lastResult = null;
14
15
 
15
- node.status({
16
- fill: "green",
17
- shape: "dot",
18
- text: `name: ${node.runtime.name}, slots: ${node.runtime.slots}`
19
- });
16
+ utils.setStatusOK(node, `name: ${node.name}, slots: ${node.slots}`);
20
17
 
21
18
  node.on("input", function(msg, send, done) {
22
19
  send = send || function() { node.send.apply(node, arguments); };
23
20
 
24
21
  // Guard against invalid msg
25
22
  if (!msg) {
26
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
23
+ utils.setStatusError(node, "invalid message");
27
24
  if (done) done();
28
25
  return;
29
26
  }
30
27
 
31
28
  // Check for missing context or payload
32
29
  if (!msg.hasOwnProperty("context")) {
33
- node.status({ fill: "red", shape: "ring", text: "missing context" });
30
+ utils.setStatusError(node, "missing context");
34
31
  if (done) done();
35
32
  return;
36
33
  }
37
34
 
38
35
  if (!msg.hasOwnProperty("payload")) {
39
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
36
+ utils.setStatusError(node, "missing payload");
40
37
  if (done) done();
41
38
  return;
42
39
  }
43
40
 
44
41
  // Handle configuration messages
45
42
  if (msg.context === "reset") {
46
- if (typeof msg.payload !== "boolean") {
47
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
43
+ const boolVal = utils.validateBoolean(msg.payload);
44
+ if (!boolVal.valid) {
45
+ utils.setStatusError(node, boolVal.error);
48
46
  if (done) done();
49
47
  return;
50
48
  }
51
- if (msg.payload === true) {
52
- node.runtime.inputs = Array(node.runtime.slots).fill(1);
53
- node.runtime.lastResult = null;
54
- node.status({ fill: "green", shape: "dot", text: "state reset" });
49
+ if (boolVal.value === true) {
50
+ node.inputs = Array(node.slots).fill(1);
51
+ node.lastResult = null;
52
+ utils.setStatusOK(node, "state reset");
55
53
  if (done) done();
56
54
  return;
57
55
  }
58
56
  } else if (msg.context === "slots") {
59
- let newSlots = parseInt(msg.payload);
60
- if (isNaN(newSlots) || newSlots < 1) {
61
- node.status({ fill: "red", shape: "ring", text: "invalid slots" });
57
+ const slotsVal = utils.validateIntRange(msg.payload, { min: 1 });
58
+ if (!slotsVal.valid) {
59
+ utils.setStatusError(node, slotsVal.error);
62
60
  if (done) done();
63
61
  return;
64
62
  }
65
- node.runtime.slots = newSlots;
66
- node.runtime.inputs = Array(newSlots).fill(1);
67
- node.runtime.lastResult = null;
68
- node.status({ fill: "green", shape: "dot", text: `slots: ${node.runtime.slots}` });
63
+ node.slots = slotsVal.value;
64
+ node.inputs = Array(newSlots).fill(1);
65
+ node.lastResult = null;
66
+ utils.setStatusOK(node, `slots: ${node.slots}`);
69
67
  if (done) done();
70
68
  return;
71
69
  } else if (msg.context.startsWith("in")) {
72
- let slotIndex = parseInt(msg.context.slice(2)) - 1;
73
- if (isNaN(slotIndex) || slotIndex < 0 || slotIndex >= node.runtime.slots) {
74
- node.status({ fill: "red", shape: "ring", text: `invalid input slot ${msg.context}` });
70
+ const slotVal = utils.validateSlotIndex(msg.context, node.slots);
71
+ if (!slotVal.valid) {
72
+ utils.setStatusError(node, slotVal.error);
75
73
  if (done) done();
76
74
  return;
77
75
  }
76
+ const slotIndex = slotVal.index - 1;
78
77
  let newValue = parseFloat(msg.payload);
79
78
  if (isNaN(newValue)) {
80
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
79
+ utils.setStatusError(node, "invalid input");
81
80
  if (done) done();
82
81
  return;
83
82
  }
84
83
  if (slotIndex > 0 && newValue === 0) {
85
- node.status({ fill: "red", shape: "ring", text: "divide by zero" });
84
+ utils.setStatusError(node, "divide by zero");
86
85
  if (done) done();
87
86
  return;
88
87
  }
89
88
  // Handle division by very small numbers approaching zero
90
89
  if (slotIndex > 0 && Math.abs(newValue) < 1e-10) { // Near-zero check
91
- node.status({ fill: "red", shape: "ring", text: "divide by near-zero" });
90
+ utils.setStatusError(node, "divide by near-zero");
92
91
  if (done) done();
93
92
  return;
94
93
  }
95
- node.runtime.inputs[slotIndex] = newValue;
94
+ node.inputs[slotIndex] = newValue;
96
95
  // Calculate division
97
- const result = node.runtime.inputs.reduce((acc, val, idx) => idx === 0 ? val : acc / val, 1);
98
- const isUnchanged = result === node.runtime.lastResult;
99
- node.status({
100
- fill: "blue",
101
- shape: isUnchanged ? "ring" : "dot",
102
- text: `in: ${msg.context}=${newValue.toFixed(2)}, out: ${result.toFixed(2)}`
103
- });
104
- if (!isUnchanged) {
105
- node.runtime.lastResult = result;
106
- send({ payload: result });
96
+ const result = node.inputs.reduce((acc, val, idx) => idx === 0 ? val : acc / val, 1);
97
+ const isUnchanged = result === node.lastResult;
98
+ const statusText = `in: ${msg.context}=${newValue.toFixed(2)}, out: ${result.toFixed(2)}`;
99
+ if (isUnchanged) {
100
+ utils.setStatusUnchanged(node, statusText);
101
+ } else {
102
+ utils.setStatusChanged(node, statusText);
107
103
  }
104
+ node.lastResult = result;
105
+ send({ payload: result });
108
106
  if (done) done();
109
107
  return;
110
108
  } else {
111
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
109
+ utils.setStatusWarn(node, "unknown context");
112
110
  if (done) done();
113
111
  return;
114
112
  }
@@ -4,7 +4,11 @@
4
4
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
5
5
  <input type="text" id="node-input-name" placeholder="Name">
6
6
  </div>
7
- <div class="form-row">
7
+ <div class="form-row">
8
+ <label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
9
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
10
+ </div>
11
+ <div class="form-row">
8
12
  <label for="node-input-algorithm" title="Transition to detect (true-to-false or false-to-true)"><i class="fa fa-exchange"></i> Algorithm</label>
9
13
  <select id="node-input-algorithm">
10
14
  <option value="true-to-false">True to False</option>
@@ -20,6 +24,7 @@
20
24
  color: "#301934",
21
25
  defaults: {
22
26
  name: { value: "" },
27
+ inputProperty: { value: "payload" },
23
28
  algorithm: { value: "true-to-false", required: true }
24
29
  },
25
30
  inputs: 1,
@@ -36,27 +41,29 @@
36
41
 
37
42
  <!-- Help Section -->
38
43
  <script type="text/markdown" data-help-name="edge-block">
39
- Detects configured boolean transitions (true-to-false or false-to-true) in `msg.payload`.
44
+ Detects boolean state transitions (true-to-false or false-to-true).
40
45
 
41
46
  ### Inputs
42
- : payload (boolean) : Boolean to monitor for transitions.
47
+ : input-property (boolean) : Boolean value to monitor for transitions, read from the configured Input Property.
43
48
  : context (string) : Action (`"algorithm"` to set transition type, `"reset"` to clear state). Unknown `msg.context` is ignored.
44
49
  : payload (string | boolean) : Transition type (`"true-to-false"`, `"false-to-true"`) for `"algorithm"`, true for `"reset"`.
45
50
 
46
51
  ### Outputs
47
- : payload (boolean) : `true` when the specified transition occurs (true-to-false or false-to-true).
52
+ : payload (boolean) : `true` when the specified transition occurs; `false` otherwise.
48
53
 
49
54
  ### Properties
50
55
  : name (string) : Display name in editor.
56
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
51
57
  : algorithm (string) : Transition to detect (`"true-to-false"`, `"false-to-true"`).
52
58
 
53
59
  ### Details
54
- Detects transitions in boolean payloads based on the configured `algorithm`.
55
- Outputs `msg.payload` as true only when the specified transition occurs (true-to-false or false-to-true).
56
- No output on first input after reset, if no transition occurs, or for non-boolean inputs.
57
- Configuration via `msg.context`
58
- - `"algorithm"` Sets transition type, no output.
59
- - `"reset"` Clears state, no output.
60
+ Detects transitions in boolean input (read from the configured **Input Property**, default: `msg.payload`) based on the configured `algorithm`.
61
+ Outputs `msg.payload` as true only when the specified transition occurs (true-to-false or false-to-true).
62
+ No output on first input after reset, if no transition occurs, or for non-boolean inputs.
63
+
64
+ Configuration via `msg.context`:
65
+ - `"algorithm"`: Sets transition type, no output.
66
+ - `"reset"`: Clears state, no output.
60
67
 
61
68
  ### Status
62
69
  - Green (dot): Configuration
@@ -1,20 +1,18 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function EdgeBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
6
8
  // Initialize runtime state
7
- node.runtime = {
8
- name: config.name,
9
- algorithm: config.algorithm,
10
- lastValue: null
11
- };
12
-
13
- node.status({
14
- fill: "green",
15
- shape: "dot",
16
- text: `name: ${node.runtime.name || "edge"}, algorithm: ${node.runtime.algorithm}`
17
- });
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.algorithm = config.algorithm;
13
+ node.lastValue = null;
14
+
15
+ utils.setStatusOK(node, `name: ${node.name || "edge"}, algorithm: ${node.algorithm}`);
18
16
 
19
17
  node.on("input", function(msg, send, done) {
20
18
  send = send || function() { node.send.apply(node, arguments); };
@@ -23,7 +21,7 @@ module.exports = function(RED) {
23
21
 
24
22
  // Guard against invalid message
25
23
  if (!msg) {
26
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
24
+ utils.setStatusError(node, "invalid message");
27
25
  if (done) done();
28
26
  return;
29
27
  }
@@ -32,31 +30,37 @@ module.exports = function(RED) {
32
30
  if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
33
31
  if (msg.context === "algorithm") {
34
32
  if (!msg.hasOwnProperty("payload")) {
35
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
33
+ utils.setStatusError(node, "missing payload");
36
34
  if (done) done();
37
35
  return;
38
36
  }
39
37
  const newAlgorithm = String(msg.payload);
40
38
  if (!validAlgorithms.includes(newAlgorithm)) {
41
- node.status({ fill: "red", shape: "ring", text: "invalid algorithm" });
39
+ utils.setStatusError(node, "invalid algorithm");
42
40
  if (done) done();
43
41
  return;
44
42
  }
45
- node.runtime.algorithm = newAlgorithm;
46
- node.status({ fill: "green", shape: "dot", text: `algorithm: ${newAlgorithm}` });
43
+ node.algorithm = newAlgorithm;
44
+ utils.setStatusOK(node, `algorithm: ${newAlgorithm}`);
47
45
  if (done) done();
48
46
  return;
49
47
  }
50
48
 
51
49
  if (msg.context === "reset") {
52
- if (!msg.hasOwnProperty("payload") || typeof msg.payload !== "boolean") {
53
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
50
+ if (!msg.hasOwnProperty("payload")) {
51
+ utils.setStatusError(node, "missing payload");
54
52
  if (done) done();
55
53
  return;
56
54
  }
57
- if (msg.payload === true) {
58
- node.runtime.lastValue = null;
59
- node.status({ fill: "green", shape: "dot", text: "state reset" });
55
+ const boolVal = utils.validateBoolean(msg.payload);
56
+ if (!boolVal.valid) {
57
+ utils.setStatusError(node, boolVal.error);
58
+ if (done) done();
59
+ return;
60
+ }
61
+ if (boolVal.value === true) {
62
+ node.lastValue = null;
63
+ utils.setStatusOK(node, "state reset");
60
64
  if (done) done();
61
65
  return;
62
66
  }
@@ -66,48 +70,46 @@ module.exports = function(RED) {
66
70
  // Ignore unknown context, process payload
67
71
  }
68
72
 
69
- // Validate payload
70
- if (!msg.hasOwnProperty("payload")) {
71
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
73
+ // Get input from configured property
74
+ let inputValue;
75
+ try {
76
+ inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
77
+ } catch (err) {
78
+ inputValue = undefined;
79
+ }
80
+ if (inputValue === undefined) {
81
+ utils.setStatusError(node, "missing or invalid input property");
72
82
  if (done) done();
73
83
  return;
74
84
  }
75
85
 
76
- if (typeof msg.payload !== "boolean") {
77
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
86
+ if (typeof inputValue !== "boolean") {
87
+ utils.setStatusError(node, "invalid input");
78
88
  if (done) done();
79
89
  return;
80
90
  }
81
91
 
82
- const currentValue = msg.payload;
83
- const lastValue = node.runtime.lastValue;
92
+ const currentValue = inputValue;
93
+ const lastValue = node.lastValue;
84
94
 
85
95
  // Check for transition
86
96
  let isTransition = false;
87
97
  if (lastValue !== null && lastValue !== undefined) {
88
- if (node.runtime.algorithm === "true-to-false" && lastValue === true && currentValue === false) {
98
+ if (node.algorithm === "true-to-false" && lastValue === true && currentValue === false) {
89
99
  isTransition = true;
90
- } else if (node.runtime.algorithm === "false-to-true" && lastValue === false && currentValue === true) {
100
+ } else if (node.algorithm === "false-to-true" && lastValue === false && currentValue === true) {
91
101
  isTransition = true;
92
102
  }
93
103
  }
94
104
 
95
105
  if (isTransition) {
96
- node.status({
97
- fill: "blue",
98
- shape: "dot",
99
- text: `in: ${currentValue}, out: true`
100
- });
106
+ utils.setStatusChanged(node, `in: ${currentValue}, out: true`);
101
107
  send({ payload: true });
102
108
  } else {
103
- node.status({
104
- fill: "blue",
105
- shape: "ring",
106
- text: `in: ${currentValue}, out: none`
107
- });
109
+ utils.setStatusUnchanged(node, `in: ${currentValue}, out: none`);
108
110
  }
109
111
 
110
- node.runtime.lastValue = currentValue;
112
+ node.lastValue = currentValue;
111
113
  if (done) done();
112
114
  });
113
115