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 this.upstreamReactor = null; this.returnPump = null; // state variables this.F_in = 0; // debit in this.Cs_in = new Array(13).fill(0); // Concentrations in this.C_TS = 2500; // Total solids concentration sludge } get getEffluent() { switch (this.config.model) { case "mb-model": return this._mbModel(); case "t-model": return this._tModel(); default: this.logger.error(`Unknown settler model: ${this.config.model}`); } } /** * Mass balance model. * * - assumes complete seperation of solids and effluent * - assumes known solids concentration given by sensor or set manually * - assumes primacy of recirculation flow, with leftover sludge being designated as waste sludge */ _mbModel() { // constrain flow to prevent negatives const F_s = Math.min((this.F_in * this.Cs_in[12]) / this.C_TS, this.F_in); const F_eff = this.F_in - F_s; let F_sr = 0; if (this.returnPump) { F_sr = Math.min(this.returnPump.measurements.type("flow").variant("measured").position("atEquipment").getCurrentValue(), F_s); } const F_so = F_s - F_sr; // effluent const Cs_eff = structuredClone(this.Cs_in); if (F_s > 0) { Cs_eff[7] = 0; Cs_eff[8] = 0; Cs_eff[9] = 0; Cs_eff[10] = 0; Cs_eff[11] = 0; Cs_eff[12] = 0; } // sludge const Cs_s = structuredClone(this.Cs_in); if (F_s > 0) { Cs_s[7] = this.F_in * this.Cs_in[7] / F_s; Cs_s[8] = this.F_in * this.Cs_in[8] / F_s; Cs_s[9] = this.F_in * this.Cs_in[9] / F_s; Cs_s[10] = this.F_in * this.Cs_in[10] / F_s; Cs_s[11] = this.F_in * this.Cs_in[11] / F_s; Cs_s[12] = this.F_in * this.Cs_in[12] / F_s; } return [ { topic: "Fluent", payload: { inlet: 0, F: F_eff, C: Cs_eff } }, // Effluent { topic: "Fluent", payload: { inlet: 1, F: F_so, C: Cs_s } }, // Sludge sink { topic: "Fluent", payload: { inlet: 2, F: F_sr, C: Cs_s } } // Sludge recirculation ]; } /** * Takacs model (Not implemented) * * More mechanistic model */ _tModel() { this.logger.error("Not implemented yet."); return [ { topic: "Fluent", payload: { inlet: 0, F: null, C: null } }, // Effluent { topic: "Fluent", payload: { inlet: 1, F: null, C: null } }, // Sludge sink { topic: "Fluent", payload: { inlet: 2, F: null, C: null } } // Sludge recirculation ]; } registerChild(child, softwareType) { if(!child) { this.logger.error(`Invalid ${softwareType} child provided.`); return; } switch (softwareType) { case "measurement": this.logger.debug(`Registering measurement child...`); this._connectMeasurement(child); break; case "reactor": this.logger.debug(`Registering reactor child...`); this._connectReactor(child); break; case "machine": this.logger.debug(`Registering machine child...`); this._connectMachine(child); break; default: this.logger.error(`Unrecognized softwareType: ${softwareType}`); } } _connectMeasurement(measurementChild) { const position = measurementChild.config.functionality.positionVsParent; const measurementType = measurementChild.config.asset.type; const eventName = `${measurementType}.measured.${position}`; // Register event listener for measurement updates measurementChild.measurements.emitter.on(eventName, (eventData) => { this.logger.debug(`${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`); // Store directly in parent's measurement container this.measurements .type(measurementType) .variant("measured") .position(position) .value(eventData.value, eventData.timestamp, eventData.unit); this._updateMeasurement(measurementType, eventData.value, position, eventData); }); } _connectReactor(reactorChild) { if (reactorChild.config.functionality.positionVsParent != "upstream") { this.logger.warn("Reactor children of settlers should be upstream."); } this.upstreamReactor = reactorChild; reactorChild.emitter.on("stateChange", (eventData) => { this.logger.debug(`State change of upstream reactor detected.`); const effluent = this.upstreamReactor.getEffluent[0]; this.F_in = effluent.payload.F; this.Cs_in = effluent.payload.C; }); } _connectMachine(machineChild) { if (machineChild.config.functionality.positionVsParent == "downstream") { machineChild.upstreamSource = this; this.returnPump = machineChild; return; } this.logger.warn(`Failed to register machine child.`); } _updateMeasurement(measurementType, value, position, context) { switch(measurementType) { case "quantity (tss)": this.C_TS = value; break; default: this.logger.error(`Type '${measurementType}' not recognized for measured update.`); return; } } } module.exports = { Settler };