You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BooleanLogicUltimate.js 6.3KB

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