Browse Source

add hysteresis

Luigi Cisana 7 years ago
parent
commit
fbf103f691
4 changed files with 52 additions and 18 deletions
  1. 3 1
      README.md
  2. 1 1
      package.json
  3. 15 2
      ramp-thermostat/ramp-thermostat.html
  4. 33 14
      ramp-thermostat/ramp-thermostat.js

+ 3 - 1
README.md

@@ -15,17 +15,19 @@ The target temperature is defined by a profile that provides the value depending
 
 A profile has at least 2 points and should typically start at 00:00 and end at 24:00.
 
+The Hysteresis is used to prevent osciliation. The `[+] value is added to the target and the `[-]` (absolute) value is subtracted from the target. Within this neutral zone no action accurs.
 
 ### Usage
 
 This node expects a numeric msg.payload containing the current temperature.
+The msg.topic should be `empty` or set to `setCurrent`. 
 It will calculate the target temperature depending on msg.payload at the current time and output 3 values:
 
 * state (boolean)
 * current temperature
 * target temperature
 
-The state (true/false) is used to control an actuator. The current and target temperature outputs can be wired e.g. into a ui_chart node.
+The state (true/false) is used to control an actuator. The current and target temperature outputs can be wired e.g. into an ui_chart node.
 
 ### Runtime settings
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
     "name"         : "node-red-contrib-ramp-thermostat",
-    "version"      : "0.2.1",
+    "version"      : "0.3.0",
     "description"  : "A Node-RED node that emulates a thermostat",
     "dependencies": {
     },

+ 15 - 2
ramp-thermostat/ramp-thermostat.html

@@ -3,10 +3,20 @@
         <label for="node-input-profile"><i class="fa fa-line-chart"></i> Profile</label>
         <input type="text" id="node-input-profile">
     </div>
+    <div class="form-row">
+        <label for="node-input-hysteresisplus"><i class="fa fa-arrows-v"></i> Hysteresis [+]</label>
+        <input type="text" id="node-input-hysteresisplus" style="width:50px">
+    </div>
+    <div class="form-row">
+        <label for="node-input-hysteresisminus"><i class="fa fa-arrows-v"></i> Hysteresis [-]</label>
+        <input type="text" id="node-input-hysteresisminus" style="width:50px">
+    </div>
     <div class="form-row">
         <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
         <input type="text" id="node-input-name" placeholder="Name">
     </div>
+    <div class="form-tips"><span>The Hysteresis is used to prevent osciliation. The [+] value is added to the target and the [-] (absolute) value is subtracted from the target. Within this neutral zone no action accurs.</span>
+    </div>
 </script>
 
 <script type="text/x-red" data-help-name="ramp-thermostat">
@@ -36,7 +46,9 @@
         color: '#E9967A',
         defaults: {
             name: {value:""},
-            profile: {value:"",type:"profile"}
+            profile: {value:"",type:"profile"},
+            hysteresisplus: {value:""},
+            hysteresisminus: {value:""}
         },
         inputs:1,
         outputs:3,
@@ -108,7 +120,8 @@
     <input class="input-append-left" type="text" id="node-config-input-time10" style="width:50px">
     <input type="text" id="node-config-input-temp10" style="margin-left:45px; width:50px">
   </div>
-  <div class="form-tips"><span>You can define up to 10 Points. A Profile has at least 2 Points and should typically start at 00:00 and end at 24:00.</span></div>
+  <div class="form-tips"><span>A Profile has at least 2 Points and should typically start at 00:00 and end at 24:00.</span>
+  </div>
 </script>
 
 <script type="text/javascript">

+ 33 - 14
ramp-thermostat/ramp-thermostat.js

@@ -23,10 +23,12 @@ module.exports = function(RED) {
       globalContext.set(node_name, this.profile);
     //}
     
+    this.h_plus = Math.abs(parseFloat(config.hysteresisplus)) || 0;
+    this.h_minus = Math.abs(parseFloat(config.hysteresisminus)) || 0;
+        
     this.status({fill:"green",shape:"dot",text:"profile set to "+this.profile.name});
     //this.warn(node_name+" - "+JSON.stringify(this.profile));
 
-
     this.on('input', function(msg) {
       
       var msg1 = {"topic":"state"};
@@ -42,13 +44,18 @@ module.exports = function(RED) {
           case "setCurrent":
           case "":
             if (!isNaN(msg.payload)) {
-              result = getState(msg.payload, this.profile);
+              result = this.getState(msg.payload, this.profile);
               
               if(isNaN(result.target)) {
                 this.warn("target undefined");
               }
               
-              msg1.payload = result.state;
+              if (result.state !== null) {
+                msg1.payload = result.state;
+              } else {
+                msg1 = null;
+              }
+              
               msg2.payload = msg.payload;
               msg3.payload = result.target;
               
@@ -99,11 +106,12 @@ module.exports = function(RED) {
   RED.nodes.registerType("ramp-thermostat",RampThermostat);
 
 
-/*
-*  ramp-thermostat specific functions
-*/
+  /**
+   *  ramp-thermostat specific functions
+   **/
 
-  function getState(current, profile) {
+  RampThermostat.prototype.getState = function(current, profile) {
+  //function getState(current, profile) {
   
     var point_mins, pre_mins, pre_target, point_target, target, gradient;
     var state;
@@ -122,22 +130,33 @@ module.exports = function(RED) {
       if (current_mins < point_mins) {
         gradient = (point_target - pre_target) / (point_mins - pre_mins);
         target = pre_target + (gradient * (current_mins - pre_mins));
-        target = target.toFixed(1);
         //console.log("k=" + k +" gradient " + gradient + " target " + target);             
         break;
       }
       pre_mins = point_mins;
       pre_target = point_target;
     }
+   
+    var target_plus = parseFloat((target + this.h_plus).toFixed(1));
+    var target_minus = parseFloat((target - this.h_minus).toFixed(1));
     
-    //console.log("current "+msg.payload+" target "+target);
-    
-    if (current < target) {
+    //this.warn(target_minus+" - "+target+" - "+target_plus);
+        
+    if (current > target_plus) {
+      state = false;
+      status = {fill:"grey",shape:"ring",text:current+" > "+target_plus+" ("+profile.name+")"};
+    } else if (current < target_minus) {
       state = true;
-      status = {fill:"yellow",shape:"dot",text:current+" < "+target+" ("+profile.name+")"};
+      status = {fill:"yellow",shape:"dot",text:current+" < "+target_minus+" ("+profile.name+")"};    
+    } else if (current == target_plus) {
+      state = null;
+      status = {fill:"grey",shape:"ring",text:current+" = "+target_plus+" ("+profile.name+")"}; 
+    } else if (current == target_minus) {
+      state = null;
+      status = {fill:"grey",shape:"ring",text:current+" = "+target_minus+" ("+profile.name+")"};  
     } else {
-      state = false;
-      status = {fill:"grey",shape:"ring",text:current+" > "+target+" ("+profile.name+")"};
+      state = null;
+      status = {fill:"grey",shape:"ring",text:target_minus+" < "+current+" < "+target_plus+" ("+profile.name+")"};    
     }
     
     return {"state":state, "target":target, "status":status};