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

BooleanLogicUltimate.js 9.0KB

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