Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

245 lines
6.9KB

  1. module.exports = function(RED) {
  2. function BooleanLogicUltimate(config) {
  3. RED.nodes.createNode(this,config);
  4. this.config = config;
  5. this.state = {};
  6. var node = this;
  7. node.config = config;
  8. var fs = require('fs');
  9. var decimal = /^\s*[+-]{0,1}\s*([\d]+(\.[\d]*)*)\s*$/
  10. // Helper for the config html, to be able to delete the peristent states file
  11. RED.httpAdmin.get("/stateoperation_delete", RED.auth.needsPermission('BooleanLogicUltimate.read'), function (req, res) {
  12. //node.send({ req: req });
  13. DeletePersistFile(req.query.nodeid);
  14. res.json({ status: 220 });
  15. });
  16. // Populate the state array with the persisten file
  17. if (node.config.persist == true) {
  18. try {
  19. var contents = fs.readFileSync("states/" + node.id.toString()).toString();
  20. if (typeof contents !== 'undefined') {
  21. node.state = JSON.parse(contents);
  22. node.status({fill: "blue",shape: "ring",text: "Loaded persistent states (" + Object.keys(node.state).length + " total)."});
  23. }
  24. } catch (error) {
  25. node.status({fill: "grey",shape: "ring",text: "No persistent states"});
  26. }
  27. } else {
  28. node.status({fill: "yellow",shape: "dot",text: "Waiting for input states"});
  29. }
  30. this.on('input', function (msg) {
  31. var topic = msg.topic;
  32. var payload = msg.payload;
  33. if (topic !== undefined && payload !== undefined) {
  34. var value = ToBoolean( payload );
  35. var state = node.state;
  36. state[topic] = value;
  37. // Save the state array to a perisistent file
  38. if (this.config.persist == true) {
  39. try {
  40. if (!fs.existsSync("states")) fs.mkdirSync("states");
  41. fs.writeFileSync("states/" + node.id.toString(),JSON.stringify(state));
  42. } catch (error) {
  43. node.status({fill: "red",shape: "dot",text: "Node cannot write to filesystem: " + error});
  44. }
  45. }
  46. // Do we have as many inputs as we expect?
  47. var keyCount = Object.keys(state).length;
  48. if( keyCount == node.config.inputCount ) {
  49. var resAND = CalculateResult("AND");
  50. var resOR = CalculateResult("OR");
  51. var resXOR = CalculateResult("XOR");
  52. if (node.config.filtertrue == "onlytrue") {
  53. if (!resAND) { resAND = null };
  54. if (!resOR) { resOR = null };
  55. if (!resXOR) { resXOR = null };
  56. }
  57. // Operation mode evaluation
  58. if (node.config.outputtriggeredby == "onlyonetopic") {
  59. if (typeof node.config.triggertopic !== "undefined"
  60. && node.config.triggertopic !== ""
  61. && msg.hasOwnProperty("topic") && msg.topic !==""
  62. && node.config.triggertopic === msg.topic)
  63. {
  64. SetResult(resAND, resOR, resXOR, node.config.topic);
  65. } else
  66. {
  67. node.status({ fill: "grey", shape: "ring", text: "Saved (" + (msg.hasOwnProperty("topic") ? msg.topic : "empty input topic") + ") " + value});
  68. }
  69. } else
  70. {
  71. SetResult(resAND, resOR, resXOR, node.config.topic);
  72. }
  73. }
  74. else if(keyCount > node.config.inputCount ) {
  75. node.warn(
  76. (node.config.name !== undefined && node.config.name.length > 0
  77. ? node.config.name : "BooleanLogicUltimate")
  78. + " [Logic]: More than the specified "
  79. + node.config.inputCount + " topics received, resetting. Will not output new value until " + node.config.inputCount + " new topics have been received.");
  80. node.state = {};
  81. DeletePersistFile(node.id);
  82. DisplayUnkownStatus();
  83. } else {
  84. node.status({ fill: "green", shape: "ring", text: " Arrived topic " + keyCount + " of " + node.config.inputCount});
  85. }
  86. }
  87. });
  88. this.on('close', function(removed, done) {
  89. if (removed) {
  90. // This node has been deleted
  91. // Delete persistent states on change/deploy
  92. DeletePersistFile(node.id);
  93. } else {
  94. // This node is being restarted
  95. }
  96. done();
  97. });
  98. function DeletePersistFile (_nodeid){
  99. // Detele the persist file
  100. var _node = RED.nodes.getNode(_nodeid); // Gets node object from nodeit, because when called from the config html, the node object is not defined
  101. try {
  102. if (fs.existsSync("states/" + _nodeid.toString())) fs.unlinkSync("states/" + _nodeid.toString());
  103. _node.status({fill: "red",shape: "ring",text: "Persistent states deleted ("+_nodeid.toString()+")."});
  104. } catch (error) {
  105. _node.status({fill: "red",shape: "ring",text: "Error deleting persistent file: " + error.toString()});
  106. }
  107. }
  108. function CalculateResult(_operation) {
  109. var res;
  110. if( _operation == "XOR") {
  111. res = PerformXOR();
  112. }
  113. else {
  114. // We need a starting value to perform AND and OR operations.
  115. var keys = Object.keys(node.state);
  116. res = node.state[keys[0]];
  117. for( var i = 1; i < keys.length; ++i ) {
  118. var key = keys[i];
  119. res = PerformSimpleOperation( _operation, res, node.state[key] );
  120. }
  121. }
  122. return res;
  123. }
  124. function PerformXOR()
  125. {
  126. // XOR = exclusively one input is true. As such, we just count the number of true values and compare to 1.
  127. var trueCount = 0;
  128. for( var key in node.state ) {
  129. if( node.state[key] ) {
  130. trueCount++;
  131. }
  132. }
  133. return trueCount == 1;
  134. }
  135. function PerformSimpleOperation( operation, val1, val2 ) {
  136. var res;
  137. if( operation === "AND" ) {
  138. res = val1 && val2;
  139. }
  140. else if( operation === "OR" ) {
  141. res = val1 || val2;
  142. }
  143. else {
  144. node.error( "Unknown operation: " + operation );
  145. }
  146. return res;
  147. }
  148. function ToBoolean( value ) {
  149. var res = false;
  150. if (typeof value === 'boolean') {
  151. res = value;
  152. }
  153. else if( typeof value === 'number' || typeof value === 'string' ) {
  154. // Is it formated as a decimal number?
  155. if( decimal.test( value ) ) {
  156. var v = parseFloat( value );
  157. res = v != 0;
  158. }
  159. else {
  160. res = value.toLowerCase() === "true";
  161. }
  162. }
  163. return res;
  164. };
  165. function DisplayStatus ( value ) {
  166. node.status(
  167. {
  168. fill: value ? "green" : "red",
  169. shape: value ? "dot" : "ring",
  170. text: value ? "true" : "false"
  171. }
  172. );
  173. };
  174. function DisplayUnkownStatus () {
  175. node.status(
  176. {
  177. fill: "gray",
  178. shape: "ring",
  179. text: "Reset due to unexpected new topic"
  180. });
  181. };
  182. function SetResult(_valueAND, _valueOR, _valueXOR, optionalTopic) {
  183. node.status({fill: "green",shape: "dot",text: "(AND)" + _valueAND + " (OR)" +_valueOR + " (XOR)" +_valueXOR});
  184. if (_valueAND!=null){
  185. var msgAND = {
  186. topic: optionalTopic === undefined ? "result" : optionalTopic,
  187. operation:"AND",
  188. payload: _valueAND
  189. };
  190. }
  191. if (_valueOR!=null){
  192. var msgOR = {
  193. topic: optionalTopic === undefined ? "result" : optionalTopic,
  194. operation:"OR",
  195. payload: _valueOR
  196. };
  197. }
  198. if (_valueXOR!=null){
  199. var msgXOR = {
  200. topic: optionalTopic === undefined ? "result" : optionalTopic,
  201. operation:"XOR",
  202. payload: _valueXOR
  203. };
  204. }
  205. node.send([msgAND,msgOR,msgXOR]);
  206. };
  207. }
  208. RED.nodes.registerType("BooleanLogicUltimate",BooleanLogicUltimate);
  209. }