@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,32 +1,33 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require("./utils")(RED);
3
+
2
4
  function SawToothWaveBlockNode(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
- lowerLimit: parseFloat(config.lowerLimit),
10
- upperLimit: parseFloat(config.upperLimit),
11
- period: (parseFloat(config.period) || 10) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1),
12
- periodUnits: config.periodUnits || "seconds",
13
- lastExecution: Date.now(),
14
- phase: 0
15
- };
9
+ // Initialize state
10
+ node.name = config.name;
11
+ node.lowerLimit = parseFloat(config.lowerLimit);
12
+ node.upperLimit = parseFloat(config.upperLimit);
13
+ node.period = (parseFloat(config.period) || 10) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1);
14
+ node.periodUnits = config.periodUnits || "seconds";
15
+ node.lastExecution = Date.now();
16
+ node.phase = 0;
16
17
 
17
18
  // Validate initial config
18
- if (isNaN(node.runtime.lowerLimit) || isNaN(node.runtime.upperLimit) || !isFinite(node.runtime.lowerLimit) || !isFinite(node.runtime.upperLimit)) {
19
- node.runtime.lowerLimit = 0;
20
- node.runtime.upperLimit = 100;
21
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
22
- } else if (node.runtime.lowerLimit > node.runtime.upperLimit) {
23
- node.runtime.upperLimit = node.runtime.lowerLimit;
24
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
19
+ if (isNaN(node.lowerLimit) || isNaN(node.upperLimit) || !isFinite(node.lowerLimit) || !isFinite(node.upperLimit)) {
20
+ node.lowerLimit = 0;
21
+ node.upperLimit = 100;
22
+ utils.setStatusError(node, "invalid limits");
23
+ } else if (node.lowerLimit > node.upperLimit) {
24
+ node.upperLimit = node.lowerLimit;
25
+ utils.setStatusError(node, "invalid limits");
25
26
  }
26
- if (isNaN(node.runtime.period) || node.runtime.period <= 0 || !isFinite(node.runtime.period)) {
27
- node.runtime.period = 10000;
28
- node.runtime.periodUnits = "milliseconds";
29
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
27
+ if (isNaN(node.period) || node.period <= 0 || !isFinite(node.period)) {
28
+ node.period = 10000;
29
+ node.periodUnits = "milliseconds";
30
+ utils.setStatusError(node, "invalid period");
30
31
  }
31
32
 
32
33
  node.on("input", function(msg, send, done) {
@@ -34,7 +35,7 @@ module.exports = function(RED) {
34
35
 
35
36
  // Guard against invalid message
36
37
  if (!msg) {
37
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
38
+ utils.setStatusError(node, "invalid message");
38
39
  if (done) done();
39
40
  return;
40
41
  }
@@ -42,74 +43,54 @@ module.exports = function(RED) {
42
43
  // Handle context updates
43
44
  if (msg.hasOwnProperty("context")) {
44
45
  if (!msg.hasOwnProperty("payload")) {
45
- node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
46
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
46
47
  if (done) done();
47
48
  return;
48
49
  }
49
50
  if (typeof msg.context !== "string") {
50
- node.status({ fill: "red", shape: "ring", text: "invalid context" });
51
+ utils.setStatusError(node, "invalid context");
51
52
  if (done) done();
52
53
  return;
53
54
  }
54
55
  let value = parseFloat(msg.payload);
55
56
  if (isNaN(value) || !isFinite(value)) {
56
- node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
57
+ utils.setStatusError(node, `invalid ${msg.context}`);
57
58
  if (done) done();
58
59
  return;
59
60
  }
60
61
  switch (msg.context) {
61
62
  case "lowerLimit":
62
- node.runtime.lowerLimit = value;
63
- if (node.runtime.lowerLimit > node.runtime.upperLimit) {
64
- node.runtime.upperLimit = node.runtime.lowerLimit;
65
- node.status({
66
- fill: "green",
67
- shape: "dot",
68
- text: `lower: ${node.runtime.lowerLimit.toFixed(2)}, upper adjusted to ${node.runtime.upperLimit.toFixed(2)}`
69
- });
63
+ node.lowerLimit = value;
64
+ if (node.lowerLimit > node.upperLimit) {
65
+ node.upperLimit = node.lowerLimit;
66
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}, upper adjusted to ${node.upperLimit.toFixed(2)}`);
70
67
  } else {
71
- node.status({
72
- fill: "green",
73
- shape: "dot",
74
- text: `lower: ${node.runtime.lowerLimit.toFixed(2)}`
75
- });
68
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}`);
76
69
  }
77
70
  break;
78
71
  case "upperLimit":
79
- node.runtime.upperLimit = value;
80
- if (node.runtime.upperLimit < node.runtime.lowerLimit) {
81
- node.runtime.lowerLimit = node.runtime.upperLimit;
82
- node.status({
83
- fill: "green",
84
- shape: "dot",
85
- text: `upper: ${node.runtime.upperLimit.toFixed(2)}, lower adjusted to ${node.runtime.lowerLimit.toFixed(2)}`
86
- });
72
+ node.upperLimit = value;
73
+ if (node.upperLimit < node.lowerLimit) {
74
+ node.lowerLimit = node.upperLimit;
75
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}, lower adjusted to ${node.lowerLimit.toFixed(2)}`);
87
76
  } else {
88
- node.status({
89
- fill: "green",
90
- shape: "dot",
91
- text: `upper: ${node.runtime.upperLimit.toFixed(2)}`
92
- });
77
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}`);
93
78
  }
94
79
  break;
95
80
  case "period":
96
81
  const multiplier = msg.units === "minutes" ? 60000 : msg.units === "seconds" ? 1000 : 1;
97
82
  value *= multiplier;
98
83
  if (value <= 0) {
99
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
84
+ utils.setStatusError(node, "invalid period");
100
85
  if (done) done();
101
86
  return;
102
87
  }
103
- node.runtime.period = value;
104
- node.runtime.periodUnits = msg.units || "milliseconds";
105
- node.status({
106
- fill: "green",
107
- shape: "dot",
108
- text: `period: ${node.runtime.period.toFixed(2)} ms`
109
- });
88
+ node.period = value;
89
+ node.periodUnits = msg.units || "milliseconds";
90
+ utils.setStatusOK(node, `period: ${node.period.toFixed(2)} ms`);
110
91
  break;
111
92
  default:
112
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
93
+ utils.setStatusWarn(node, "unknown context");
113
94
  if (done) done("Unknown context");
114
95
  return;
115
96
  }
@@ -119,34 +100,26 @@ module.exports = function(RED) {
119
100
 
120
101
  // Calculate time difference
121
102
  const now = Date.now();
122
- const deltaTime = (now - node.runtime.lastExecution) / 1000; // Seconds
123
- node.runtime.lastExecution = now;
103
+ const deltaTime = (now - node.lastExecution) / 1000; // Seconds
104
+ node.lastExecution = now;
124
105
 
125
106
  // Return lowerLimit if period is invalid
126
- if (node.runtime.period <= 0) {
127
- node.status({
128
- fill: "blue",
129
- shape: "dot",
130
- text: `out: ${node.runtime.lowerLimit.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}`
131
- });
132
- send({ payload: node.runtime.lowerLimit });
107
+ if (node.period <= 0) {
108
+ utils.setStatusOK(node, `out: ${node.lowerLimit.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
109
+ send({ payload: node.lowerLimit });
133
110
  if (done) done();
134
111
  return;
135
112
  }
136
113
 
137
114
  // Update phase
138
- node.runtime.phase = (node.runtime.phase + deltaTime / (node.runtime.period / 1000)) % 1;
115
+ node.phase = (node.phase + deltaTime / (node.period / 1000)) % 1;
139
116
 
140
117
  // Sawtooth wave calculation
141
- const amplitude = node.runtime.upperLimit - node.runtime.lowerLimit;
142
- const value = node.runtime.lowerLimit + amplitude * node.runtime.phase;
118
+ const amplitude = node.upperLimit - node.lowerLimit;
119
+ const value = node.lowerLimit + amplitude * node.phase;
143
120
 
144
121
  // Output new message
145
- node.status({
146
- fill: "blue",
147
- shape: "dot",
148
- text: `out: ${value.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}`
149
- });
122
+ utils.setStatusOK(node, `out: ${value.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
150
123
  send({ payload: value });
151
124
 
152
125
  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-inMin" title="Minimum input value (number)"><i class="fa fa-arrow-down"></i> Input Min</label>
9
13
  <input type="number" id="node-input-inMin" placeholder="0.0" step="any">
@@ -33,6 +37,7 @@
33
37
  color: "#301934",
34
38
  defaults: {
35
39
  name: { value: "" },
40
+ inputProperty: { value: "payload" },
36
41
  inMin: { value: 0.0, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && isFinite(parseFloat(v)); } },
37
42
  inMax: { value: 100.0, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && isFinite(parseFloat(v)); } },
38
43
  outMin: { value: 0.0, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && isFinite(parseFloat(v)); } },
@@ -53,16 +58,18 @@
53
58
 
54
59
  <!-- Help Section -->
55
60
  <script type="text/markdown" data-help-name="scale-range-block">
56
- Scales a numeric input from one range to another and passes the original message.
61
+ Scales numeric input from one range to another.
57
62
 
58
63
  ### Inputs
64
+ : input-property (number) : Numeric value to scale, read from the configured Input Property.
59
65
  : context (string) : Configures settings (`"inMin"`, `"inMax"`, `"outMin"`, `"outMax"`, `"clamp"`). Unmatched values trigger error.
60
- : payload (number | boolean) : Number for input or range configuration, boolean for clamp.
61
66
 
62
67
  ### Outputs
63
68
  : payload (number) : Scaled output value.
64
69
 
65
70
  ### Properties
71
+ : name (string) : Display name in editor.
72
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
66
73
  : inMin (number) : Minimum input value.
67
74
  : inMax (number) : Maximum input value (> inMin).
68
75
  : outMin (number) : Minimum output value.
@@ -70,12 +77,11 @@ Scales a numeric input from one range to another and passes the original message
70
77
  : clamp (boolean) : Clamp output to output range.
71
78
 
72
79
  ### Details
73
- Scales `msg.payload` (number) from an input range (`inMin` to `inMax`) to an output range (`outMin` to `outMax`) using linear interpolation, with optional
74
- clamping to `[outMin, outMax]` if `clamp` is `true`.
80
+ Scales numeric input (read from the configured **Input Property**, default: `msg.payload`) from an input range (`inMin` to `inMax`) to an output range (`outMin` to `outMax`) using linear interpolation, with optional clamping to `[outMin, outMax]` if `clamp` is enabled.
75
81
 
76
- Outputs the input message with `msg.payload` set to the scaled value, preserving other properties.
82
+ Formula: `outValue = outMin + (inValue - inMin) * (outMax - outMin) / (inMax - inMin)`
77
83
 
78
- Outputs on valid input or configuration change (recalculates with last input).
84
+ Output is always written to `msg.payload`.
79
85
 
80
86
  ### Status
81
87
  - Green (dot): Configuration update
@@ -1,25 +1,26 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function ScaleRangeBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
6
7
  // Initialize runtime state
7
- node.runtime = {
8
- name: config.name || "",
9
- inMin: parseFloat(config.inMin),
10
- inMax: parseFloat(config.inMax),
11
- outMin: parseFloat(config.outMin),
12
- outMax: parseFloat(config.outMax),
13
- clamp: config.clamp,
14
- lastInput: parseFloat(config.inMin)
15
- };
8
+ // Initialize state
9
+ node.name = config.name || "";
10
+ node.inputProperty = config.inputProperty || "payload";
11
+ node.inMin = parseFloat(config.inMin);
12
+ node.inMax = parseFloat(config.inMax);
13
+ node.outMin = parseFloat(config.outMin);
14
+ node.outMax = parseFloat(config.outMax);
15
+ node.clamp = config.clamp;
16
+ node.lastInput = parseFloat(config.inMin);
16
17
 
17
18
  // Validate initial config
18
- if (isNaN(node.runtime.inMin) || isNaN(node.runtime.inMax) || !isFinite(node.runtime.inMin) || !isFinite(node.runtime.inMax) || node.runtime.inMin >= node.runtime.inMax) {
19
- node.runtime.inMin = 0.0;
20
- node.runtime.inMax = 100.0;
21
- node.runtime.lastInput = 0.0;
22
- node.status({ fill: "red", shape: "ring", text: "invalid input range" });
19
+ if (isNaN(node.inMin) || isNaN(node.inMax) || !isFinite(node.inMin) || !isFinite(node.inMax) || node.inMin >= node.inMax) {
20
+ node.inMin = 0.0;
21
+ node.inMax = 100.0;
22
+ node.lastInput = 0.0;
23
+ utils.setStatusError(node, "invalid input range");
23
24
  }
24
25
 
25
26
  node.on("input", function(msg, send, done) {
@@ -27,7 +28,7 @@ module.exports = function(RED) {
27
28
 
28
29
  // Guard against invalid message
29
30
  if (!msg) {
30
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
31
+ utils.setStatusError(node, "invalid message");
31
32
  if (done) done();
32
33
  return;
33
34
  }
@@ -35,7 +36,7 @@ module.exports = function(RED) {
35
36
  // Handle context updates
36
37
  if (msg.hasOwnProperty("context")) {
37
38
  if (!msg.hasOwnProperty("payload")) {
38
- node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
39
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
39
40
  if (done) done();
40
41
  return;
41
42
  }
@@ -48,74 +49,80 @@ module.exports = function(RED) {
48
49
  case "outMax":
49
50
  const value = parseFloat(msg.payload);
50
51
  if (isNaN(value) || !isFinite(value)) {
51
- node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
52
+ utils.setStatusError(node, `invalid ${msg.context}`);
52
53
  if (done) done();
53
54
  return;
54
55
  }
55
- node.runtime[msg.context] = value;
56
- if (node.runtime.inMax <= node.runtime.inMin) {
57
- node.status({ fill: "red", shape: "ring", text: "invalid input range" });
56
+ node[msg.context] = value;
57
+ if (node.inMax <= node.inMin) {
58
+ utils.setStatusError(node, "invalid input range");
58
59
  if (done) done();
59
60
  return;
60
61
  }
61
- if (node.runtime.outMax <= node.runtime.outMin) {
62
- node.status({ fill: "red", shape: "ring", text: "invalid output range" });
62
+ if (node.outMax <= node.outMin) {
63
+ utils.setStatusError(node, "invalid output range");
63
64
  if (done) done();
64
65
  return;
65
66
  }
66
- node.status({ fill: "green", shape: "dot", text: `${msg.context}: ${value.toFixed(2)}` });
67
+ utils.setStatusOK(node, `${msg.context}: ${value.toFixed(2)}`);
67
68
  shouldOutput = true;
68
69
  break;
69
70
  case "clamp":
70
71
  if (typeof msg.payload !== "boolean") {
71
- node.status({ fill: "red", shape: "ring", text: "invalid clamp" });
72
+ utils.setStatusError(node, "invalid clamp");
72
73
  if (done) done();
73
74
  return;
74
75
  }
75
- node.runtime.clamp = msg.payload;
76
- node.status({ fill: "green", shape: "dot", text: `clamp: ${node.runtime.clamp}` });
76
+ node.clamp = msg.payload;
77
+ utils.setStatusOK(node, `clamp: ${node.clamp}`);
77
78
  shouldOutput = true;
78
79
  break;
79
80
  default:
80
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
81
+ utils.setStatusWarn(node, "unknown context");
81
82
  if (done) done("Unknown context");
82
83
  return;
83
84
  }
84
85
 
85
86
  // Recalculate with last input after config update
86
87
  if (shouldOutput) {
87
- const out = calculate(node.runtime.lastInput, node.runtime.inMin, node.runtime.inMax, node.runtime.outMin, node.runtime.outMax, node.runtime.clamp);
88
+ const out = calculate(node.lastInput, node.inMin, node.inMax, node.outMin, node.outMax, node.clamp);
88
89
  msg.payload = out;
89
- node.status({ fill: "blue", shape: "dot", text: `in: ${node.runtime.lastInput.toFixed(2)}, out: ${out.toFixed(2)}` });
90
+ utils.setStatusOK(node, `in: ${node.lastInput.toFixed(2)}, out: ${out.toFixed(2)}`);
90
91
  send(msg);
91
92
  }
92
93
  if (done) done();
93
94
  return;
94
95
  }
95
96
 
96
- // Validate input
97
- if (!msg.hasOwnProperty("payload")) {
98
- node.status({ fill: "red", shape: "ring", text: "missing input" });
97
+ // Get input from configured property
98
+ let input;
99
+ try {
100
+ input = RED.util.getMessageProperty(msg, node.inputProperty);
101
+ } catch (err) {
102
+ input = undefined;
103
+ }
104
+ if (input === undefined) {
105
+ utils.setStatusError(node, "missing or invalid input property");
99
106
  if (done) done();
100
107
  return;
101
108
  }
102
- const inputValue = parseFloat(msg.payload);
109
+ const inputValue = parseFloat(input);
103
110
  if (isNaN(inputValue) || !isFinite(inputValue)) {
104
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
111
+ utils.setStatusError(node, "invalid input");
105
112
  if (done) done();
106
113
  return;
107
114
  }
108
- if (node.runtime.inMax <= node.runtime.inMin) {
109
- node.status({ fill: "red", shape: "ring", text: "inMinx must be < inMax" });
115
+ if (node.inMax <= node.inMin) {
116
+ utils.setStatusError(node, "inMinx must be < inMax");
110
117
  if (done) done();
111
118
  return;
112
119
  }
113
120
 
114
121
  // Scale input
115
- node.runtime.lastInput = inputValue;
116
- const out = calculate(inputValue, node.runtime.inMin, node.runtime.inMax, node.runtime.outMin, node.runtime.outMax, node.runtime.clamp);
122
+ node.lastInput = inputValue;
123
+ const out = calculate(inputValue, node.inMin, node.inMax, node.outMin, node.outMax, node.clamp);
117
124
  msg.payload = out;
118
- node.status({ fill: "blue", shape: "dot", text: `in: ${inputValue.toFixed(2)}, out: ${out.toFixed(2)}` });
125
+ utils.setStatusOK(node, `in: ${inputValue.toFixed(2)}, out: ${out.toFixed(2)}`);
119
126
  send(msg);
120
127
 
121
128
  if (done) done();
@@ -1,32 +1,32 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function SineWaveBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
6
7
  // Initialize runtime state
7
- node.runtime = {
8
- name: config.name,
9
- lowerLimit: parseFloat(config.lowerLimit),
10
- upperLimit: parseFloat(config.upperLimit),
11
- period: (parseFloat(config.period)) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1),
12
- periodUnits: config.periodUnits,
13
- lastExecution: Date.now(),
14
- phase: 0
15
- };
8
+ // Initialize state
9
+ node.name = config.name;
10
+ node.lowerLimit = parseFloat(config.lowerLimit);
11
+ node.upperLimit = parseFloat(config.upperLimit);
12
+ node.period = (parseFloat(config.period)) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1);
13
+ node.periodUnits = config.periodUnits;
14
+ node.lastExecution = Date.now();
15
+ node.phase = 0;
16
16
 
17
17
  // Validate initial config
18
- if (isNaN(node.runtime.lowerLimit) || isNaN(node.runtime.upperLimit) || !isFinite(node.runtime.lowerLimit) || !isFinite(node.runtime.upperLimit)) {
19
- node.runtime.lowerLimit = 0;
20
- node.runtime.upperLimit = 100;
21
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
22
- } else if (node.runtime.lowerLimit > node.runtime.upperLimit) {
23
- node.runtime.upperLimit = node.runtime.lowerLimit;
24
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
18
+ if (isNaN(node.lowerLimit) || isNaN(node.upperLimit) || !isFinite(node.lowerLimit) || !isFinite(node.upperLimit)) {
19
+ node.lowerLimit = 0;
20
+ node.upperLimit = 100;
21
+ utils.setStatusError(node, "invalid limits");
22
+ } else if (node.lowerLimit > node.upperLimit) {
23
+ node.upperLimit = node.lowerLimit;
24
+ utils.setStatusError(node, "invalid limits");
25
25
  }
26
- if (isNaN(node.runtime.period) || node.runtime.period <= 0 || !isFinite(node.runtime.period)) {
27
- node.runtime.period = 10000;
28
- node.runtime.periodUnits = "milliseconds";
29
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
26
+ if (isNaN(node.period) || node.period <= 0 || !isFinite(node.period)) {
27
+ node.period = 10000;
28
+ node.periodUnits = "milliseconds";
29
+ utils.setStatusError(node, "invalid period");
30
30
  }
31
31
 
32
32
  node.on("input", function(msg, send, done) {
@@ -34,7 +34,7 @@ module.exports = function(RED) {
34
34
 
35
35
  // Guard against invalid message
36
36
  if (!msg) {
37
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
37
+ utils.setStatusError(node, "invalid message");
38
38
  if (done) done();
39
39
  return;
40
40
  }
@@ -42,62 +42,54 @@ module.exports = function(RED) {
42
42
  // Handle context updates
43
43
  if (msg.hasOwnProperty("context")) {
44
44
  if (!msg.hasOwnProperty("payload")) {
45
- node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
45
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
46
46
  if (done) done();
47
47
  return;
48
48
  }
49
49
  if (typeof msg.context !== "string") {
50
- node.status({ fill: "red", shape: "ring", text: "invalid context" });
50
+ utils.setStatusError(node, "invalid context");
51
51
  if (done) done();
52
52
  return;
53
53
  }
54
54
  let value = parseFloat(msg.payload);
55
55
  if (isNaN(value) || !isFinite(value)) {
56
- node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
56
+ utils.setStatusError(node, `invalid ${msg.context}`);
57
57
  if (done) done();
58
58
  return;
59
59
  }
60
60
  switch (msg.context) {
61
61
  case "lowerLimit":
62
- node.runtime.lowerLimit = value;
63
- if (node.runtime.lowerLimit > node.runtime.upperLimit) {
64
- node.runtime.upperLimit = node.runtime.lowerLimit;
65
- node.status({
66
- fill: "green",
67
- shape: "dot",
68
- text: `lower: ${node.runtime.lowerLimit.toFixed(2)}, upper adjusted to ${node.runtime.upperLimit.toFixed(2)}`
69
- });
62
+ node.lowerLimit = value;
63
+ if (node.lowerLimit > node.upperLimit) {
64
+ node.upperLimit = node.lowerLimit;
65
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}, upper adjusted to ${node.upperLimit.toFixed(2)}`);
70
66
  } else {
71
- node.status({ fill: "green", shape: "dot", text: `lower: ${node.runtime.lowerLimit.toFixed(2)}` });
67
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}`);
72
68
  }
73
69
  break;
74
70
  case "upperLimit":
75
- node.runtime.upperLimit = value;
76
- if (node.runtime.upperLimit < node.runtime.lowerLimit) {
77
- node.runtime.lowerLimit = node.runtime.upperLimit;
78
- node.status({
79
- fill: "green",
80
- shape: "dot",
81
- text: `upper: ${node.runtime.upperLimit.toFixed(2)}, lower adjusted to ${node.runtime.lowerLimit.toFixed(2)}`
82
- });
71
+ node.upperLimit = value;
72
+ if (node.upperLimit < node.lowerLimit) {
73
+ node.lowerLimit = node.upperLimit;
74
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}, lower adjusted to ${node.lowerLimit.toFixed(2)}`);
83
75
  } else {
84
- node.status({ fill: "green", shape: "dot", text: `upper: ${node.runtime.upperLimit.toFixed(2)}` });
76
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}`);
85
77
  }
86
78
  break;
87
79
  case "period":
88
80
  const multiplier = msg.units === "minutes" ? 60000 : msg.units === "seconds" ? 1000 : 1;
89
81
  value *= multiplier;
90
82
  if (value <= 0) {
91
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
83
+ utils.setStatusError(node, "invalid period");
92
84
  if (done) done();
93
85
  return;
94
86
  }
95
- node.runtime.period = value;
96
- node.runtime.periodUnits = msg.units || "milliseconds";
97
- node.status({ fill: "green", shape: "dot", text: `period: ${node.runtime.period.toFixed(2)} ms` });
87
+ node.period = value;
88
+ node.periodUnits = msg.units || "milliseconds";
89
+ utils.setStatusOK(node, `period: ${node.period.toFixed(2)} ms`);
98
90
  break;
99
91
  default:
100
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
92
+ utils.setStatusWarn(node, "unknown context");
101
93
  if (done) done("Unknown context");
102
94
  return;
103
95
  }
@@ -107,27 +99,27 @@ module.exports = function(RED) {
107
99
 
108
100
  // Calculate time difference
109
101
  const now = Date.now();
110
- const deltaTime = (now - node.runtime.lastExecution) / 1000; // Seconds
111
- node.runtime.lastExecution = now;
102
+ const deltaTime = (now - node.lastExecution) / 1000; // Seconds
103
+ node.lastExecution = now;
112
104
 
113
105
  // Return lowerLimit if period is invalid
114
- if (node.runtime.period <= 0) {
115
- node.status({ fill: "blue", shape: "dot", text: `out: ${node.runtime.lowerLimit.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}` });
116
- send({ payload: node.runtime.lowerLimit });
106
+ if (node.period <= 0) {
107
+ utils.setStatusOK(node, `out: ${node.lowerLimit.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
108
+ send({ payload: node.lowerLimit });
117
109
  if (done) done();
118
110
  return;
119
111
  }
120
112
 
121
113
  // Update phase
122
- node.runtime.phase = (node.runtime.phase + deltaTime / (node.runtime.period / 1000)) % 1;
114
+ node.phase = (node.phase + deltaTime / (node.period / 1000)) % 1;
123
115
 
124
116
  // Sine wave calculation
125
- const sineValue = Math.sin(2 * Math.PI * node.runtime.phase);
126
- const amplitude = (node.runtime.upperLimit - node.runtime.lowerLimit) / 2;
127
- const value = node.runtime.lowerLimit + amplitude * (sineValue + 1);
117
+ const sineValue = Math.sin(2 * Math.PI * node.phase);
118
+ const amplitude = (node.upperLimit - node.lowerLimit) / 2;
119
+ const value = node.lowerLimit + amplitude * (sineValue + 1);
128
120
 
129
121
  // Output new message
130
- node.status({ fill: "blue", shape: "dot", text: `out: ${value.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}` });
122
+ utils.setStatusOK(node, `out: ${value.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
131
123
  send({ payload: value });
132
124
 
133
125
  if (done) done();