diff --git a/liquidFlowHandler.html b/liquidFlowHandler.html index b98a278..ebb171d 100644 --- a/liquidFlowHandler.html +++ b/liquidFlowHandler.html @@ -1,28 +1,51 @@ + + diff --git a/liquidFlowHandler.js b/liquidFlowHandler.js index ea9f62d..2559c07 100644 --- a/liquidFlowHandler.js +++ b/liquidFlowHandler.js @@ -1,6 +1,26 @@ +const nameOfNode = "liquidFlowHandler"; +const nodeClass = require('./src/nodeClass.js'); +const { MenuManager } = require('generalFunctions'); + module.exports = function(RED) { - function liquidFlowHandler(config) { - RED.nodes.createNode(this, config); + // 1) Register the node type and delegate to your class + RED.nodes.registerType(nameOfNode, function(config) { + RED.nodes.createNode(this, config); + this.nodeClass = new nodeClass(config, RED, this, nameOfNode); + }); + + // 2) Setup the dynamic menu endpoint + const menuMgr = new MenuManager(); + + // Serve /liquidFlowHandler/menu.js + RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => { + try { + const script = menuMgr.createEndpoint(nameOfNode, ['logger']); + res.type('application/javascript').send(script); + } catch (err) { + res.status(500).send(`// Error generating menu: ${err.message}`); } - RED.nodes.registerType("liquidFlowHandler", liquidFlowHandler); -}; \ No newline at end of file + }); + + +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d9b7e0f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "liquidflowhandler", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "liquidflowhandler", + "version": "0.0.1", + "license": "SEE LICENSE", + "dependencies": { + "generalFunctions": "git+https://gitea.centraal.wbd-rd.nl/p.vanderwilt/generalFunctions.git" + } + }, + "node_modules/generalFunctions": { + "version": "1.0.0", + "resolved": "git+https://gitea.centraal.wbd-rd.nl/p.vanderwilt/generalFunctions.git#f13ee68938ea9d4b3a17ad90618c72634769c777", + "license": "SEE LICENSE" + } + } +} diff --git a/src/SpecificClass.js b/src/SpecificClass.js new file mode 100644 index 0000000..803dcc5 --- /dev/null +++ b/src/SpecificClass.js @@ -0,0 +1,8 @@ + +class liquidFlowHandler { + constructor(config) { + this.config = config; + } +} + +module.exports = { liquidFlowHandler }; \ No newline at end of file diff --git a/src/nodeClass.js b/src/nodeClass.js new file mode 100644 index 0000000..91a6098 --- /dev/null +++ b/src/nodeClass.js @@ -0,0 +1,133 @@ +/** + * node class.js + * + * Encapsulates all node logic in a reusable class. In future updates we can split this into multiple generic classes and use the config to specifiy which ones to use. + * This allows us to keep the Node-RED node clean and focused on wiring up the UI and event handlers. + */ +const Specific = require("./specificClass.js"); + +class nodeClass { + /** + * Create a Node. + * @param {object} uiConfig - Node-RED node configuration. + * @param {object} RED - Node-RED runtime API. + */ + constructor(uiConfig, RED, nodeInstance, nameOfNode) { + + // Preserve RED reference for HTTP endpoints if needed + this.node = nodeInstance; // This is the Node-RED node instance, we can use this to send messages and update status + this.RED = RED; // This is the Node-RED runtime API, we can use this to create endpoints if needed + this.name = nameOfNode; // This is the name of the node, it should match the file name and the node type in Node-RED + this.source = null; // Will hold the specific class instance + this.config = null; // Will hold the merged configuration + + // Load default & UI config + this._loadConfig(uiConfig); + + // Instantiate core class + this._setupSpecificClass(); + + // Wire up event and lifecycle handlers + // this._registerChild(); + this._startTickLoop(); + // this._attachInputHandler(); + this._attachCloseHandler(); + } + + /** + * Load and merge default config with user-defined settings. + * @param {object} uiConfig - Raw config from Node-RED UI. + */ + _loadConfig(uiConfig) { + + // Merge UI config over defaults + this.config = { + general: { + name: uiConfig.name, + id: this.node.id, // node.id is for the child registration process + unit: uiConfig.unit, // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards) + logging: { + enabled: uiConfig.enableLog, + logLevel: uiConfig.logLevel + } + }, + functionality: { + positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified + } + }; + } + + /** + * Instantiate the specific class and store as source. + */ + _setupSpecificClass() { + + this.source = new Specific(this.config); + + //store in node + this.node.source = this.source; // Store the source in the node instance for easy access + } + + /** + * Register this node as a child upstream and downstream. + * Delayed to avoid Node-RED startup race conditions. + */ + _registerChild() { + setTimeout(() => { + this.node.send([ + null, + null, + { topic: 'registerChild', payload: this.config.general.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' }, + ]); + }, 100); + } + + /** + * Start the periodic tick loop. + */ + _startTickLoop() { + setTimeout(() => { + this._tickInterval = setInterval(() => this._tick(), 1000); + }, 1000); + } + + /** + * Execute a single tick and send outputs. + */ + _tick() { + processMsg = null; + influxMsg = null; + + // Send only updated outputs on ports 0 & 1 + this.node.send([processMsg, influxMsg, null]); + } + + /** + * Attach the node's input handler, routing control messages to the class. + */ + _attachInputHandler() { + this.node.on('input', (msg, send, done) => { + /* Update to complete event based node by putting the tick function after an input event */ + switch(msg.topic) { + case 'registerChild': + // Register this node as a child of the parent node + const childId = msg.payload; + const childObj = this.RED.nodes.getNode(childId); + this.source.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); + break; + } + }); + } + + /** + * Clean up timers and intervals when Node-RED stops the node. + */ + _attachCloseHandler() { + this.node.on('close', (done) => { + clearInterval(this._tickInterval); + done(); + }); + } +} + +module.exports = nodeClass;