From 51dc56cc85af36037d4a2d9d21ddc8be4f1874bc Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 22 Oct 2025 16:07:42 +0200
Subject: [PATCH] Add initial implementation of the settler node for Node-RED
- Create package.json to define project metadata and dependencies
- Implement settler.html for the node's UI in Node-RED
- Add settler.js to register the settler node type and handle HTTP requests
- Introduce src/nodeClass.js for the settler node class functionality
- Define src/specificClass.js for settler-specific logic and utilities
---
package.json | 30 ++++++++++++
settler.html | 86 ++++++++++++++++++++++++++++++++
settler.js | 26 ++++++++++
src/nodeClass.js | 114 +++++++++++++++++++++++++++++++++++++++++++
src/specificClass.js | 15 ++++++
5 files changed, 271 insertions(+)
create mode 100644 package.json
create mode 100644 settler.html
create mode 100644 settler.js
create mode 100644 src/nodeClass.js
create mode 100644 src/specificClass.js
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1503baf
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "settler",
+ "version": "0.0.1",
+ "description": "Sludge settler model for Node-Red",
+ "repository": {
+ "type": "git",
+ "url": "https://gitea.centraal.wbd-rd.nl/p.vanderwilt/settler.git"
+ },
+ "keywords": [
+ "settler",
+ "activated sludge",
+ "wastewater",
+ "node-red"
+ ],
+ "license": "SEE LICENSE",
+ "author": "P.R. van der Wilt",
+ "main": "settler.js",
+ "scripts": {
+ "test": "node settler.js"
+ },
+ "node-red": {
+ "nodes": {
+ "settler": "settler.js"
+ }
+ },
+ "dependencies": {
+ "generalFunctions": "git+https://gitea.centraal.wbd-rd.nl/RnD/generalFunctions.git",
+ "mathjs": "^14.5.2"
+ }
+}
diff --git a/settler.html b/settler.html
new file mode 100644
index 0000000..429f818
--- /dev/null
+++ b/settler.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settler.js b/settler.js
new file mode 100644
index 0000000..06f81a1
--- /dev/null
+++ b/settler.js
@@ -0,0 +1,26 @@
+const nameOfNode = "settler"; // name of the node, should match file name and node type in Node-RED
+const nodeClass = require('./src/nodeClass.js'); // node class
+const { MenuManager } = require('generalFunctions');
+
+
+module.exports = function (RED) {
+ // Register the node type
+ RED.nodes.registerType(nameOfNode, function (config) {
+ // Initialize the Node-RED node first
+ RED.nodes.createNode(this, config);
+ // Then create your custom class and attach it
+ this.nodeClass = new nodeClass(config, RED, this, nameOfNode);
+ });
+
+ const menuMgr = new MenuManager();
+
+ // Serve /settler/menu.js
+ RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => {
+ try {
+ const script = menuMgr.createEndpoint(nameOfNode, ['logger', 'position']);
+ res.type('application/javascript').send(script);
+ } catch (err) {
+ res.status(500).send(`// Error generating menu: ${err.message}`);
+ }
+ });
+};
\ No newline at end of file
diff --git a/src/nodeClass.js b/src/nodeClass.js
new file mode 100644
index 0000000..2d16352
--- /dev/null
+++ b/src/nodeClass.js
@@ -0,0 +1,114 @@
+const { Settler } = require('./specificClass.js');
+
+
+class nodeClass {
+ /**
+ * Node-RED node class for settler.
+ * @param {object} uiConfig - Node-RED node configuration
+ * @param {object} RED - Node-RED runtime API
+ * @param {object} nodeInstance - Node-RED node instance
+ * @param {string} nameOfNode - Name of the node
+ */
+ constructor(uiConfig, RED, nodeInstance, nameOfNode) {
+ // Preserve RED reference for HTTP endpoints if needed
+ this.node = nodeInstance;
+ this.RED = RED;
+ this.name = nameOfNode;
+ this.source = null;
+
+ this._loadConfig(uiConfig)
+ this._setupClass();
+
+ this._attachInputHandler();
+ this._registerChild();
+ this._startTickLoop();
+ this._attachCloseHandler();
+ }
+
+ /**
+ * Handle node-red input messages
+ */
+ _attachInputHandler() {
+ this.node.on('input', (msg, send, done) => {
+
+ switch (msg.topic) {
+ case 'registerChild':
+ // Register this node as a parent of the child node
+ const childId = msg.payload;
+ const childObj = this.RED.nodes.getNode(childId);
+ this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
+ break;
+ default:
+ console.log("Unknown topic: " + msg.topic);
+ }
+
+ if (done) {
+ done();
+ }
+ });
+ }
+
+ /**
+ * Parse node configuration
+ * @param {object} uiConfig Config set in UI in node-red
+ */
+ _loadConfig(uiConfig) {
+ this.config = {
+ general: {
+ name: uiConfig.name || this.name,
+ id: this.node.id,
+ unit: null,
+ logging: {
+ enabled: uiConfig.enableLog,
+ logLevel: uiConfig.logLevel
+ }
+ },
+ functionality: {
+ positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified
+ softwareType: "settler" // should be set in config manager
+ }
+ }
+ }
+
+ /**
+ * 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.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' }
+ ]);
+ }, 100);
+ }
+
+ /**
+ * Setup settler class
+ */
+ _setupClass() {
+
+ this.source = new Settler(this.config); // protect from reassignment
+ this.node.source = this.source;
+ }
+
+ _startTickLoop() {
+ setTimeout(() => {
+ this._tickInterval = setInterval(() => this._tick(), 1000);
+ }, 1000);
+ }
+
+ _tick(){
+ this.node.send([null, null, null]);
+ }
+
+ _attachCloseHandler() {
+ this.node.on('close', (done) => {
+ clearInterval(this._tickInterval);
+ done();
+ });
+ }
+}
+
+module.exports = nodeClass;
\ No newline at end of file
diff --git a/src/specificClass.js b/src/specificClass.js
new file mode 100644
index 0000000..32589f9
--- /dev/null
+++ b/src/specificClass.js
@@ -0,0 +1,15 @@
+const { childRegistrationUtils, logger, MeasurementContainer } = require('generalFunctions');
+const EventEmitter = require('events');
+
+class Settler {
+ constructor(config) {
+ this.config = config;
+ // EVOLV stuff
+ this.logger = new logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, config.general.name);
+ this.emitter = new EventEmitter();
+ this.measurements = new MeasurementContainer();
+ this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
+ }
+}
+
+module.exports = { Settler };
\ No newline at end of file