@bldgblocks/node-red-contrib-control 0.1.30 → 0.1.32

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.
@@ -4,24 +4,19 @@ module.exports = function(RED) {
4
4
  function RateOfChangeNode(config) {
5
5
  RED.nodes.createNode(this, config);
6
6
  const node = this;
7
+ node.isBusy = false;
7
8
 
8
9
  // Initialize runtime state
9
10
  node.runtime = {
10
11
  maxSamples: parseInt(config.sampleSize),
11
12
  samples: [], // Array of {timestamp: Date, value: number}
12
13
  units: config.units || "minutes", // minutes, seconds, hours
13
- lastRate: null
14
+ lastRate: null,
15
+ minValid: parseFloat(config.minValid),
16
+ maxValid: parseFloat(config.maxValid)
14
17
  };
15
18
 
16
- // Evaluate typed-input properties
17
- try {
18
- node.runtime.minValid = parseFloat(RED.util.evaluateNodeProperty( config.minValid, config.minValidType, node ));
19
- node.runtime.maxValid = parseFloat(RED.util.evaluateNodeProperty( config.maxValid, config.maxValidType, node ));
20
- } catch (err) {
21
- node.error(`Error evaluating properties: ${err.message}`);
22
- }
23
-
24
- node.on("input", function(msg, send, done) {
19
+ node.on("input", async function(msg, send, done) {
25
20
  send = send || function() { node.send.apply(node, arguments); };
26
21
 
27
22
  // Guard against invalid msg
@@ -31,18 +26,49 @@ module.exports = function(RED) {
31
26
  return;
32
27
  }
33
28
 
34
- // Update typed-input properties if needed
35
- try {
36
- if (utils.requiresEvaluation(config.minValidType)) {
37
- node.runtime.minValid = parseFloat(RED.util.evaluateNodeProperty( config.minValid, config.minValidType, node, msg ));
38
- }
39
- if (utils.requiresEvaluation(config.maxValidType)) {
40
- node.runtime.maxValid = parseFloat(RED.util.evaluateNodeProperty( config.maxValid, config.maxValidType, node, msg ));
29
+ // Evaluate dynamic properties
30
+ try {
31
+
32
+ // Check busy lock
33
+ if (node.isBusy) {
34
+ // Update status to let user know they are pushing too fast
35
+ node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
36
+ if (done) done();
37
+ return;
41
38
  }
39
+
40
+ // Lock node during evaluation
41
+ node.isBusy = true;
42
+
43
+ // Begin evaluations
44
+ const evaluations = [];
45
+
46
+ evaluations.push(
47
+ utils.requiresEvaluation(config.minValidType)
48
+ ? utils.evaluateNodeProperty(config.minValid, config.minValidType, node, msg)
49
+ .then(val => parseFloat(val))
50
+ : Promise.resolve(node.runtime.minValid),
51
+ );
52
+
53
+ evaluations.push(
54
+ utils.requiresEvaluation(config.maxValidType)
55
+ ? utils.evaluateNodeProperty(config.maxValid, config.maxValidType, node, msg)
56
+ .then(val => parseFloat(val))
57
+ : Promise.resolve(node.runtime.maxValid),
58
+ );
59
+
60
+ const results = await Promise.all(evaluations);
61
+
62
+ // 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];
42
65
  } catch (err) {
43
66
  node.error(`Error evaluating properties: ${err.message}`);
44
67
  if (done) done();
45
68
  return;
69
+ } finally {
70
+ // Release, all synchronous from here on
71
+ node.isBusy = false;
46
72
  }
47
73
 
48
74
  // Acceptable fallbacks
@@ -4,19 +4,15 @@ module.exports = function(RED) {
4
4
  function StringBuilderBlockNode(config) {
5
5
  RED.nodes.createNode(this, config);
6
6
  const node = this;
7
+ node.isBusy = false;
8
+
7
9
  node.name = config.name;
10
+ node.in1 = config.in1;
11
+ node.in2 = config.in2;
12
+ node.in3 = config.in3;
13
+ node.in4 = config.in4;
8
14
 
9
- // Evaluate typed-input properties
10
- try {
11
- node.in1 = RED.util.evaluateNodeProperty( config.in1, config.in1Type, node );
12
- node.in2 = RED.util.evaluateNodeProperty( config.in2, config.in2Type, node );
13
- node.in3 = RED.util.evaluateNodeProperty( config.in3, config.in3Type, node );
14
- node.in4 = RED.util.evaluateNodeProperty( config.in4, config.in4Type, node );
15
- } catch (err) {
16
- node.error(`Error evaluating properties: ${err.message}`);
17
- }
18
-
19
- node.on("input", function(msg, send, done) {
15
+ node.on("input", async function(msg, send, done) {
20
16
  send = send || function() { node.send.apply(node, arguments); };
21
17
 
22
18
  if (!msg) {
@@ -24,25 +20,62 @@ module.exports = function(RED) {
24
20
  if (done) done();
25
21
  return;
26
22
  }
27
-
28
- // Update typed-input properties if needed
29
- try {
30
- if (utils.requiresEvaluation(config.in1Type)) {
31
- node.in1 = RED.util.evaluateNodeProperty( config.in1, config.in1Type, node, msg );
32
- }
33
- if (utils.requiresEvaluation(config.in2Type)) {
34
- node.in2 = RED.util.evaluateNodeProperty( config.in2, config.in2Type, node, msg );
35
- }
36
- if (utils.requiresEvaluation(config.in3Type)) {
37
- node.in3 = RED.util.evaluateNodeProperty( config.in3, config.in3Type, node, msg );
38
- }
39
- if (utils.requiresEvaluation(config.in4Type)) {
40
- node.in4 = RED.util.evaluateNodeProperty( config.in4, config.in4Type, node, msg );
23
+
24
+ // Evaluate dynamic properties
25
+ try {
26
+
27
+ // Check busy lock
28
+ if (node.isBusy) {
29
+ // Update status to let user know they are pushing too fast
30
+ node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
31
+ if (done) done();
32
+ return;
41
33
  }
34
+
35
+ // Lock node during evaluation
36
+ node.isBusy = true;
37
+
38
+ // Begin evaluations
39
+ const evaluations = [];
40
+
41
+ evaluations.push(
42
+ utils.requiresEvaluation(config.in1Type)
43
+ ? utils.evaluateNodeProperty(config.in1, config.in1Type, node, msg)
44
+ : Promise.resolve(node.in1),
45
+ );
46
+
47
+ evaluations.push(
48
+ utils.requiresEvaluation(config.in2Type)
49
+ ? utils.evaluateNodeProperty(config.in2, config.in2Type, node, msg)
50
+ : Promise.resolve(node.in2),
51
+ );
52
+
53
+ evaluations.push(
54
+ utils.requiresEvaluation(config.in3Type)
55
+ ? utils.evaluateNodeProperty(config.in3, config.in3Type, node, msg)
56
+ : Promise.resolve(node.in3),
57
+ );
58
+
59
+ evaluations.push(
60
+ utils.requiresEvaluation(config.in4Type)
61
+ ? utils.evaluateNodeProperty(config.in4, config.in4Type, node, msg)
62
+ : Promise.resolve(node.in4),
63
+ );
64
+
65
+ const results = await Promise.all(evaluations);
66
+
67
+ // Update runtime with evaluated values
68
+ if (results[0] != null) node.in1 = results[0];
69
+ if (results[1] != null) node.in2 = results[1];
70
+ if (results[2] != null) node.in3 = results[2];
71
+ if (results[3] != null) node.in4 = results[3];
42
72
  } catch (err) {
43
73
  node.error(`Error evaluating properties: ${err.message}`);
44
74
  if (done) done();
45
75
  return;
76
+ } finally {
77
+ // Release, all synchronous from here on
78
+ node.isBusy = false;
46
79
  }
47
80
 
48
81
  // Check required properties
@@ -4,29 +4,22 @@ module.exports = function(RED) {
4
4
  function TstatBlockNode(config) {
5
5
  RED.nodes.createNode(this, config);
6
6
  const node = this;
7
+ node.isBusy = false;
7
8
 
8
9
  // Store typed-input properties
9
- node.isHeating = config.isHeating;
10
- node.algorithm = config.algorithm;
11
10
  node.name = config.name;
12
-
13
- // Evaluate typed-input properties
14
- try {
15
- node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node ));
16
- node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node ));
17
- node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node ));
18
- node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node ));
19
- node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node ));
20
- node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node ));
21
- node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node ));
22
- node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node ));
23
- node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node ));
24
- node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node ));
25
- node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node ) === true;
26
- node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node );
27
- } catch (err) {
28
- node.error(`Error evaluating properties: ${err.message}`);
29
- }
11
+ node.setpoint = parseFloat(config.setpoint);
12
+ node.heatingSetpoint = parseFloat(config.heatingSetpoint);
13
+ node.coolingSetpoint = parseFloat(config.coolingSetpoint);
14
+ node.coolingOn = parseFloat(config.coolingOn);
15
+ node.coolingOff = parseFloat(config.coolingOff);
16
+ node.heatingOff = parseFloat(config.heatingOff);
17
+ node.heatingOn = parseFloat(config.heatingOn);
18
+ node.diff = parseFloat(config.diff);
19
+ node.anticipator = parseFloat(config.anticipator);
20
+ node.ignoreAnticipatorCycles = Math.floor(config.ignoreAnticipatorCycles);
21
+ node.isHeating = config.isHeating === true;
22
+ node.algorithm = config.algorithm;
30
23
 
31
24
  let above = false;
32
25
  let below = false;
@@ -36,7 +29,7 @@ module.exports = function(RED) {
36
29
  let cyclesSinceModeChange = 0;
37
30
  let modeChanged = false;
38
31
 
39
- node.on("input", function(msg, send, done) {
32
+ node.on("input", async function(msg, send, done) {
40
33
  send = send || function() { node.send.apply(node, arguments); };
41
34
 
42
35
  if (!msg) {
@@ -44,49 +37,130 @@ module.exports = function(RED) {
44
37
  if (done) done();
45
38
  return;
46
39
  }
47
-
48
- // Update typed-input properties if needed
49
- try {
50
- if (utils.requiresEvaluation(config.setpointType)) {
51
- node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node, msg ));
52
- }
53
- if (utils.requiresEvaluation(config.heatingSetpointType)) {
54
- node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node, msg ));
55
- }
56
- if (utils.requiresEvaluation(config.coolingSetpointType)) {
57
- node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node, msg ));
58
- }
59
- if (utils.requiresEvaluation(config.coolingOnType)) {
60
- node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node, msg ));
61
- }
62
- if (utils.requiresEvaluation(config.coolingOffType)) {
63
- node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node, msg ));
64
- }
65
- if (utils.requiresEvaluation(config.heatingOffType)) {
66
- node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node, msg ));
67
- }
68
- if (utils.requiresEvaluation(config.heatingOnType)) {
69
- node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node, msg ));
70
- }
71
- if (utils.requiresEvaluation(config.diffType)) {
72
- node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node, msg ));
73
- }
74
- if (utils.requiresEvaluation(config.anticipatorType)) {
75
- node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node, msg ));
76
- }
77
- if (utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)) {
78
- node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg ));
79
- }
80
- if (utils.requiresEvaluation(config.isHeatingType)) {
81
- node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node, msg ) === true;
82
- }
83
- if (utils.requiresEvaluation(config.algorithmType)) {
84
- node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node, msg );
40
+
41
+ // Evaluate dynamic properties
42
+ try {
43
+
44
+ // Check busy lock
45
+ if (node.isBusy) {
46
+ // Update status to let user know they are pushing too fast
47
+ node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
48
+ if (done) done();
49
+ return;
85
50
  }
51
+
52
+ // Lock node during evaluation
53
+ node.isBusy = true;
54
+
55
+ // Begin evaluations
56
+ const evaluations = [];
57
+
58
+ //0
59
+ evaluations.push(
60
+ utils.requiresEvaluation(config.setpointType)
61
+ ? utils.evaluateNodeProperty(config.setpoint, config.setpointType, node, msg)
62
+ .then(val => parseFloat(val))
63
+ : Promise.resolve(node.setpoint),
64
+ );
65
+ //1
66
+ evaluations.push(
67
+ utils.requiresEvaluation(config.heatingSetpointType)
68
+ ? utils.evaluateNodeProperty(config.heatingSetpoint, config.heatingSetpointType, node, msg)
69
+ .then(val => parseFloat(val))
70
+ : Promise.resolve(node.heatingSetpoint),
71
+ );
72
+ //2
73
+ evaluations.push(
74
+ utils.requiresEvaluation(config.coolingSetpointType)
75
+ ? utils.evaluateNodeProperty(config.coolingSetpoint, config.coolingSetpointType, node, msg)
76
+ .then(val => parseFloat(val))
77
+ : Promise.resolve(node.coolingSetpoint),
78
+ );
79
+ //3
80
+ evaluations.push(
81
+ utils.requiresEvaluation(config.coolingOnType)
82
+ ? utils.evaluateNodeProperty(config.coolingOn, config.coolingOnType, node, msg)
83
+ .then(val => parseFloat(val))
84
+ : Promise.resolve(node.coolingOn),
85
+ );
86
+ //4
87
+ evaluations.push(
88
+ utils.requiresEvaluation(config.coolingOffType)
89
+ ? utils.evaluateNodeProperty(config.coolingOff, config.coolingOffType, node, msg)
90
+ .then(val => parseFloat(val))
91
+ : Promise.resolve(node.coolingOff),
92
+ );
93
+ //5
94
+ evaluations.push(
95
+ utils.requiresEvaluation(config.heatingOffType)
96
+ ? utils.evaluateNodeProperty(config.heatingOff, config.heatingOffType, node, msg)
97
+ .then(val => parseFloat(val))
98
+ : Promise.resolve(node.heatingOff),
99
+ );
100
+ //6
101
+ evaluations.push(
102
+ utils.requiresEvaluation(config.heatingOnType)
103
+ ? utils.evaluateNodeProperty(config.heatingOn, config.heatingOnType, node, msg)
104
+ .then(val => parseFloat(val))
105
+ : Promise.resolve(node.heatingOn),
106
+ );
107
+ //7
108
+ evaluations.push(
109
+ utils.requiresEvaluation(config.diffType)
110
+ ? utils.evaluateNodeProperty(config.diff, config.diffType, node, msg)
111
+ .then(val => parseFloat(val))
112
+ : Promise.resolve(node.diff),
113
+ );
114
+ //8
115
+ evaluations.push(
116
+ utils.requiresEvaluation(config.anticipatorType)
117
+ ? utils.evaluateNodeProperty(config.anticipator, config.anticipatorType, node, msg)
118
+ .then(val => parseFloat(val))
119
+ : Promise.resolve(node.anticipator),
120
+ );
121
+ //9
122
+ evaluations.push(
123
+ utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)
124
+ ? utils.evaluateNodeProperty(config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg)
125
+ .then(val => Math.floor(val))
126
+ : Promise.resolve(node.ignoreAnticipatorCycles),
127
+ );
128
+ //10
129
+ evaluations.push(
130
+ utils.requiresEvaluation(config.isHeatingType)
131
+ ? utils.evaluateNodeProperty(config.isHeating, config.isHeatingType, node, msg)
132
+ .then(val => val === true)
133
+ : Promise.resolve(node.isHeating),
134
+ );
135
+ //11
136
+ evaluations.push(
137
+ utils.requiresEvaluation(config.algorithmType)
138
+ ? utils.evaluateNodeProperty(config.algorithm, config.algorithmType, node, msg)
139
+ : Promise.resolve(node.algorithm),
140
+ );
141
+
142
+ const results = await Promise.all(evaluations);
143
+
144
+ // Update runtime with evaluated values
145
+ if (!isNaN(results[0])) node.setpoint = results[0];
146
+ if (!isNaN(results[1])) node.heatingSetpoint = results[1];
147
+ if (!isNaN(results[2])) node.coolingSetpoint = results[2];
148
+ if (!isNaN(results[3])) node.coolingOn = results[3];
149
+ if (!isNaN(results[4])) node.coolingOff = results[4];
150
+ if (!isNaN(results[5])) node.heatingOff = results[5];
151
+ if (!isNaN(results[6])) node.heatingOn = results[6];
152
+ if (!isNaN(results[7])) node.diff = results[7];
153
+ if (!isNaN(results[8])) node.anticipator = results[8];
154
+ if (!isNaN(results[9])) node.ignoreAnticipatorCycles = results[9];
155
+ if (results[10] !== null) node.isHeating = results[10];
156
+ if (results[11]) node.algorithm = results[11];
86
157
  } catch (err) {
87
158
  node.error(`Error evaluating properties: ${err.message}`);
88
159
  if (done) done();
89
160
  return;
161
+ } finally {
162
+ // Release, all synchronous from here on
163
+ node.isBusy = false;
90
164
  }
91
165
 
92
166
  // Handle configuration messages
@@ -320,7 +394,7 @@ module.exports = function(RED) {
320
394
  }
321
395
  above = false;
322
396
  } else {
323
- if (input > coolingOn) {
397
+ if (input > node.coolingOn) {
324
398
  above = true;
325
399
  } else if (above && input < node.coolingOff + effectiveAnticipator) {
326
400
  above = false;
package/nodes/utils.js CHANGED
@@ -1,10 +1,25 @@
1
1
  module.exports = function(RED) {
2
2
  function requiresEvaluation(type) { return type === "flow" || type === "global" || type === "msg"; }
3
-
4
3
 
4
+ // Safe evaluation helper (promisified)
5
+ function evaluateNodeProperty(value, type, node, msg) {
6
+ return new Promise((resolve, reject) => {
7
+ if (!this.requiresEvaluation(type)) {
8
+ resolve(value); // Return raw value for static types
9
+ } else {
10
+ RED.util.evaluateNodeProperty(
11
+ value, type, node, msg,
12
+ (err, result) => err ? reject(err) : resolve(result)
13
+ );
14
+ }
15
+ });
16
+ }
17
+
18
+ // Usage:
5
19
  // const utils = require('./utils')(RED);
6
20
 
7
21
  return {
8
- requiresEvaluation
22
+ requiresEvaluation,
23
+ evaluateNodeProperty
9
24
  };
10
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bldgblocks/node-red-contrib-control",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Sedona-inspired control nodes for Node-RED",
5
5
  "keywords": [ "node-red", "sedona", "control", "hvac" ],
6
6
  "files": ["nodes/*.js", "nodes/*.html"],
@@ -67,7 +67,7 @@
67
67
  }
68
68
  },
69
69
  "author": "buildingblocks",
70
- "license": "MIT",
70
+ "license": "Apache-2.0",
71
71
  "repository": {
72
72
  "type": "git",
73
73
  "url": "git+https://github.com/BldgBlocks/node-red-contrib-control.git"