@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
@@ -1,17 +1,15 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function NullifyBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
6
- // Initialize runtime state
7
- node.runtime = {
8
- name: config.name,
9
- rules: config.rules
10
- };
7
+ // Initialize state
8
+ node.rules = config.rules || [];
11
9
 
12
10
  // Validate configuration
13
11
  let valid = true;
14
- node.runtime.rules = node.runtime.rules.map(rule => {
12
+ node.rules = node.rules.map(rule => {
15
13
  if (rule.propertyType !== "msg" || !rule.property || typeof rule.property !== "string" || !rule.property.trim()) {
16
14
  valid = false;
17
15
  return { property: "payload", propertyType: "msg" };
@@ -19,9 +17,9 @@ module.exports = function(RED) {
19
17
  return rule;
20
18
  });
21
19
  if (!valid) {
22
- node.status({ fill: "red", shape: "ring", text: "invalid rules, using defaults" });
20
+ utils.setStatusError(node, "invalid rules, using defaults");
23
21
  } else {
24
- node.status({ fill: "green", shape: "dot", text: `rules: ${node.runtime.rules.map(r => r.property).join(", ")}` });
22
+ utils.setStatusOK(node, `rules: ${node.rules.map(r => r.property).join(", ")}`);
25
23
  }
26
24
 
27
25
  node.on("input", function(msg, send, done) {
@@ -29,7 +27,7 @@ module.exports = function(RED) {
29
27
 
30
28
  // Guard against invalid message
31
29
  if (!msg) {
32
- node.status({ fill: "red", shape: "ring", text: "missing message" });
30
+ utils.setStatusError(node, "missing message");
33
31
  if (done) done();
34
32
  return;
35
33
  }
@@ -37,18 +35,18 @@ module.exports = function(RED) {
37
35
  // Handle configuration messages
38
36
  if (msg.context) {
39
37
  if (typeof msg.context !== "string" || !msg.context.trim()) {
40
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
38
+ utils.setStatusWarn(node, "unknown context");
41
39
  if (done) done();
42
40
  return;
43
41
  }
44
42
  if (msg.context === "rules") {
45
43
  if (!msg.hasOwnProperty("payload") || !Array.isArray(msg.payload) || !msg.payload.every(r => r.property && typeof r.property === "string" && r.propertyType === "msg")) {
46
- node.status({ fill: "red", shape: "ring", text: "invalid rules" });
44
+ utils.setStatusError(node, "invalid rules");
47
45
  if (done) done();
48
46
  return;
49
47
  }
50
- node.runtime.rules = msg.payload;
51
- node.status({ fill: "green", shape: "dot", text: `rules: ${node.runtime.rules.map(r => r.property).join(", ")}` });
48
+ node.rules = msg.payload;
49
+ utils.setStatusOK(node, `rules: ${node.rules.map(r => r.property).join(", ")}`);
52
50
  if (done) done();
53
51
  return;
54
52
  }
@@ -57,13 +55,13 @@ module.exports = function(RED) {
57
55
  // Apply nullification rules
58
56
  const outputMsg = RED.util.cloneMessage(msg);
59
57
  const nullified = [];
60
- node.runtime.rules.forEach(rule => {
58
+ node.rules.forEach(rule => {
61
59
  RED.util.setMessageProperty(outputMsg, rule.property, null);
62
60
  nullified.push(rule.property);
63
61
  });
64
62
 
65
63
  // Update status and send output
66
- node.status({ fill: "blue", shape: "dot", text: `nullified: ${nullified.join(", ")}` });
64
+ utils.setStatusOK(node, `nullified: ${nullified.join(", ")}`);
67
65
  send(outputMsg);
68
66
 
69
67
  if (done) done();
@@ -4,6 +4,10 @@
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">
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>
7
11
  <div class="form-row">
8
12
  <label for="node-input-period" title="Filter period in milliseconds (non-negative number from msg, flow, global, or static value)"><i class="fa fa-clock-o"></i> Filter Period (ms)</label>
9
13
  <input type="text" id="node-input-period" placeholder="0">
@@ -18,6 +22,7 @@
18
22
  color: "#301934",
19
23
  defaults: {
20
24
  name: { value: "" },
25
+ inputProperty: { value: "payload" },
21
26
  period: { value: 0 },
22
27
  periodType: { value: "num" }
23
28
  },
@@ -42,10 +47,10 @@
42
47
 
43
48
  <!-- Help Section -->
44
49
  <script type="text/markdown" data-help-name="on-change-block">
45
- Filters redundant messages based on value changes and a configurable period.
50
+ Filters redundant messages based on value changes from a configured property.
46
51
 
47
52
  ### Inputs
48
- : payload (any) : Input value to compare.
53
+ : input-property (any) : Value to monitor for changes, read from the configured Input Property.
49
54
  : context (string, optional) : Configures period (`"period"`) or queries state (`"status"`).
50
55
  : payload (number | any, for `context`) : Non-negative number for `"period"` (ms), any for `"status"`.
51
56
 
@@ -54,16 +59,19 @@ Filters redundant messages based on value changes and a configurable period.
54
59
  : payload (object, for `msg.context = "status"`) : `{ period, periodType }`.
55
60
 
56
61
  ### Properties
57
- : name (string) : Display name in editor. Default "" (shows "on change").
58
- : period (number) : Filter period in milliseconds ( 0, static or from `msg`, `flow`, `global`). Default 0.
62
+ : name (string) : Display name in editor.
63
+ : inputProperty (string) : Message property to monitor for changes (default: `payload`). Supports nested properties (e.g., `data.value`).
64
+ : period (number) : Filter period in milliseconds (≥ 0). Default 0.
59
65
  : periodType (string) : Source of period (`"num"`, `"msg"`, `"flow"`, `"global"`). Default `"num"`.
60
66
 
61
67
  ### Details
62
- Filters messages based on value changes (deep comparison) and a filter period. When `period = 0`, outputs the input message only if `msg.payload`
63
- differs from the last output value. When `period > 0`, outputs the first message, suppresses all messages during the period (including blips).
64
- Supports complex payloads (objects, arrays) via deep comparison.
65
- Configuration
66
- - `msg.context = "period"` Sets period (ms), no output.
68
+ Filters messages based on value changes (deep comparison) to the input property and an optional filter period.
69
+ When `period = 0`, outputs the input message only if the input property differs from the last output value.
70
+ When `period > 0`, outputs the first message, then suppresses all messages during the period.
71
+
72
+ Supports complex payloads (objects, arrays) via deep comparison.
73
+
74
+ Configuration via `msg.context = "period"` with numeric `msg.payload` sets the filter period in milliseconds (no output).
67
75
 
68
76
  ### Status
69
77
  - Green (dot): Configuration update
@@ -1,5 +1,5 @@
1
1
  module.exports = function(RED) {
2
- const utils = require('./utils')(RED);
2
+ const utils = require("./utils")(RED);
3
3
 
4
4
  function OnChangeBlockNode(config) {
5
5
  RED.nodes.createNode(this, config);
@@ -7,19 +7,19 @@ module.exports = function(RED) {
7
7
  node.isBusy = false;
8
8
 
9
9
  // Initialize runtime state
10
- node.runtime = {
11
- name: config.name,
12
- lastValue: null,
13
- blockTimer: null,
14
- period: parseFloat(config.period),
15
- };
10
+ // Initialize state
11
+ node.name = config.name;
12
+ node.inputProperty = config.inputProperty || "payload";
13
+ node.lastValue = null;
14
+ node.blockTimer = null;
15
+ node.period = parseFloat(config.period);
16
16
 
17
17
  node.on("input", async function(msg, send, done) {
18
18
  send = send || function() { node.send.apply(node, arguments); };
19
19
 
20
20
  // Guard against invalid message
21
21
  if (!msg) {
22
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
22
+ utils.setStatusError(node, "invalid message");
23
23
  if (done) done();
24
24
  return;
25
25
  }
@@ -30,7 +30,7 @@ module.exports = function(RED) {
30
30
  // Check busy lock
31
31
  if (node.isBusy) {
32
32
  // Update status to let user know they are pushing too fast
33
- node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
33
+ utils.setStatusBusy(node, "busy - dropped msg");
34
34
  if (done) done();
35
35
  return;
36
36
  }
@@ -45,13 +45,13 @@ module.exports = function(RED) {
45
45
  utils.requiresEvaluation(config.periodType)
46
46
  ? utils.evaluateNodeProperty(config.period, config.periodType, node, msg)
47
47
  .then(val => parseFloat(val))
48
- : Promise.resolve(node.runtime.period),
48
+ : Promise.resolve(node.period),
49
49
  );
50
50
 
51
51
  const results = await Promise.all(evaluations);
52
52
 
53
53
  // Update runtime with evaluated values
54
- if (!isNaN(results[0])) node.runtime.period = results[0];
54
+ if (!isNaN(results[0])) node.period = results[0];
55
55
  } catch (err) {
56
56
  node.error(`Error evaluating properties: ${err.message}`);
57
57
  if (done) done();
@@ -62,32 +62,28 @@ module.exports = function(RED) {
62
62
  }
63
63
 
64
64
  // Acceptable fallbacks
65
- if (isNaN(node.runtime.period) || node.runtime.period < 0) {
66
- node.runtime.period = config.period;
67
- node.status({ fill: "red", shape: "ring", text: "invalid period, using 0" });
65
+ if (isNaN(node.period) || node.period < 0) {
66
+ node.period = config.period;
67
+ utils.setStatusError(node, "invalid period, using 0");
68
68
  }
69
69
 
70
70
  // Handle context updates
71
71
  if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
72
72
  if (msg.context === "period") {
73
73
  if (!msg.hasOwnProperty("payload")) {
74
- node.status({ fill: "red", shape: "ring", text: "missing payload for period" });
74
+ utils.setStatusError(node, "missing payload for period");
75
75
  if (done) done();
76
76
  return;
77
77
  }
78
78
  const newPeriod = parseFloat(msg.payload);
79
79
  if (isNaN(newPeriod) || newPeriod < 0) {
80
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
80
+ utils.setStatusError(node, "invalid period");
81
81
  if (done) done();
82
82
  return;
83
83
  }
84
- node.runtime.period = newPeriod;
85
- node.runtime.periodType = "num";
86
- node.status({
87
- fill: "green",
88
- shape: "dot",
89
- text: `period: ${node.runtime.period.toFixed(0)} ms`
90
- });
84
+ node.period = newPeriod;
85
+ node.periodType = "num";
86
+ utils.setStatusOK(node, `period: ${node.period.toFixed(0)} ms`);
91
87
  if (done) done();
92
88
  return;
93
89
  }
@@ -96,13 +92,26 @@ module.exports = function(RED) {
96
92
 
97
93
  // Validate input payload
98
94
  if (!msg.hasOwnProperty("payload")) {
99
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
95
+ utils.setStatusError(node, "missing payload");
100
96
  send(msg);
101
97
  if (done) done();
102
98
  return;
103
99
  }
104
100
 
105
- const currentValue = msg.payload;
101
+ let inputValue;
102
+ try {
103
+ inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
104
+ } catch (err) {
105
+ inputValue = undefined;
106
+ }
107
+ if (inputValue === undefined) {
108
+ utils.setStatusError(node, "missing or invalid input property");
109
+ send(msg);
110
+ if (done) done();
111
+ return;
112
+ }
113
+
114
+ const currentValue = inputValue;
106
115
 
107
116
  // Deep comparison function
108
117
  function isEqual(a, b) {
@@ -122,46 +131,40 @@ module.exports = function(RED) {
122
131
  }
123
132
 
124
133
  // Block if in filter period
125
- if (node.runtime.blockTimer) {
126
- node.status({
127
- fill: "blue",
128
- shape: "ring",
129
- text: `filtered: ${JSON.stringify(currentValue).slice(0, 20)} |`
130
- });
134
+ if (node.blockTimer) {
135
+ const filteredText = `filtered: ${JSON.stringify(currentValue).slice(0, 20)} |`;
136
+ utils.setStatusUnchanged(node, filteredText);
131
137
  if (done) done();
132
138
  return;
133
139
  }
134
140
 
135
141
  // period === 0 means only ever on change, not equal outside of filter period sends an update message
136
- if (isEqual(currentValue, node.runtime.lastValue)) {
137
- if (node.runtime.period === 0) {
142
+ if (isEqual(currentValue, node.lastValue)) {
143
+ if (node.period === 0) {
138
144
  if (done) done();
139
145
  return;
140
146
  }
141
147
  }
142
148
 
143
- node.runtime.lastValue = currentValue;
149
+ node.lastValue = currentValue;
144
150
  send(msg);
145
151
 
146
152
  // Start filter period if applicable
147
- if (node.runtime.period > 0) {
148
- node.runtime.blockTimer = setTimeout(() => {
149
- node.runtime.blockTimer = null;
150
- node.status({
151
- fill: "blue",
152
- shape: "ring",
153
- text: `filtered: ${JSON.stringify(currentValue).slice(0, 20)}` // remove ' |' to indicate end of filter period
154
- });
155
- }, node.runtime.period);
153
+ if (node.period > 0) {
154
+ node.blockTimer = setTimeout(() => {
155
+ node.blockTimer = null;
156
+ const endFilterText = `filtered: ${JSON.stringify(currentValue).slice(0, 20)}`;
157
+ utils.setStatusUnchanged(node, endFilterText);
158
+ }, node.period);
156
159
  }
157
160
 
158
161
  if (done) done();
159
162
  });
160
163
 
161
164
  node.on("close", function(done) {
162
- if (node.runtime.blockTimer) {
163
- clearTimeout(node.runtime.blockTimer);
164
- node.runtime.blockTimer = null;
165
+ if (node.blockTimer) {
166
+ clearTimeout(node.blockTimer);
167
+ node.blockTimer = null;
165
168
  }
166
169
  done();
167
170
  });
@@ -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-duration" title="Duration of true pulse (positive number)"><i class="fa fa-clock-o"></i> Duration</label>
9
13
  <input type="number" id="node-input-duration" placeholder="1000" min="0" step="any">
10
14
  <select id="node-input-durationUnits">
@@ -30,6 +34,7 @@
30
34
  color: "#301934",
31
35
  defaults: {
32
36
  name: { value: "" },
37
+ inputProperty: { value: "payload" },
33
38
  duration: {
34
39
  value: 1000,
35
40
  required: true,
@@ -53,9 +58,10 @@
53
58
 
54
59
  <!-- Help Section -->
55
60
  <script type="text/markdown" data-help-name="oneshot-block">
56
- Outputs a true pulse for a configurable duration when triggered with `msg.payload = true`, then false, and locks until reset.
61
+ Outputs a true pulse for a configurable duration when triggered, then locks until reset.
57
62
 
58
63
  ### Inputs
64
+ : input-property (boolean) : Trigger signal to initiate pulse, read from the configured Input Property.
59
65
  : context (string) : Configures node (`"reset"`, `"duration"`). Unmatched values trigger error.
60
66
  : payload (boolean | number) : `true` triggers pulse if not locked; number for `"duration"`; boolean for `"reset"`.
61
67
  : units (string) : Units for `"duration"` (`"milliseconds"`, `"seconds"`, `"minutes"`).
@@ -65,23 +71,20 @@ Outputs a true pulse for a configurable duration when triggered with `msg.payloa
65
71
 
66
72
  ### Properties
67
73
  : name (string) : Display name in editor.
74
+ : inputProperty (string) : Message property to read trigger signal from (default: `payload`). Supports nested properties (e.g., `data.value`).
68
75
  : duration (number) : Pulse duration (positive number).
69
76
  : durationUnits (string) : Units for duration (`"milliseconds"`, `"seconds"`, `"minutes"`).
70
77
  : resetRequireTrue (boolean) : Require `msg.payload = true` for reset.
71
78
  : resetOnComplete (boolean) : Automatically reset (unlock) after pulse duration.
72
79
 
73
80
  ### Details
74
- Generates a `true` pulse for `duration` when triggered with `msg.payload = true`,
75
- then outputs `false`.
76
-
77
- Locks until reset via `msg.context = "reset"` (requires `msg.payload = true` if `resetRequireTrue = true`) or
78
- automatically if `resetOnComplete = true`. Non-`true` payloads are ignored with no output.
81
+ Generates a `true` pulse for `duration` when triggered with input property = true (read from configured **Input Property**, default: `msg.payload`), then outputs `false`.
79
82
 
80
- Duration configurable via editor or `msg.context = "duration"` with `msg.units` (`"milliseconds"`, `"seconds"`, `"minutes"`).
83
+ Locks until reset via `msg.context = "reset"` (requires `msg.payload = true` if `resetRequireTrue = true`) or automatically if `resetOnComplete = true`.
81
84
 
82
- Tracks `triggerCount` (number of triggers) and displays in status.
85
+ Non-`true` inputs are ignored with no output. Duration configurable via editor or `msg.context = "duration"` with `msg.units` (`"milliseconds"`, `"seconds"`, `"minutes"`).
83
86
 
84
- Outputs new `{ payload boolean }` messages for every trigger (`true` then `false`), reset, or locked input.
87
+ Outputs are written to `msg.payload` as boolean values.
85
88
 
86
89
  ### Status
87
90
  - Green (dot): Configuration update
@@ -1,44 +1,41 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function OneshotBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
6
7
  // Initialize runtime state
7
8
  const durationMultiplier = config.durationUnits === "seconds" ? 1000 : config.durationUnits === "minutes" ? 60000 : 1;
8
- node.runtime = {
9
- name: config.name,
10
- duration: (parseFloat(config.duration)) * durationMultiplier,
11
- durationUnits: config.durationUnits,
12
- resetRequireTrue: config.resetRequireTrue,
13
- resetOnComplete: config.resetOnComplete,
14
- triggerCount: 0,
15
- locked: false,
16
- output: false
17
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.duration = (parseFloat(config.duration)) * durationMultiplier;
13
+ node.durationUnits = config.durationUnits;
14
+ node.resetRequireTrue = config.resetRequireTrue;
15
+ node.resetOnComplete = config.resetOnComplete;
16
+ node.triggerCount = 0;
17
+ node.locked = false;
18
+ node.output = false;
18
19
 
19
20
  // Validate initial config
20
- if (isNaN(node.runtime.duration) || node.runtime.duration < 1) {
21
- node.runtime.duration = 1000;
22
- node.runtime.durationUnits = "milliseconds";
23
- node.status({ fill: "red", shape: "ring", text: "invalid duration" });
21
+ if (isNaN(node.duration) || node.duration < 1) {
22
+ node.duration = 1000;
23
+ node.durationUnits = "milliseconds";
24
+ utils.setStatusError(node, "invalid duration");
24
25
  }
25
26
 
26
27
  // Timer for pulse
27
28
  let timer = null;
28
29
 
29
30
  // Set initial status
30
- node.status({
31
- fill: "blue",
32
- shape: "ring",
33
- text: `triggers: ${node.runtime.triggerCount}, ${node.runtime.locked ? "locked" : "unlocked"}`
34
- });
31
+ utils.setStatusOK(node, `triggers: ${node.triggerCount}, ${node.locked ? "locked" : "unlocked"}`);
35
32
 
36
33
  node.on("input", function(msg, send, done) {
37
34
  send = send || function() { node.send.apply(node, arguments); };
38
35
 
39
36
  // Guard against invalid message
40
37
  if (!msg) {
41
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
38
+ utils.setStatusError(node, "invalid message");
42
39
  if (done) done();
43
40
  return;
44
41
  }
@@ -46,8 +43,8 @@ module.exports = function(RED) {
46
43
  // Handle context updates
47
44
  if (msg.hasOwnProperty("context")) {
48
45
  if (msg.context === "reset") {
49
- if (node.runtime.resetRequireTrue && msg.payload !== true) {
50
- node.status({ fill: "red", shape: "ring", text: "invalid reset payload" });
46
+ if (node.resetRequireTrue && msg.payload !== true) {
47
+ utils.setStatusError(node, "invalid reset payload");
51
48
  if (done) done();
52
49
  return;
53
50
  }
@@ -55,20 +52,16 @@ module.exports = function(RED) {
55
52
  clearTimeout(timer);
56
53
  timer = null;
57
54
  }
58
- node.runtime.locked = false;
59
- node.runtime.output = false;
60
- node.status({
61
- fill: "blue",
62
- shape: "dot",
63
- text: `triggers: ${node.runtime.triggerCount}, reset`
64
- });
55
+ node.locked = false;
56
+ node.output = false;
57
+ utils.setStatusChanged(node, `triggers: ${node.triggerCount}, reset`);
65
58
  send({ payload: false });
66
59
  if (done) done();
67
60
  return;
68
61
  }
69
62
  if (msg.context === "duration") {
70
63
  if (!msg.hasOwnProperty("payload")) {
71
- node.status({ fill: "red", shape: "ring", text: "missing payload for duration" });
64
+ utils.setStatusError(node, "missing payload for duration");
72
65
  if (done) done();
73
66
  return;
74
67
  }
@@ -77,81 +70,70 @@ module.exports = function(RED) {
77
70
  const multiplier = newDurationUnits === "seconds" ? 1000 : newDurationUnits === "minutes" ? 60000 : 1;
78
71
  newDuration *= multiplier;
79
72
  if (isNaN(newDuration) || newDuration < 1) {
80
- node.status({ fill: "red", shape: "ring", text: "invalid duration" });
73
+ utils.setStatusError(node, "invalid duration");
81
74
  if (done) done();
82
75
  return;
83
76
  }
84
- node.runtime.duration = newDuration;
85
- node.runtime.durationUnits = newDurationUnits;
86
- node.status({
87
- fill: "green",
88
- shape: "dot",
89
- text: `duration: ${node.runtime.duration.toFixed(0)} ms`
90
- });
77
+ node.duration = newDuration;
78
+ node.durationUnits = newDurationUnits;
79
+ utils.setStatusOK(node, `duration: ${node.duration.toFixed(0)} ms`);
91
80
  if (done) done();
92
81
  return;
93
82
  }
94
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
83
+ utils.setStatusWarn(node, "unknown context");
95
84
  if (done) done("Unknown context");
96
85
  return;
97
86
  }
98
87
 
99
- // Validate payload for trigger
100
- if (msg.payload !== true) {
101
- node.status({
102
- fill: "yellow",
103
- shape: "ring",
104
- text: `ignored: non-true`
105
- });
88
+ // Get trigger input from configured property
89
+ let triggerValue;
90
+ try {
91
+ triggerValue = RED.util.getMessageProperty(msg, node.inputProperty);
92
+ } catch (err) {
93
+ triggerValue = undefined;
94
+ }
95
+ if (triggerValue === undefined) {
96
+ utils.setStatusError(node, "missing or invalid input property");
97
+ if (done) done();
98
+ return;
99
+ }
100
+
101
+ // Validate trigger input
102
+ if (triggerValue !== true) {
103
+ utils.setStatusWarn(node, `ignored: non-true`);
106
104
  if (done) done();
107
105
  return;
108
106
  }
109
107
 
110
108
  // Check if locked
111
- if (node.runtime.locked) {
112
- node.status({
113
- fill: "red",
114
- shape: "ring",
115
- text: `triggers: ${node.runtime.triggerCount}, locked`
116
- });
117
- send({ payload: node.runtime.output });
109
+ if (node.locked) {
110
+ utils.setStatusError(node, `triggers: ${node.triggerCount}, locked`);
111
+ send({ payload: node.output });
118
112
  if (done) done();
119
113
  return;
120
114
  }
121
115
 
122
116
  // Trigger pulse
123
- node.runtime.triggerCount++;
124
- node.runtime.locked = true;
125
- node.runtime.output = true;
117
+ node.triggerCount++;
118
+ node.locked = true;
119
+ node.output = true;
126
120
 
127
121
  // Send true pulse
128
- node.status({
129
- fill: "green",
130
- shape: "dot",
131
- text: `triggers: ${node.runtime.triggerCount}, out: true`
132
- });
122
+ utils.setStatusOK(node, `triggers: ${node.triggerCount}, out: true`);
133
123
  send({ payload: true });
134
124
 
135
125
  // Schedule false output
136
126
  timer = setTimeout(() => {
137
- node.runtime.output = false;
138
- if (node.runtime.resetOnComplete) {
139
- node.runtime.locked = false;
140
- node.status({
141
- fill: "blue",
142
- shape: "ring",
143
- text: `triggers: ${node.runtime.triggerCount}, unlocked`
144
- });
127
+ node.output = false;
128
+ if (node.resetOnComplete) {
129
+ node.locked = false;
130
+ utils.setStatusOK(node, `triggers: ${node.triggerCount}, unlocked`);
145
131
  } else {
146
- node.status({
147
- fill: "red",
148
- shape: "ring",
149
- text: `triggers: ${node.runtime.triggerCount}, locked`
150
- });
132
+ utils.setStatusError(node, `triggers: ${node.triggerCount}, locked`);
151
133
  }
152
134
  send({ payload: false });
153
135
  timer = null;
154
- }, node.runtime.duration);
136
+ }, node.duration);
155
137
 
156
138
  if (done) done();
157
139
  });