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

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 +464 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +46 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +66 -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
@@ -0,0 +1,142 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
4
+ function AlarmServiceNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+
8
+ // Initialize configuration
9
+ node.name = config.name || "alarm-service";
10
+ node.filterTopic = config.filterTopic || ""; // Optional: only listen to alarms with this topic
11
+ node.filterPriority = config.filterPriority || ""; // Optional: only listen to this priority
12
+
13
+ // Runtime state
14
+ node.activeAlarms = new Map(); // Map of topic → { state, data, timestamp }
15
+ node.alarmListener = null;
16
+
17
+ utils.setStatusOK(node, "listening");
18
+
19
+ // ====================================================================
20
+ // Handle alarm events from collectors
21
+ // ====================================================================
22
+ node.alarmListener = function(eventData) {
23
+ // Filter by topic if configured
24
+ if (node.filterTopic && eventData.topic !== node.filterTopic) {
25
+ return;
26
+ }
27
+
28
+ // Filter by priority if configured
29
+ if (node.filterPriority && eventData.priority !== node.filterPriority) {
30
+ return;
31
+ }
32
+
33
+ // Use topic as the key (allows multiple alarms with different topics)
34
+ const key = eventData.topic || `alarm_${eventData.nodeId}`;
35
+
36
+ // Update active alarms map
37
+ if (eventData.state === true) {
38
+ // Alarm triggered
39
+ node.activeAlarms.set(key, {
40
+ state: true,
41
+ data: eventData,
42
+ timestamp: new Date(eventData.timestamp)
43
+ });
44
+
45
+ // Update status to show active alarm count
46
+ const activeCount = node.activeAlarms.size;
47
+ utils.setStatusError(node, `${activeCount} active alarm(s)`);
48
+
49
+ // Send alarm message with status
50
+ const msg = {
51
+ payload: eventData,
52
+ status: { state: "triggered", transition: eventData.transition },
53
+ activeAlarmCount: activeCount,
54
+ alarmKey: key
55
+ };
56
+ node.send(msg);
57
+
58
+ } else if (eventData.state === false) {
59
+ // Alarm cleared
60
+ if (node.activeAlarms.has(key)) {
61
+ const clearedAlarm = node.activeAlarms.get(key);
62
+ node.activeAlarms.delete(key);
63
+
64
+ // Update status
65
+ const activeCount = node.activeAlarms.size;
66
+ if (activeCount === 0) {
67
+ utils.setStatusOK(node, "listening (no active alarms)");
68
+ } else {
69
+ utils.setStatusWarn(node, `${activeCount} active alarm(s)`);
70
+ }
71
+
72
+ // Send clear message with status
73
+ const msg = {
74
+ payload: eventData,
75
+ status: { state: "cleared", transition: eventData.transition },
76
+ activeAlarmCount: activeCount,
77
+ alarmKey: key
78
+ };
79
+ node.send(msg);
80
+ }
81
+ }
82
+ };
83
+
84
+ // Listen to the fixed alarm event
85
+ RED.events.on("bldgblocks:alarms:state-change", node.alarmListener);
86
+
87
+ // Handle wired input messages (optional - can relay or query status)
88
+ node.on("input", function(msg, send, done) {
89
+ send = send || function() { node.send.apply(node, arguments); };
90
+
91
+ if (!msg) {
92
+ utils.setStatusError(node, "invalid message");
93
+ if (done) done();
94
+ return;
95
+ }
96
+
97
+ // Handle control messages
98
+ if (msg.hasOwnProperty("context")) {
99
+ if (msg.context === "getStatus") {
100
+ // Query current alarm status
101
+ const alarmArray = Array.from(node.activeAlarms.values()).map(a => a.data);
102
+ const statusMsg = {
103
+ payload: alarmArray,
104
+ activeCount: node.activeAlarms.size,
105
+ timestamp: new Date().toISOString()
106
+ };
107
+ send(statusMsg);
108
+ utils.setStatusOK(node, `status: ${node.activeAlarms.size} alarms`);
109
+ if (done) done();
110
+ return;
111
+ } else if (msg.context === "clearAll") {
112
+ // Clear all active alarms from tracking (for reset scenarios)
113
+ if (msg.payload === true) {
114
+ node.activeAlarms.clear();
115
+ utils.setStatusOK(node, "listening (cleared)");
116
+ if (done) done();
117
+ return;
118
+ }
119
+ }
120
+ }
121
+
122
+ // Pass-through: relay incoming message downstream
123
+ send(msg);
124
+ if (done) done();
125
+ });
126
+
127
+ node.on("close", function(done) {
128
+ // Remove alarm listener
129
+ if (node.alarmListener) {
130
+ RED.events.off("bldgblocks:alarms:state-change", node.alarmListener);
131
+ node.alarmListener = null;
132
+ }
133
+
134
+ // Clear active alarms map
135
+ node.activeAlarms.clear();
136
+
137
+ done();
138
+ });
139
+ }
140
+
141
+ RED.nodes.registerType("alarm-service", AlarmServiceNode);
142
+ };
@@ -1,82 +1,75 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function AnalogSwitchBlockNode(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
- slots: parseInt(config.slots, 10),
10
- inputs: Array(parseInt(config.slots, 10) || 2).fill(0),
11
- switch: 1
12
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.slots = parseInt(config.slots, 10);
12
+ node.inputs = Array(parseInt(config.slots, 10) || 2).fill(0);
13
+ node.switch = 1;
13
14
 
14
15
  node.on("input", function(msg, send, done) {
15
16
  send = send || function() { node.send.apply(node, arguments); };
16
17
 
17
18
  // Guard against invalid message
18
19
  if (!msg) {
19
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
20
+ utils.setStatusError(node, "invalid message");
20
21
  if (done) done();
21
22
  return;
22
23
  }
23
24
 
24
25
  // Validate context
25
26
  if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
26
- node.status({ fill: "red", shape: "ring", text: "missing context" });
27
+ utils.setStatusError(node, "missing context");
27
28
  if (done) done();
28
29
  return;
29
30
  }
30
31
 
31
32
  // Validate payload
32
33
  if (!msg.hasOwnProperty("payload")) {
33
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
34
+ utils.setStatusError(node, "missing payload");
34
35
  if (done) done();
35
36
  return;
36
37
  }
37
38
 
38
39
  let shouldOutput = false;
39
- const prevSwitch = node.runtime.switch;
40
+ const prevSwitch = node.switch;
40
41
 
41
42
  switch (msg.context) {
42
43
  case "switch":
43
44
  const switchValue = parseInt(msg.payload, 10);
44
- if (isNaN(switchValue) || switchValue < 1 || switchValue > node.runtime.slots) {
45
- node.status({ fill: "red", shape: "ring", text: "invalid switch" });
45
+ if (isNaN(switchValue) || switchValue < 1 || switchValue > node.slots) {
46
+ utils.setStatusError(node, "invalid switch");
46
47
  if (done) done();
47
48
  return;
48
49
  }
49
- node.runtime.switch = switchValue;
50
- shouldOutput = prevSwitch !== node.runtime.switch;
51
- node.status({
52
- fill: "green",
53
- shape: "dot",
54
- text: `switch: ${node.runtime.switch}`
55
- });
50
+ node.switch = switchValue;
51
+ shouldOutput = prevSwitch !== node.switch;
52
+ utils.setStatusOK(node, `switch: ${node.switch}`);
56
53
  break;
57
54
  default:
58
55
  if (msg.context.startsWith("in")) {
59
56
  const index = parseInt(msg.context.slice(2), 10);
60
- if (isNaN(index) || index < 1 || index > node.runtime.slots) {
61
- node.status({ fill: "red", shape: "ring", text: `invalid input index ${index}` });
57
+ if (isNaN(index) || index < 1 || index > node.slots) {
58
+ utils.setStatusError(node, `invalid input index ${index}`);
62
59
  if (done) done();
63
60
  return;
64
61
  }
65
62
  const value = parseFloat(msg.payload);
66
63
  if (isNaN(value)) {
67
- node.status({ fill: "red", shape: "ring", text: `invalid in${index}` });
64
+ utils.setStatusError(node, `invalid in${index}`);
68
65
  if (done) done();
69
66
  return;
70
67
  }
71
- node.runtime.inputs[index - 1] = value;
72
- shouldOutput = index === node.runtime.switch;
73
- node.status({
74
- fill: "green",
75
- shape: "dot",
76
- text: `in${index}: ${value.toFixed(2)}`
77
- });
68
+ node.inputs[index - 1] = value;
69
+ shouldOutput = index === node.switch;
70
+ utils.setStatusOK(node, `in${index}: ${value.toFixed(2)}`);
78
71
  } else {
79
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
72
+ utils.setStatusWarn(node, "unknown context");
80
73
  if (done) done("Unknown context");
81
74
  return;
82
75
  }
@@ -85,12 +78,8 @@ module.exports = function(RED) {
85
78
 
86
79
  // Output new message if the active slot is updated or switch/slots change affects output
87
80
  if (shouldOutput) {
88
- const out = node.runtime.inputs[node.runtime.switch - 1] ?? node.runtime.inputs[0];
89
- node.status({
90
- fill: "blue",
91
- shape: "dot",
92
- text: `slots: ${node.runtime.slots}, switch: ${node.runtime.switch}, out: ${out.toFixed(2)}`
93
- });
81
+ const out = node.inputs[node.switch - 1] ?? node.inputs[0];
82
+ utils.setStatusChanged(node, `slots: ${node.slots}, switch: ${node.switch}, out: ${out.toFixed(2)}`);
94
83
  send({ payload: out });
95
84
  }
96
85
 
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function AndBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -7,60 +9,87 @@ module.exports = function(RED) {
7
9
  node.inputs = Array(parseInt(config.slots) || 2).fill(false);
8
10
  node.slots = parseInt(config.slots);
9
11
 
10
- node.status({ fill: "green", shape: "dot", text: `slots: ${node.slots}` });
12
+ utils.setStatusOK(node, `slots: ${node.slots}`);
11
13
 
12
14
  // Initialize fields
13
15
  let lastResult = null;
14
16
  let lastInputs = node.inputs.slice();
17
+ let lastOutputTime = 0; // Track last output timestamp for debounce
18
+ let lastOutputValue = undefined; // Track last output value for duplicate suppression
19
+ const DEBOUNCE_MS = 500; // Debounce period in milliseconds
15
20
 
16
21
  node.on("input", function(msg, send, done) {
17
22
  send = send || function() { node.send.apply(node, arguments); };
18
23
 
19
24
  // Guard against invalid msg
20
25
  if (!msg) {
21
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
26
+ utils.setStatusError(node, "invalid message");
22
27
  if (done) done();
23
28
  return;
24
29
  }
25
30
 
26
31
  // Check required properties
27
32
  if (!msg.hasOwnProperty("context")) {
28
- node.status({ fill: "red", shape: "ring", text: "missing context" });
33
+ utils.setStatusError(node, "missing context");
29
34
  if (done) done();
30
35
  return;
31
36
  }
32
37
 
33
38
  if (!msg.hasOwnProperty("payload")) {
34
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
39
+ utils.setStatusError(node, "missing payload");
35
40
  if (done) done();
36
41
  return;
37
42
  }
38
43
 
39
44
  // Process input slot
40
45
  if (msg.context.startsWith("in")) {
41
- let index = parseInt(msg.context.slice(2), 10);
42
- if (!isNaN(index) && index >= 1 && index <= node.slots) {
43
- node.inputs[index - 1] = Boolean(msg.payload);
46
+ const slotVal = utils.validateSlotIndex(msg.context, node.slots);
47
+ if (slotVal.valid) {
48
+ node.inputs[slotVal.index - 1] = Boolean(msg.payload);
44
49
  const result = node.inputs.every(v => v === true);
45
50
  const isUnchanged = result === lastResult && node.inputs.every((v, i) => v === lastInputs[i]);
46
- node.status({
47
- fill: "blue",
48
- shape: isUnchanged ? "ring" : "dot",
49
- text: `in: [${node.inputs.join(", ")}], out: ${result}`
50
- });
51
+ const statusText = `in: [${node.inputs.join(", ")}], out: ${result}`;
52
+
53
+ // ================================================================
54
+ // Debounce: Suppress consecutive same outputs within 500ms
55
+ // But always output if value is different or debounce time expired
56
+ // ================================================================
57
+ const now = Date.now();
58
+ const timeSinceLastOutput = now - lastOutputTime;
59
+ const isSameOutput = result === lastOutputValue;
60
+ const shouldSuppress = isSameOutput && timeSinceLastOutput < DEBOUNCE_MS;
61
+
62
+ if (shouldSuppress) {
63
+ // Same output within debounce window - don't send, just update status
64
+ utils.setStatusUnchanged(node, statusText);
65
+ } else {
66
+ // Different output or debounce period expired - send it
67
+ if (isUnchanged) {
68
+ utils.setStatusUnchanged(node, statusText);
69
+ } else {
70
+ utils.setStatusChanged(node, statusText);
71
+ }
72
+
73
+ // Record output for next debounce comparison
74
+ lastOutputTime = now;
75
+ lastOutputValue = result;
76
+
77
+ // Send output to allow all downstream branches to update
78
+ send({ payload: result });
79
+ }
80
+
51
81
  lastResult = result;
52
82
  lastInputs = node.inputs.slice();
53
- send({ payload: result });
54
83
  if (done) done();
55
84
  return;
56
85
  } else {
57
- node.status({ fill: "red", shape: "ring", text: `invalid input index ${index || "NaN"}` });
86
+ utils.setStatusError(node, slotVal.error);
58
87
  if (done) done();
59
88
  return;
60
89
  }
61
90
  }
62
91
 
63
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
92
+ utils.setStatusWarn(node, "unknown context");
64
93
  if (done) done();
65
94
  });
66
95
 
@@ -6,13 +6,12 @@ module.exports = function(RED) {
6
6
  const node = this;
7
7
 
8
8
  // Initialize runtime state
9
- node.runtime = {
10
- maxValues: parseInt(config.sampleSize),
11
- values: [], // Queue for rolling window
12
- lastAvg: null,
13
- minValid: parseFloat(config.minValid),
14
- maxValid: parseFloat(config.maxValid)
15
- };
9
+ // Initialize state
10
+ node.maxValues = parseInt(config.sampleSize);
11
+ node.values = [], // Queue for rolling window;
12
+ node.lastAvg = null;
13
+ node.minValid = parseFloat(config.minValid);
14
+ node.maxValid = parseFloat(config.maxValid);
16
15
 
17
16
  node.isBusy = false;
18
17
 
@@ -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.minValidType)
48
47
  ? utils.evaluateNodeProperty(config.minValid, config.minValidType, node, msg)
49
48
  .then(val => parseFloat(val))
50
- : Promise.resolve(node.runtime.minValid),
49
+ : Promise.resolve(node.minValid),
51
50
  );
52
51
 
53
52
  evaluations.push(
54
53
  utils.requiresEvaluation(config.maxValidType)
55
54
  ? utils.evaluateNodeProperty(config.maxValid, config.maxValidType, node, msg)
56
55
  .then(val => parseFloat(val))
57
- : Promise.resolve(node.runtime.maxValid),
56
+ : Promise.resolve(node.maxValid),
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.minValid = results[0];
64
- if (!isNaN(results[1])) node.runtime.maxValid = results[1];
62
+ if (!isNaN(results[0])) node.minValid = results[0];
63
+ if (!isNaN(results[1])) node.maxValid = results[1];
65
64
  } catch (err) {
66
65
  node.error(`Error evaluating properties: ${err.message}`);
67
66
  if (done) done();
@@ -72,8 +71,8 @@ module.exports = function(RED) {
72
71
  }
73
72
 
74
73
  // Validate values
75
- if (isNaN(node.runtime.maxValid) || isNaN(node.runtime.minValid) || node.runtime.maxValid <= node.runtime.minValid ) {
76
- node.status({ fill: "red", shape: "ring", text: `invalid evaluated values ${node.runtime.minValid}, ${node.runtime.maxValid}` });
74
+ if (isNaN(node.maxValid) || isNaN(node.minValid) || node.maxValid <= node.minValid ) {
75
+ utils.setStatusError(node, `invalid evaluated values ${node.minValid}, ${node.maxValid}`);
77
76
  if (done) done();
78
77
  return;
79
78
  }
@@ -81,42 +80,43 @@ module.exports = function(RED) {
81
80
  // Handle configuration messages
82
81
  if (msg.hasOwnProperty("context")) {
83
82
  if (!msg.hasOwnProperty("payload")) {
84
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
83
+ utils.setStatusError(node, "missing payload");
85
84
  if (done) done();
86
85
  return;
87
86
  }
88
87
 
89
88
  switch (msg.context) {
90
89
  case "reset":
91
- if (typeof msg.payload !== "boolean") {
92
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
90
+ const boolVal = utils.validateBoolean(msg.payload);
91
+ if (!boolVal.valid) {
92
+ utils.setStatusError(node, boolVal.error);
93
93
  if (done) done();
94
94
  return;
95
95
  }
96
- if (msg.payload === true) {
97
- node.runtime.values = [];
98
- node.runtime.lastAvg = null;
99
- node.status({ fill: "green", shape: "dot", text: "state reset" });
96
+ if (boolVal.value === true) {
97
+ node.values = [];
98
+ node.lastAvg = null;
99
+ utils.setStatusOK(node, "state reset");
100
100
  }
101
101
  break;
102
102
 
103
103
  case "sampleSize":
104
- let newMaxValues = parseInt(msg.payload);
105
- if (isNaN(newMaxValues) || newMaxValues < 1) {
106
- node.status({ fill: "red", shape: "ring", text: "invalid window size" });
104
+ const sizeVal = utils.validateIntRange(msg.payload, { min: 1 });
105
+ if (!sizeVal.valid) {
106
+ utils.setStatusError(node, sizeVal.error);
107
107
  if (done) done();
108
108
  return;
109
109
  }
110
- node.runtime.maxValues = newMaxValues;
110
+ node.maxValues = sizeVal.value;
111
111
  // Trim values if new window is smaller
112
- if (node.runtime.values.length > newMaxValues) {
113
- node.runtime.values = node.runtime.values.slice(-newMaxValues);
112
+ if (node.values.length > sizeVal.value) {
113
+ node.values = node.values.slice(-sizeVal.value);
114
114
  }
115
- node.status({ fill: "green", shape: "dot", text: `window: ${newMaxValues}` });
115
+ utils.setStatusOK(node, `window: ${sizeVal.value}`);
116
116
  break;
117
117
 
118
118
  default:
119
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
119
+ utils.setStatusWarn(node, "unknown context");
120
120
  break;
121
121
  }
122
122
  if (done) done();
@@ -125,32 +125,37 @@ module.exports = function(RED) {
125
125
 
126
126
  // Check for missing payload
127
127
  if (!msg.hasOwnProperty("payload")) {
128
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
128
+ utils.setStatusError(node, "missing payload");
129
129
  if (done) done();
130
130
  return;
131
131
  }
132
132
 
133
133
  // Process input
134
- const inputValue = parseFloat(msg.payload);
135
- if (isNaN(inputValue) || inputValue < node.runtime.minValid || inputValue > node.runtime.maxValid) {
136
- node.status({ fill: "yellow", shape: "ring", text: "out of range" });
134
+ const numVal = utils.validateNumericPayload(msg.payload, { min: node.minValid, max: node.maxValid });
135
+ if (!numVal.valid) {
136
+ utils.setStatusWarn(node, "out of range");
137
137
  if (done) done();
138
138
  return;
139
139
  }
140
+ const inputValue = numVal.value;
140
141
 
141
142
  // Update rolling window
142
- node.runtime.values.push(inputValue);
143
- if (node.runtime.values.length > node.runtime.maxValues) {
144
- node.runtime.values.shift();
143
+ node.values.push(inputValue);
144
+ if (node.values.length > node.maxValues) {
145
+ node.values.shift();
145
146
  }
146
147
 
147
148
  // Calculate average
148
- const avg = node.runtime.values.length ? node.runtime.values.reduce((a, b) => a + b, 0) / node.runtime.values.length : null;
149
- const isUnchanged = avg === node.runtime.lastAvg;
149
+ const avg = node.values.length ? node.values.reduce((a, b) => a + b, 0) / node.values.length : null;
150
+ const isUnchanged = avg === node.lastAvg;
150
151
 
151
152
  // Send new message
152
- node.status({ fill: "blue", shape: isUnchanged ? "ring" : "dot", text: `out: ${avg !== null ? avg.toFixed(3) : "null"}` });
153
- node.runtime.lastAvg = avg;
153
+ if (isUnchanged) {
154
+ utils.setStatusUnchanged(node, `out: ${avg !== null ? avg.toFixed(3) : "null"}`);
155
+ } else {
156
+ utils.setStatusChanged(node, `out: ${avg !== null ? avg.toFixed(3) : "null"}`);
157
+ }
158
+ node.lastAvg = avg;
154
159
  send({ payload: avg });
155
160
 
156
161
  if (done) done();
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function BooleanSwitchBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -7,25 +9,21 @@ module.exports = function(RED) {
7
9
  node.state = config.state;
8
10
 
9
11
  // Set initial status
10
- node.status({
11
- fill: "green",
12
- shape: "dot",
13
- text: `state: ${node.state}`
14
- });
12
+ utils.setStatusOK(node, `state: ${node.state}`);
15
13
 
16
14
  node.on("input", function(msg, send, done) {
17
15
  send = send || function() { node.send.apply(node, arguments); };
18
16
 
19
17
  // Guard against invalid message
20
18
  if (!msg) {
21
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
19
+ utils.setStatusError(node, "invalid message");
22
20
  if (done) done();
23
21
  return;
24
22
  }
25
23
 
26
24
  // Validate context
27
25
  if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
28
- node.status({ fill: "red", shape: "ring", text: "missing or invalid context" });
26
+ utils.setStatusError(node, "missing or invalid context");
29
27
  if (done) done();
30
28
  return;
31
29
  }
@@ -34,44 +32,28 @@ module.exports = function(RED) {
34
32
  switch (msg.context) {
35
33
  case "toggle":
36
34
  node.state = !node.state;
37
- node.status({
38
- fill: "green",
39
- shape: "dot",
40
- text: `state: ${node.state}`
41
- });
35
+ utils.setStatusChanged(node, `state: ${node.state}`);
42
36
  send([null, null, { payload: node.state }]);
43
37
  break;
44
38
  case "switch":
45
39
  node.state = !!msg.payload;
46
- node.status({
47
- fill: "green",
48
- shape: "dot",
49
- text: `state: ${node.state}`
50
- });
40
+ utils.setStatusChanged(node, `state: ${node.state}`);
51
41
  send([null, null, { payload: node.state }]);
52
42
  break;
53
43
  case "inTrue":
54
44
  if (node.state) {
55
- node.status({
56
- fill: "blue",
57
- shape: "dot",
58
- text: `out: ${msg.payload}`
59
- });
45
+ utils.setStatusOK(node, `out: ${msg.payload}`);
60
46
  send([msg, null, { payload: node.state }]);
61
47
  }
62
48
  break;
63
49
  case "inFalse":
64
50
  if (!node.state) {
65
- node.status({
66
- fill: "blue",
67
- shape: "dot",
68
- text: `out: ${msg.payload}`
69
- });
51
+ utils.setStatusOK(node, `out: ${msg.payload}`);
70
52
  send([null, msg, { payload: node.state }]);
71
53
  }
72
54
  break;
73
55
  default:
74
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
56
+ utils.setStatusWarn(node, "unknown context");
75
57
  if (done) done("Unknown context");
76
58
  return;
77
59
  }