選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

BooleanLogicUltimate.js 8.6KB

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