@bldgblocks/node-red-contrib-control 0.1.33 → 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 (113) 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 +74 -67
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +168 -188
  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-point-register.js +126 -0
  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-service-read.js +58 -0
  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-service-write.js +83 -0
  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 +275 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-read.js +0 -59
  111. package/nodes/network-register.js +0 -161
  112. package/nodes/network-write.html +0 -64
  113. package/nodes/network-write.js +0 -126
@@ -0,0 +1,83 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+ function NetworkServiceWriteNode(config) {
4
+ RED.nodes.createNode(this, config);
5
+ const node = this;
6
+ node.registry = RED.nodes.getNode(config.registry);
7
+
8
+ node.on("input", async function(msg, send, done) {
9
+ send = send || function() { node.send.apply(node, arguments); };
10
+
11
+ try {
12
+ // Validation
13
+ if (!msg || !msg.pointId || !msg.priority || msg.value === undefined) {
14
+ return utils.sendError(node, msg, done, "Invalid msg properties", msg?.pointId);
15
+ }
16
+
17
+ // Registry Lookup
18
+ const entry = node.registry.lookup(msg.pointId);
19
+ if (!entry?.path) {
20
+ const store = entry?.store ?? "unknown";
21
+ return utils.sendError(node, msg, done, `Unknown ID: (${store})::${msg.pointId}`, msg.pointId);
22
+ }
23
+
24
+ const { store = "default", path, writable } = entry;
25
+
26
+ if (!writable) {
27
+ return utils.sendError(node, msg, done, `Not Writable: (${store})::${path}::${msg.pointId}`, msg.pointId);
28
+ }
29
+
30
+ // Get State (Async)
31
+ let state = await utils.getGlobalState(node, path, store);
32
+
33
+ if (!state || !state.priority) {
34
+ return utils.sendError(node, msg, done, `Point Not Found: (${store})::${path}`, msg.pointId);
35
+ }
36
+
37
+ // Type Check
38
+ let newValue = msg.value === "null" || msg.value === null ? null : msg.value;
39
+ if (newValue !== null) {
40
+ const dataType = state.metadata?.type;
41
+ if (dataType && typeof newValue !== dataType) {
42
+ return utils.sendError(node, msg, done, `Type Mismatch: Expected ${dataType}`, msg.pointId);
43
+ }
44
+ }
45
+
46
+ // Update Priority Logic
47
+ if (msg.priority === 'default') {
48
+ state.defaultValue = newValue ?? state.defaultValue;
49
+ } else {
50
+ const priority = parseInt(msg.priority, 10);
51
+ if (isNaN(priority) || priority < 1 || priority > 16) {
52
+ return utils.sendError(node, msg, done, `Invalid Priority: ${msg.priority}`, msg.pointId);
53
+ }
54
+ state.priority[msg.priority] = newValue;
55
+ }
56
+
57
+ // Calculate Winner
58
+ const result = utils.getHighestPriority(state);
59
+ state.value = result.value;
60
+ state.activePriority = result.priority;
61
+ state.metadata.lastSet = new Date().toISOString();
62
+
63
+ // Save (Async) & Emit
64
+ await utils.setGlobalState(node, path, store, state);
65
+
66
+ const prefixReq = msg.priority === 'default' ? '' : 'P';
67
+ const prefixAct = state.activePriority === 'default' ? '' : 'P';
68
+ const statusMsg = `Wrote: ${prefixReq}${msg.priority}:${newValue} > Active: ${prefixAct}${state.activePriority}:${state.value}`;
69
+
70
+ msg = { ...state, status: null };
71
+
72
+ RED.events.emit("bldgblocks:global:value-changed", { key: path, store: store, data: state });
73
+
74
+ utils.sendSuccess(node, msg, done, statusMsg, msg.pointId, "ring");
75
+
76
+ } catch (err) {
77
+ node.error(err);
78
+ utils.sendError(node, msg, done, `Internal Error: ${err.message}`, msg?.pointId);
79
+ }
80
+ });
81
+ }
82
+ RED.nodes.registerType("network-service-write", NetworkServiceWriteNode);
83
+ }
@@ -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