ramp-thermostat.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. module.exports = function(RED) {
  2. "use strict";
  3. function Profile(n) {
  4. RED.nodes.createNode(this,n);
  5. this.n = n;
  6. this.name = n.name;
  7. }
  8. RED.nodes.registerType("profile",Profile);
  9. function RampThermostat(config) {
  10. RED.nodes.createNode(this, config);
  11. var node_name = this.name.replace(" ", "_");
  12. var globalContext = this.context().global;
  13. // experimental
  14. //this.profile = globalContext.get(node_name);
  15. //if (typeof this.profile === "undefined") {
  16. this.profile = RED.nodes.getNode(config.profile);
  17. this.profile.points = getPoints(this.profile.n);
  18. globalContext.set(node_name, this.profile);
  19. //}
  20. this.h_plus = Math.abs(parseFloat(config.hysteresisplus)) || 0;
  21. this.h_minus = Math.abs(parseFloat(config.hysteresisminus)) || 0;
  22. this.status({fill:"green",shape:"dot",text:"profile set to "+this.profile.name});
  23. //this.warn(node_name+" - "+JSON.stringify(this.profile));
  24. this.on('input', function(msg) {
  25. var msg1 = {"topic":"state"};
  26. var msg2 = {"topic":"current"};
  27. var msg3 = {"topic":"target"};
  28. var result = {};
  29. //this.warn(JSON.stringify(msg));
  30. if (typeof msg.payload !== "undefined") {
  31. switch (msg.topic) {
  32. case "setCurrent":
  33. case "":
  34. if (!isNaN(msg.payload)) {
  35. result = this.getState(msg.payload, this.profile);
  36. if(isNaN(result.target)) {
  37. this.warn("target undefined");
  38. }
  39. if (result.state !== null) {
  40. msg1.payload = result.state;
  41. } else {
  42. msg1 = null;
  43. }
  44. msg2.payload = msg.payload;
  45. msg3.payload = result.target;
  46. this.send([msg1, msg2, msg3]);
  47. } else {
  48. node.warn("Non numeric input");
  49. }
  50. break;
  51. case "setTarget":
  52. result = setTarget(msg.payload);
  53. if (result.isValid) {
  54. this.profile = result.profile;
  55. globalContext.set(node_name, this.profile);
  56. }
  57. break;
  58. case "setProfile":
  59. //this.warn(JSON.stringify(msg.payload));
  60. result = setProfile(msg.payload);
  61. if (result.found) {
  62. this.profile = result.profile;
  63. if (this.profile.name === "default") {
  64. this.profile = RED.nodes.getNode(config.profile);
  65. this.profile.points = getPoints(this.profile.n);
  66. //this.warn("default "+this.profile.name);
  67. result.status = {fill:"green",shape:"dot",text:"profile set to default ("+this.profile.name+")"};
  68. }
  69. globalContext.set(node_name, this.profile);
  70. } else {
  71. this.warn(msg.payload+" not found");
  72. }
  73. break;
  74. default:
  75. this.warn("invalid topic");
  76. }
  77. } else {
  78. this.warn("msg.payload undefined");
  79. }
  80. this.status(result.status);
  81. });
  82. }
  83. RED.nodes.registerType("ramp-thermostat",RampThermostat);
  84. /**
  85. * ramp-thermostat specific functions
  86. **/
  87. RampThermostat.prototype.getState = function(current, profile) {
  88. //function getState(current, profile) {
  89. var point_mins, pre_mins, pre_target, point_target, target, gradient;
  90. var state;
  91. var status = {};
  92. var date = new Date();
  93. var current_mins = date.getHours()*60 + date.getMinutes();
  94. //console.log("name " + profile.name + " profile.points " + JSON.stringify(profile.points));
  95. for (var k in profile.points) {
  96. point_mins = parseInt(k);
  97. //console.log("mins " + point_mins + " temp " + profile.points[k]);
  98. point_target = profile.points[k];
  99. if (current_mins < point_mins) {
  100. gradient = (point_target - pre_target) / (point_mins - pre_mins);
  101. target = pre_target + (gradient * (current_mins - pre_mins));
  102. //console.log("k=" + k +" gradient " + gradient + " target " + target);
  103. break;
  104. }
  105. pre_mins = point_mins;
  106. pre_target = point_target;
  107. }
  108. var target_plus = parseFloat((target + this.h_plus).toFixed(1));
  109. var target_minus = parseFloat((target - this.h_minus).toFixed(1));
  110. //this.warn(target_minus+" - "+target+" - "+target_plus);
  111. if (current > target_plus) {
  112. state = false;
  113. status = {fill:"grey",shape:"ring",text:current+" > "+target_plus+" ("+profile.name+")"};
  114. } else if (current < target_minus) {
  115. state = true;
  116. status = {fill:"yellow",shape:"dot",text:current+" < "+target_minus+" ("+profile.name+")"};
  117. } else if (current == target_plus) {
  118. state = null;
  119. status = {fill:"grey",shape:"ring",text:current+" = "+target_plus+" ("+profile.name+")"};
  120. } else if (current == target_minus) {
  121. state = null;
  122. status = {fill:"grey",shape:"ring",text:current+" = "+target_minus+" ("+profile.name+")"};
  123. } else {
  124. state = null;
  125. status = {fill:"grey",shape:"ring",text:target_minus+" < "+current+" < "+target_plus+" ("+profile.name+")"};
  126. }
  127. return {"state":state, "target":target, "status":status};
  128. }
  129. function setTarget(target) {
  130. var valid;
  131. var status = {};
  132. var profile = {};
  133. if (typeof target === "string") {
  134. target = parseFloat(target);
  135. }
  136. if (typeof target === "number") {
  137. profile.name = "manual";
  138. profile.points = {"0":target, "1440":target};
  139. valid = true;
  140. status = {fill:"green",shape:"dot",text:"set target to "+target+" ("+profile.name+")"};
  141. } else {
  142. valid = false;
  143. status = {fill:"red",shape:"dot",text:"invalid type of target"};
  144. }
  145. return {"profile":profile, "status":status, "isValid": valid};
  146. }
  147. function setProfile(input) {
  148. var found = false;
  149. var status = {};
  150. var profile = {};
  151. //var count = 0;
  152. var type = typeof input;
  153. switch (type) {
  154. case "string":
  155. if (input === "default") {
  156. profile.name = "default";
  157. found = true;
  158. } else {
  159. RED.nodes.eachNode(function(n) {
  160. if (n.type === "profile" && n.name === input) {
  161. profile.n = n;
  162. profile.name = n.name;
  163. profile.points = getPoints(profile.n);
  164. found = true;
  165. }
  166. //count++;
  167. });
  168. //console.log("count " + count);
  169. if (found) {
  170. status = {fill:"green",shape:"dot",text:"profile set to "+profile.name};
  171. } else {
  172. status = {fill:"red",shape:"dot",text:profile.name+" not found"};
  173. }
  174. }
  175. break;
  176. case "object":
  177. profile.name = input.name || "input profile";
  178. var points = {};
  179. var arr, minutes;
  180. for (var k in input.points) {
  181. arr = k.split(":");
  182. minutes = parseInt(arr[0])*60 + parseInt(arr[1]);
  183. points[minutes] = input.points[k];
  184. }
  185. profile.points = points;
  186. found = true;
  187. status = {fill:"green",shape:"dot",text:"profile set to "+profile.name};
  188. //console.log(points);
  189. break;
  190. default:
  191. status = {fill:"red",shape:"dot",text:"invalid type "+type};
  192. }
  193. return {"profile":profile, "status":status, "found":found};
  194. }
  195. function getPoints(n) {
  196. var timei, tempi, arr, minutes;
  197. var points = {};
  198. var points_str = '{';
  199. for (var i=1; i<=10; i++) {
  200. timei = "time"+i;
  201. tempi = "temp"+i;
  202. if (typeof(n[timei]) !== "undefined" && n[timei] !== "") {
  203. arr = n[timei].split(":");
  204. minutes = parseInt(arr[0])*60 + parseInt(arr[1]);
  205. points_str += '"' + minutes + '":' + n[tempi] + ',';
  206. }
  207. }
  208. points_str = points_str.slice(0,points_str.length-1);
  209. points_str += '}';
  210. //console.log(points_str);
  211. points = JSON.parse(points_str);
  212. return points;
  213. }
  214. }