ramp-thermostat.js 8.3 KB

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