From f70fa15ef5b838e83aaabe30d98f08cc0298531f Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 23 Jul 2025 14:46:03 +0200
Subject: [PATCH] Basic EVOLV setup
---
liquidFlowHandler.html | 61 +++++++++++++------
liquidFlowHandler.js | 28 +++++++--
package-lock.json | 21 +++++++
src/SpecificClass.js | 8 +++
src/nodeClass.js | 133 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 228 insertions(+), 23 deletions(-)
create mode 100644 package-lock.json
create mode 100644 src/SpecificClass.js
create mode 100644 src/nodeClass.js
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;