Browse Source

Initial Upload

Luigi Cisana 7 years ago
parent
commit
aa006a8e0d
6 changed files with 269 additions and 0 deletions
  1. 8 0
      .gitignore
  2. 25 0
      README.md
  3. 14 0
      package.json
  4. BIN
      ramp-thermostat/icons/trigger.png
  5. 140 0
      ramp-thermostat/ramp-thermostat.html
  6. 82 0
      ramp-thermostat/ramp-thermostat.js

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+
+# Mac
+.DS_Store
+
+# Node
+node_modules/
+npm-debug.log
+.node-version

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+
+### ramp-thermostat
+A Node-RED contrib-node that emulates a thermostat.
+
+The ramp-thermostat controls a heating-device such a valve depending on the actual input temperature and the target temperature.
+
+The target temperature is defined by a profile that provides the value depending on the current time `00:00-24:00`. The profile consists of several points whose connections build a sequence of lines. The switching moment can be optimized by defing a gradient line like a `ramp`.
+
+The node provides 3 outputs:
+
+* state (boolean)
+* actual temperature
+* target temperature
+
+### Installation
+
+Change directory to your node red installation:
+
+    $ npm install node-red-contrib-ramp-thermostat
+
+### Configuration
+
+Define a profile which consists of up to 10 Points.
+A Profile has at least 2 Points. The first Point must start at 00:00 and the last at 24:00.
+The target temperature is calculated depending on the actual time.

+ 14 - 0
package.json

@@ -0,0 +1,14 @@
+{
+    "name"         : "node-red-contrib-ramp-thermostat",
+    "version"      : "0.1.0",
+    "description"  : "A Node-RED node that emulates a thermostat",
+    "license"      : "ISC",
+    "dependencies" : {
+    },
+    "keywords": [ "node-red" ],
+    "node-red"     : {
+        "nodes": {
+            "ramp-thermostat": "ramp-thermostat/ramp-thermostat.js"
+        }
+    }
+}

BIN
ramp-thermostat/icons/trigger.png


+ 140 - 0
ramp-thermostat/ramp-thermostat.html

@@ -0,0 +1,140 @@
+<script type="text/x-red" data-template-name="ramp-thermostat">
+    <div class="form-row">
+        <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-name"><i class="fa fa-tag"></i> Name</label>
+        <input type="text" id="node-input-name" placeholder="Name">
+    </div>
+</script>
+
+<script type="text/x-red" data-help-name="ramp-thermostat">
+    <p>A node that emulates a thermostat.</p>
+    <p>The ramp-thermostat controls a heating-device such a valve
+    depending on the actual input temperature and the target temperature.</p>
+    <p>The target temperature is defined by a profile that
+    provides the value depending on the current time <code>00:00-24:00</code>.
+    The profile consists of several points whose connections build a sequence of lines.
+    The switching moment can be optimized by defing a gradient line like a <code>ramp</code>.</p>
+    <p>The node provides 3 outputs:</p>
+      <ul>
+        <li><code>state (boolean)</code></li>
+        <li><code>actual temperature</code></li>
+        <li><code>target temperature</code></li>
+      </ul>
+</script>
+
+<script type="text/javascript">
+    RED.nodes.registerType('ramp-thermostat',{
+        category: 'smart home',
+        color: '#E9967A',
+        defaults: {
+            name: {value:""},
+            profile: {value:"",type:"profile"}
+        },
+        inputs:1,
+        outputs:3,
+        icon: "trigger.png",
+        label: function() {
+            return this.name||"ramp-thermostat";
+        },
+        paletteLabel: 'thermostat'
+    });
+</script>
+
+<script type="text/x-red" data-template-name="profile">
+  <div class="form-row">
+    <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
+    <input type="text" id="node-config-input-name">
+  </div>
+  
+  <div>
+    <label style="float:left; margin-bottom:10px"><i class="fa fa-dot-circle-o"></i> Points:</label>
+    <label style="float:left;margin-left:45px;"> Time (hh:mm)&nbsp;&nbsp&nbsp;Temp (°)</label>
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time1"><i class="fa fa-dot-circle-o"></i> #1</label>
+    <input class="input-append-left" type="text" id="node-config-input-time1" style="width:50px">
+    <input type="text" id="node-config-input-temp1" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time2"><i class="fa fa-dot-circle-o"></i> #2</label>
+    <input class="input-append-left" type="text" id="node-config-input-time2" style="width:50px">
+    <input type="text" id="node-config-input-temp2" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time3"><i class="fa fa-dot-circle-o"></i> #3</label>
+    <input class="input-append-left" type="text" id="node-config-input-time3" style="width:50px">
+    <input type="text" id="node-config-input-temp3" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time4"><i class="fa fa-dot-circle-o"></i> #4</label>
+    <input class="input-append-left" type="text" id="node-config-input-time4" style="width:50px">
+    <input type="text" id="node-config-input-temp4" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time5"><i class="fa fa-dot-circle-o"></i> #5</label>
+    <input class="input-append-left" type="text" id="node-config-input-time5" style="width:50px">
+    <input type="text" id="node-config-input-temp5" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time6"><i class="fa fa-dot-circle-o"></i> #6</label>
+    <input class="input-append-left" type="text" id="node-config-input-time6" style="width:50px">
+    <input type="text" id="node-config-input-temp6" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time7"><i class="fa fa-dot-circle-o"></i> #7</label>
+    <input class="input-append-left" type="text" id="node-config-input-time7" style="width:50px">
+    <input type="text" id="node-config-input-temp7" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time8"><i class="fa fa-dot-circle-o"></i> #8</label>
+    <input class="input-append-left" type="text" id="node-config-input-time8" style="width:50px">
+    <input type="text" id="node-config-input-temp8" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time9"><i class="fa fa-dot-circle-o"></i> #9</label>
+    <input class="input-append-left" type="text" id="node-config-input-time9" style="width:50px">
+    <input type="text" id="node-config-input-temp9" style="margin-left:45px; width:50px">
+  </div>
+  <div class="form-row">
+    <label for="node-config-input-time10"><i class="fa fa-dot-circle-o"></i> #10</label>
+    <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.<br>
+The first Point must start at 00:00 and the last at 24:00.</span></div>
+</script>
+
+<script type="text/javascript">
+    RED.nodes.registerType('profile',{
+        category: 'config',
+        defaults: {
+            name: {value:'',required:true},
+            time1: {value:''},
+            temp1: {value:''},
+            time2: {value:''},
+            temp2: {value:''},
+            time3: {value:''},
+            temp3: {value:''},
+            time4: {value:''},
+            temp4: {value:''},
+            time5: {value:''},
+            temp5: {value:''},
+            time6: {value:''},
+            temp6: {value:''},
+            time7: {value:''},
+            temp7: {value:''},
+            time8: {value:''},
+            temp8: {value:''},
+            time9: {value:''},
+            temp9: {value:''},
+            time10: {value:''},
+            temp10: {value:''},
+        },
+        label: function() {
+            return this.name;
+        }
+    });
+</script>

+ 82 - 0
ramp-thermostat/ramp-thermostat.js

@@ -0,0 +1,82 @@
+module.exports = function(RED) {
+  "use strict";
+  
+  function Profile(n) {
+    RED.nodes.createNode(this,n);
+    this.name = n.name;
+    var timei, tempi, arr, minutes;
+    this.points = {};
+    
+    var points_str = '{';
+    
+    for (var i=1; i<=10; i++) {
+      timei = "time"+i;
+      tempi = "temp"+i;
+      if (typeof(n[timei]) !== "undefined" && n[timei] !== "") {
+        arr = n[timei].split(":");
+        minutes = parseInt(arr[0])*60 + parseInt(arr[1]);
+        points_str += '"' + minutes + '":"' + n[tempi] + '",'; 
+      }
+    }
+    points_str = points_str.slice(0,points_str.length-1);
+    points_str += '}';
+    
+    //console.log(points_str);
+    
+    this.points = JSON.parse(points_str);
+  }
+  RED.nodes.registerType("profile",Profile);
+  
+  function RampThermostat(config) {
+    RED.nodes.createNode(this,config);
+    var node = this;
+       
+    this.profile = RED.nodes.getNode(config.profile);
+    //node.warn(JSON.stringify(this.profile.points));
+    
+    this.on('input', function(msg) {
+    
+      var msg1 = {"topic":"state"};
+      var msg2 = {"topic":"actual"};
+      var msg3 = {"topic":"target"};
+      
+      var point_mins, pre_mins, pre_target, point_target, target, gradient;
+      
+      var date = new Date();
+      var actual_mins = date.getHours()*60 + date.getMinutes();
+
+      for (var k in this.profile.points) {
+          point_mins = parseInt(k);
+          //node.warn("mins " + point_mins + " temp " + this.profile.points[k]);
+          point_target = parseFloat(this.profile.points[k]);
+          
+          if (actual_mins < point_mins) {
+              gradient = (point_target - pre_target) / (point_mins - pre_mins);
+              target = pre_target + (gradient * (actual_mins - pre_mins));
+              target = target.toFixed(1);
+              //node.warn(gradient + " target " + target);
+              break;
+          }
+          pre_mins = point_mins;
+          pre_target = point_target;
+      }
+
+      //node.warn("actual "+msg.payload+" target "+target);
+      
+      if (msg.payload < target) {
+          msg1.payload = true;
+          this.status({fill:"yellow",shape:"dot",text:msg.payload+" < "+target+" ("+this.profile.name+")"});
+      } else {
+          msg1.payload = false;
+          this.status({fill:"grey",shape:"ring",text:msg.payload+" > "+target+" ("+this.profile.name+")"});
+      }
+      
+      msg2.payload = msg.payload;
+      msg3.payload = target;
+      
+      node.send([msg1, msg2, msg3]);
+    
+    });
+  }
+  RED.nodes.registerType("ramp-thermostat",RampThermostat);
+}