Compare commits
5 Commits
9b1af8ffa2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e69825a48e | ||
|
|
9dea685db7 | ||
|
|
e2e2001647 | ||
|
|
604781f573 | ||
|
|
167628a436 |
102
LICENSE
102
LICENSE
@@ -1,15 +1,97 @@
|
||||
MIT License
|
||||
OPENBARE LICENTIE VAN DE EUROPESE UNIE v. 1.2.
|
||||
EUPL © Europese Unie 2007, 2016
|
||||
Deze openbare licentie van de Europese Unie („EUPL”) is van toepassing op het werk (zoals hieronder gedefinieerd) dat onder de voorwaarden van deze licentie wordt verstrekt. Elk gebruik van het werk dat niet door deze licentie is toegestaan, is verboden (voor zover dit gebruik valt onder een recht van de houder van het auteursrecht op het werk). Het werk wordt verstrekt onder de voorwaarden van deze licentie wanneer de licentiegever (zoals hieronder gedefinieerd), direct volgend op de kennisgeving inzake het auteursrecht op het werk, de volgende kennisgeving opneemt:
|
||||
In licentie gegeven krachtens de EUPL
|
||||
of op een andere wijze zijn bereidheid te kennen heeft gegeven krachtens de EUPL in licentie te geven.
|
||||
|
||||
Copyright (c) 2025 Janneke Tack / Rene De Ren
|
||||
1.Definities
|
||||
In deze licentie wordt verstaan onder:
|
||||
— „de licentie”:de onderhavige licentie;
|
||||
— „het oorspronkelijke werk”:het werk dat of de software die door de licentiegever krachtens deze licentie wordt verspreid of medegedeeld, en dat/die beschikbaar is als broncode en, in voorkomend geval, ook als uitvoerbare code;
|
||||
— „bewerkingen”:de werken of software die de licentiehouder kan creëren op grond van het oorspronkelijke werk of wijzigingen ervan. In deze licentie wordt niet gedefinieerd welke mate van wijziging of afhankelijkheid van het oorspronkelijke werk vereist is om een werk als een bewerking te kunnen aanmerken; dat wordt bepaald conform het auteursrecht dat van toepassing is in de in artikel 15 bedoelde staat;
|
||||
— „het werk”:het oorspronkelijke werk of de bewerkingen ervan;
|
||||
— „de broncode”:de voor mensen leesbare vorm van het werk, die het gemakkelijkste door mensen kan worden bestudeerd en gewijzigd;
|
||||
— „de uitvoerbare code”:elke code die over het algemeen is gecompileerd en is bedoeld om door een computer als een programma te worden uitgevoerd;
|
||||
— „de licentiegever”:de natuurlijke of rechtspersoon die het werk krachtens de licentie verspreidt of mededeelt;
|
||||
— „bewerker(s)”:elke natuurlijke of rechtspersoon die het werk krachtens de licentie wijzigt of op een andere wijze bijdraagt tot de totstandkoming van een bewerking;
|
||||
— „de licentiehouder” of „u”:elke natuurlijke of rechtspersoon die het werk onder de voorwaarden van de licentie gebruikt; — „verspreiding” of „mededeling”:het verkopen, geven, uitlenen, verhuren, verspreiden, mededelen, doorgeven, of op een andere wijze online of offline beschikbaar stellen van kopieën van het werk of het verlenen van toegang tot de essentiële functies ervan ten behoeve van andere natuurlijke of rechtspersonen.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to use,
|
||||
copy, modify, merge, publish, and distribute the Software for **personal, scientific, or educational purposes**, subject to the following conditions:
|
||||
2.Draagwijdte van de uit hoofde van de licentie verleende rechten
|
||||
De licentiegever verleent u hierbij een wereldwijde, royaltyvrije, niet-exclusieve, voor een sublicentie in aanmerking komende licentie, om voor de duur van het aan het oorspronkelijke werk verbonden auteursrecht, het volgende te doen:
|
||||
— het werk in alle omstandigheden en voor ongeacht welk doel te gebruiken;
|
||||
— het werk te verveelvoudigen;
|
||||
— het werk te wijzigen en op grond van het werk bewerkingen te ontwikkelen;
|
||||
— het werk aan het publiek mede te delen, waaronder het recht om het werk of kopieën ervan aan het publiek ter beschikking te stellen of te vertonen, en het werk, in voorkomend geval, in het openbaar uit te voeren;
|
||||
— het werk of kopieën ervan te verspreiden;
|
||||
— het werk of kopieën ervan uit te lenen en te verhuren;
|
||||
— de rechten op het werk of op kopieën ervan in sublicentie te geven.
|
||||
Deze rechten kunnen worden uitgeoefend met gebruikmaking van alle thans bekende of nog uit te vinden media, dragers en formaten, voor zover het toepasselijke recht dit toestaat. In de landen waar immateriële rechten van toepassing zijn, doet de licentiegever afstand van zijn recht op uitoefening van zijn immateriële rechten in de mate die door het toepasselijke recht wordt toegestaan teneinde een doeltreffende uitoefening van de bovenvermelde in licentie gegeven economische rechten mogelijk te maken. De licentiegever verleent de licentiehouder een royaltyvrij, niet-exclusief gebruiksrecht op alle octrooien van de licentiegever, voor zover dit noodzakelijk is om de uit hoofde van deze licentie verleende rechten op het werk te gebruiken.
|
||||
|
||||
**Commercial use of the Software or any derivative work is explicitly prohibited without prior written consent from the authors.**
|
||||
This includes but is not limited to resale, inclusion in paid products or services, and monetized distribution.
|
||||
Any commercial usage must be governed by a shared license or explicit contractual agreement with the authors.
|
||||
3.Mededeling van de broncode
|
||||
De licentiegever kan het werk verstrekken in zijn broncode of als uitvoerbare code. Indien het werk als uitvoerbare code wordt verstrekt, verstrekt de licentiegever bij elke door hem verspreide kopie van het werk tevens een machinaal leesbare kopie van de broncode van het werk of geeft hij in een mededeling, volgende op de bij het werk gevoegde auteursrechtelijke kennisgeving, de plaats aan waar de broncode gemakkelijk en vrij toegankelijk is, zolang de licentiegever het werk blijft verspreiden of mededelen.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
4.Beperkingen van het auteursrecht
|
||||
Geen enkele bepaling in deze licentie heeft ten doel de licentiehouder het recht te ontnemen een beroep te doen op een uitzondering op of een beperking van de exclusieve rechten van de rechthebbenden op het werk, of op de uitputting van die rechten of andere toepasselijke beperkingen daarvan.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED...
|
||||
5.Verplichtingen van de licentiehouder
|
||||
De verlening van de bovenvermelde rechten is onderworpen aan een aantal aan de licentiehouder opgelegde beperkingen en verplichtingen. Het gaat om de onderstaande verplichtingen.
|
||||
|
||||
Attributierecht: de licentiehouder moet alle auteurs-, octrooi- of merkenrechtelijke kennisgevingen onverlet laten alsook alle kennisgevingen die naar de licentie en de afwijzing van garanties verwijzen. De licentiehouder moet een afschrift van deze kennisgevingen en een afschrift van de licentie bij elke kopie van het werk voegen die hij verspreidt of mededeelt. De licentiehouder moet in elke bewerking duidelijk aangeven dat het werk is gewijzigd, en eveneens de datum van wijziging vermelden.
|
||||
|
||||
Copyleftclausule: wanneer de licentiehouder kopieën van het oorspronkelijke werk of bewerkingen verspreidt of mededeelt, geschiedt die verspreiding of mededeling onder de voorwaarden van deze licentie of van een latere versie van deze licentie, tenzij het oorspronkelijke werk uitdrukkelijk alleen onder deze versie van de licentie wordt verspreid — bijvoorbeeld door de mededeling „alleen EUPL v. 1.2”. De licentiehouder (die licentiegever wordt) kan met betrekking tot het werk of de bewerkingen geen aanvullende bepalingen of voorwaarden opleggen of stellen die de voorwaarden van de licentie wijzigen of beperken.
|
||||
|
||||
Verenigbaarheidsclausule: wanneer de licentiehouder bewerkingen of kopieën ervan verspreidt of mededeelt die zijn gebaseerd op het werk en op een ander werk dat uit hoofde van een verenigbare licentie in licentie is gegeven, kan die verspreiding of mededeling geschieden onder de voorwaarden van deze verenigbare licentie. Voor de toepassing van deze clausule wordt onder „verenigbare licentie” verstaan, de licenties die in het aanhangsel bij deze licentie zijn opgesomd. Indien de verplichtingen van de licentiehouder uit hoofde van de verenigbare licentie in strijd zijn met diens verplichtingen uit hoofde van deze licentie, hebben de verplichtingen van de verenigbare licentie voorrang.
|
||||
|
||||
Verstrekking van de broncode: bij de verspreiding of mededeling van kopieën van het werk verstrekt de licentiehouder een machinaal leesbare kopie van de broncode of geeft hij aan waar deze broncode gemakkelijk en vrij toegankelijk is, zolang de licentiehouder het werk blijft verspreiden of mededelen.
|
||||
|
||||
Juridische bescherming: deze licentie verleent geen toestemming om handelsnamen, handelsmerken, dienstmerken of namen van de licentiegever te gebruiken, behalve wanneer dit op grond van een redelijk en normaal gebruik noodzakelijk is om de oorsprong van het werk te beschrijven en de inhoud van de auteursrechtelijke kennisgeving te herhalen.
|
||||
|
||||
6.Auteursketen
|
||||
De oorspronkelijke licentiegever garandeert dat hij houder is van het hierbij verleende auteursrecht op het oorspronkelijke werk dan wel dat dit hem in licentie is gegeven en dat hij de bevoegdheid heeft de licentie te verlenen. Elke bewerker garandeert dat hij houder is van het auteursrecht op de door hem aan het werk aangebrachte wijzigingen dan wel dat dit hem in licentie is gegeven en dat hij de bevoegdheid heeft de licentie te verlenen. Telkens wanneer u de licentie aanvaardt, verlenen de oorspronkelijke licentiegever en de opeenvolgende bewerkers u een licentie op hun bijdragen aan het werk onder de voorwaarden van deze licentie.
|
||||
|
||||
7.Uitsluiting van garantie
|
||||
Het werk is een werk in ontwikkeling, dat voortdurend door vele bewerkers wordt verbeterd. Het is een onvoltooid werk, dat bijgevolg nog tekortkomingen of programmeerfouten („bugs”) kan vertonen, die onlosmakelijk verbonden zijn met dit soort ontwikkeling. Om die reden wordt het werk op grond van de licentie verstrekt „zoals het is” en zonder enige garantie met betrekking tot het werk te geven, met inbegrip van, maar niet beperkt tot garanties met betrekking tot de verhandelbaarheid, de geschiktheid voor een specifiek doel, de afwezigheid van tekortkomingen of fouten, de nauwkeurigheid, de eerbiediging van andere intellectuele-eigendomsrechten dan het in artikel 6 van deze licentie bedoelde auteursrecht. Deze uitsluiting van garantie is een essentieel onderdeel van de licentie en een voorwaarde voor de verlening van rechten op het werk.
|
||||
|
||||
8.Uitsluiting van aansprakelijkheid
|
||||
Behoudens in het geval van een opzettelijke fout of directe schade aan natuurlijke personen, is de licentiegever in geen enkel geval aansprakelijk voor ongeacht welke directe of indirecte, materiële of immateriële schade die voortvloeit uit de licentie of het gebruik van het werk, met inbegrip van, maar niet beperkt tot schade als gevolg van het verlies van goodwill, verloren werkuren, een computerdefect of computerfout, het verlies van gegevens, of enige andere commerciële schade, zelfs indien de licentiegever werd gewezen op de mogelijkheid van dergelijke schade. De licentiegever is echter aansprakelijk op grond van de wetgeving inzake productaansprakelijkheid, voor zover deze wetgeving op het werk van toepassing is.
|
||||
|
||||
9.Aanvullende overeenkomsten
|
||||
Bij de verspreiding van het werk kunt u ervoor kiezen een aanvullende overeenkomst te sluiten, waarin de verplichtingen of diensten overeenkomstig deze licentie worden omschreven. Indien deze verplichtingen worden aanvaard, kunt u echter alleen in eigen naam en onder eigen verantwoordelijkheid handelen, en dus niet in naam van de oorspronkelijke licentiegever of een bewerker, en kunt u voorts alleen handelen indien u ermee instemt alle bewerkers schadeloos te stellen, te verdedigen of te vrijwaren met betrekking tot de aansprakelijkheid van of vorderingen tegen deze bewerkers op grond van het feit dat u een garantie of aanvullende aansprakelijkheid hebt aanvaard.
|
||||
|
||||
10.Aanvaarding van de licentie
|
||||
De bepalingen van deze licentie kunnen worden aanvaard door te klikken op het pictogram „Ik ga akkoord”, dat zich bevindt onderaan het venster waarin de tekst van deze licentie is weergegeven, of door overeenkomstig de toepasselijke wetsbepalingen op een soortgelijke wijze met de licentie in te stemmen. Door op dat pictogram te klikken geeft u aan dat u deze licentie en alle voorwaarden ervan ondubbelzinnig en onherroepelijk aanvaardt. Evenzo aanvaardt u onherroepelijk deze licentie en alle voorwaarden ervan door uitoefening van de rechten die u in artikel 2 van deze licentie zijn verleend, zoals het gebruik van het werk, het creëren door u van een bewerking of de verspreiding of mededeling door u van het werk of kopieën ervan.
|
||||
|
||||
11.Voorlichting van het publiek
|
||||
Indien u het werk verspreidt of mededeelt door middel van elektronische communicatiemiddelen (bijvoorbeeld door voor te stellen het werk op afstand te downloaden), moet het distributiekanaal of het medium (bijvoorbeeld een website) het publiek ten minste de gegevens verschaffen die door het toepasselijke recht zijn voorgeschreven met betrekking tot de licentiegever, de licentie en de wijze waarop deze kan worden geraadpleegd, gesloten, opgeslagen en gereproduceerd door de licentiehouder.
|
||||
|
||||
12.Einde van de licentie
|
||||
De licentie en de uit hoofde daarvan verleende rechten eindigen automatisch bij elke inbreuk door de licentiehouder op de voorwaarden van de licentie. Dit einde beëindigt niet de licenties van personen die het werk van de licentiehouder krachtens de licentie hebben ontvangen, mits deze personen zich volledig aan de licentie houden.
|
||||
|
||||
13.Overige
|
||||
Onverminderd artikel 9 vormt de licentie de gehele overeenkomst tussen de partijen met betrekking tot het werk. Indien een bepaling van de licentie volgens het toepasselijke recht ongeldig is of niet uitvoerbaar is, doet dit geen afbreuk aan de geldigheid of uitvoerbaarheid van de licentie in haar geheel. Deze bepaling dient zodanig te worden uitgelegd of gewijzigd dat zij geldig en uitvoerbaar wordt. De Europese Commissie kan, voor zover dit noodzakelijk en redelijk is, versies in andere talen of nieuwe versies van deze licentie of geactualiseerde versies van dit aanhangsel publiceren, zonder de draagwijdte van de uit hoofde van de licentie verleende rechten te beperken. Nieuwe versies van de licentie zullen worden gepubliceerd met een uniek versienummer. Alle door de Europese Commissie goedgekeurde taalversies van deze licentie hebben dezelfde waarde. De partijen kunnen zich beroepen op de taalversie van hun keuze.
|
||||
|
||||
14.Bevoegd gerecht
|
||||
Onverminderd specifieke overeenkomsten tussen de partijen,
|
||||
— vallen alle geschillen tussen de instellingen, organen en instanties van de Europese Unie, als licentiegeefster, en een licentiehouder in verband met de uitlegging van deze licentie onder de bevoegdheid van het Hof van Justitie van de Europese Unie, conform artikel 272 van het Verdrag betreffende de werking van de Europese Unie,
|
||||
— vallen alle geschillen tussen andere partijen in verband met de uitlegging van deze licentie onder de uitsluitende bevoegdheid van het bevoegde gerecht van de plaats waar de licentiegever is gevestigd of zijn voornaamste activiteit uitoefent.
|
||||
|
||||
15.Toepasselijk recht
|
||||
Onverminderd specifieke overeenkomsten tussen de partijen,
|
||||
— wordt deze licentie beheerst door het recht van de lidstaat van de Europese Unie waar de licentiegever zijn statutaire zetel, verblijfplaats of hoofdkantoor heeft,
|
||||
— wordt deze licentie beheerst door het Belgische recht indien de licentiegever geen statutaire zetel, verblijfplaats of hoofdkantoor heeft in een lidstaat van de Europese Unie.
|
||||
|
||||
|
||||
Aanhangsel
|
||||
„Verenigbare licenties” in de zin van artikel 5 EUPL zijn:
|
||||
— GNU General Public License (GPL) v. 2, v. 3
|
||||
— GNU Affero General Public License (AGPL) v. 3
|
||||
— Open Software License (OSL) v. 2.1, v. 3.0
|
||||
— Eclipse Public License (EPL) v. 1.0
|
||||
— CeCILL v. 2.0, v. 2.1
|
||||
— Mozilla Public Licence (MPL) v. 2
|
||||
— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||
— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) voor andere werken dan software
|
||||
— European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||
— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) of Strong Reciprocity (LiLiQ-R+).
|
||||
De Europese Commissie kan dit aanhangsel actualiseren in geval van latere versies van de bovengenoemde licenties zonder dat er een nieuwe EUPL-versie wordt ontwikkeld, zolang die versies de uit hoofde van artikel 2 van deze licentie verleende rechten verlenen en ze de betrokken broncode beschermen tegen exclusieve toe-eigening.
|
||||
Voor alle andere wijzigingen van of aanvullingen op dit aanhangsel is de ontwikkeling van een nieuwe EUPL-versie vereist.
|
||||
290
dependencies/valveClass.js
vendored
290
dependencies/valveClass.js
vendored
@@ -1,290 +0,0 @@
|
||||
/**
|
||||
* @file valveClass.js
|
||||
*
|
||||
* Permission is hereby granted to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to use it for personal
|
||||
....
|
||||
*/
|
||||
|
||||
|
||||
//load local dependencies #NOTE: Vul hier nog de juiste dependencies in als meer nodig of sommige niet nodig
|
||||
const EventEmitter = require('events');
|
||||
const Logger = require('../../generalFunctions/helper/logger');
|
||||
const State = require('../../generalFunctions/helper/state/state');
|
||||
const Predict = require('../../predict/dependencies/predict/predict_class');
|
||||
const { MeasurementContainer } = require('../../generalFunctions/helper/measurements/index');
|
||||
|
||||
//load all config modules #NOTE: Vul hier nog de juiste dependencies in als meer nodig of sommige niet nodig
|
||||
const defaultConfig = require('./valveConfig.json');
|
||||
const ConfigUtils = require('../../generalFunctions/helper/configUtils');
|
||||
|
||||
//load registration utility #NOTE: Vul hier nog de juiste dependencies in als meer nodig of sommige niet nodig
|
||||
const ChildRegistrationUtils = require('../../generalFunctions/helper/childRegistrationUtils');
|
||||
|
||||
class Valve {
|
||||
constructor(valveConfig = {}, stateConfig = {}) {
|
||||
this.emitter = new EventEmitter(); // nodig voor ontvangen en uitvoeren van events emit() en on() --> Zien als internet berichten (niet bedraad in node-red)
|
||||
this.configUtils = new ConfigUtils(defaultConfig); // nodig voor het ophalen van de default configuaratie
|
||||
this.config = this.configUtils.initConfig(valveConfig); //valve configurations die bij invoer in node-red worden gegeven
|
||||
|
||||
// Initialize measurements
|
||||
this.measurements = new MeasurementContainer();
|
||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
||||
|
||||
// Init after config is set
|
||||
this.logger = new Logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, this.config.general.name);
|
||||
this.state = new State(stateConfig, this.logger); // Init State manager and pass logger
|
||||
this.state.stateManager.currentState = "operational"; // Set default state to operational
|
||||
|
||||
this.kv = 0; //default
|
||||
this.rho = 1,225 //dichtheid van lucht standaard
|
||||
this.T = 293; // temperatuur in K standaard
|
||||
this.downstreamP = 0.54 //hardcodes for now --> assumed to be constant watercolumn and deltaP diffuser
|
||||
this.currentMode = this.config.mode.current;
|
||||
|
||||
// wanneer hij deze ontvangt is de positie van de klep verandererd en gaat hij de updateposition functie aanroepen wat dan alle metingen en standen gaat updaten
|
||||
this.state.emitter.on("positionChange", (data) => {
|
||||
this.logger.debug(`Position change detected: ${data}`);
|
||||
this.updatePosition()}); //To update deltaP
|
||||
|
||||
|
||||
this.childRegistrationUtils = new ChildRegistrationUtils(this); // Child registration utility
|
||||
|
||||
//replace v_curve loadspecs with config file afterwards !!!!!!!!!!
|
||||
this.vCurve = this.loadSpecs().v_curve
|
||||
this.predictKv = new Predict({curve:this.vCurve}); // load valve size (x : ctrl , y : kv relationship)
|
||||
}
|
||||
|
||||
// -------- Config -------- //
|
||||
updateConfig(newConfig) {
|
||||
this.config = this.configUtils.updateConfig(this.config, newConfig);
|
||||
}
|
||||
|
||||
isValidSourceForMode(source, mode) {
|
||||
const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
|
||||
return allowedSourcesSet.has(source);
|
||||
}
|
||||
|
||||
async handleInput(source, action, parameter) {
|
||||
if (!this.isValidSourceForMode(source, this.currentMode)) {
|
||||
let warningTxt = `Source '${source}' is not valid for mode '${this.currentMode}'.`;
|
||||
this.logger.warn(warningTxt);
|
||||
return {status : false , feedback: warningTxt};
|
||||
}
|
||||
|
||||
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
|
||||
try {
|
||||
switch (action) {
|
||||
case "execSequence":
|
||||
await this.executeSequence(parameter);
|
||||
break;
|
||||
case "execMovement": // past het setpoint aan - movement van klep stand
|
||||
await this.setpoint(parameter);
|
||||
break;
|
||||
case "emergencyStop":
|
||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||
await this.executeSequence("emergencyStop");
|
||||
break;
|
||||
case "statusCheck":
|
||||
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source }'.`);
|
||||
break;
|
||||
default:
|
||||
this.logger.warn(`Action '${action}' is not implemented.`);
|
||||
break;
|
||||
}
|
||||
this.logger.debug(`Action '${action}' successfully executed`);
|
||||
return {status : true , feedback: `Action '${action}' successfully executed.`};
|
||||
} catch (error) {
|
||||
this.logger.error(`Error handling input: ${error}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setMode(newMode) {
|
||||
const availableModes = defaultConfig.mode.current.rules.values.map(v => v.value);
|
||||
if (!availableModes.includes(newMode)) {
|
||||
this.logger.warn(`Invalid mode '${newMode}'. Allowed modes are: ${availableModes.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentMode = newMode;
|
||||
this.logger.info(`Mode successfully changed to '${newMode}'.`);
|
||||
}
|
||||
|
||||
loadSpecs(){ //static betekend dat die in andere classes kan worden aangeroepen met const specs = Valve.loadSpecs()
|
||||
//lateron based on valve caracteristics --> then it searches for right valve
|
||||
let specs = {
|
||||
supplier : "Binder",
|
||||
type : "HDCV",
|
||||
units:{
|
||||
Nm3: { "temp": 20, "pressure" : 1.01325 , "RH" : 0 }, // according to DIN
|
||||
v_curve : { x : "% stroke", y : "Kv value"} ,
|
||||
},
|
||||
v_curve: {
|
||||
125: // valve size
|
||||
{
|
||||
x:[0,10,20,30,40,50,60,70,80,90,100], //stroke in %
|
||||
y:[0,18,50,95,150,216,337,564,882,1398,1870], //Kv value expressed in m3/h
|
||||
},
|
||||
150: // valve size
|
||||
{
|
||||
x:[0,10,20,30,40,50,60,70,80,90,100], //stroke in %
|
||||
y:[0,25,73,138,217,314,490,818,1281,2029,2715], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m
|
||||
},
|
||||
400: // valve size
|
||||
{
|
||||
x:[0,10,20,30,40,50,60,70,80,90,100], //stroke in %
|
||||
y:[0,155,443,839,1322,1911,2982,4980,7795,12349,16524], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
// -------- Sequence Handlers -------- //
|
||||
async executeSequence(sequenceName) {
|
||||
|
||||
const sequence = this.config.sequences[sequenceName];
|
||||
|
||||
if (!sequence || sequence.size === 0) {
|
||||
this.logger.warn(`Sequence '${sequenceName}' not defined.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.getCurrentState() == "operational" && sequenceName == "shutdown") {
|
||||
this.logger.info(`Machine will ramp down to position 0 before performing ${sequenceName} sequence`);
|
||||
await this.setpoint(0);
|
||||
}
|
||||
|
||||
this.logger.info(` --------- Executing sequence: ${sequenceName} -------------`);
|
||||
|
||||
for (const state of sequence) {
|
||||
try {
|
||||
await this.state.transitionToState(state);
|
||||
// Update measurements after state change
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`Error during sequence '${sequenceName}': ${error}`);
|
||||
break; // Exit sequence execution on error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setpoint(setpoint) {
|
||||
|
||||
try {
|
||||
// Validate setpoint
|
||||
if (typeof setpoint !== 'number' || setpoint < 0) {
|
||||
throw new Error("Invalid setpoint: Setpoint must be a non-negative number.");
|
||||
}
|
||||
|
||||
// Move to the desired setpoint
|
||||
await this.state.moveTo(setpoint);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error setting setpoint: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// NOTE: Omdat met zeer kleine getallen wordt gewerkt en er kwadraten in de formule zitten kan het zijn dat we alles *1000 moeten doen
|
||||
updateDeltaPKlep(q,kv,downstreamP,rho,temp){
|
||||
//q must be in Nm3/h
|
||||
//temp must be in K
|
||||
//q must be in m3/h
|
||||
|
||||
//downstreamP must be in bar so transfer from mbar to bar
|
||||
downstreamP = downstreamP / 1000;
|
||||
//convert downstreamP to absolute bar
|
||||
downstreamP += 1.01325;
|
||||
|
||||
//calculate deltaP
|
||||
let deltaP = ( q**2 * rho * temp ) / ( 514**2 * kv**2 * downstreamP);
|
||||
|
||||
//convert deltaP to mbar
|
||||
deltaP = deltaP * 1000;
|
||||
|
||||
// Synchroniseer deltaP met het Valve-object
|
||||
this.deltaPKlep = deltaP
|
||||
|
||||
// Opslaan in measurement container
|
||||
this.measurements.type("pressure").variant("predicted").position("delta").value(deltaP);
|
||||
this.logger.info('DeltaP updated to: ' + deltaP);
|
||||
|
||||
this.emitter.emit('deltaPChange', deltaP); // Emit event to notify valveGroupController of deltaP change
|
||||
this.logger.info('DeltaPChange emitted to valveGroupController');
|
||||
}
|
||||
|
||||
|
||||
// Als er een nieuwe flow door de klep komt doordat de pompen harder zijn gaan pompen, dan update deze functie dit ook in de valve attributes en measurements
|
||||
//NOTE: samenvoegen met updateFlow als header node er is
|
||||
updateFlowKlep(q){
|
||||
//q must be in Nm3/h
|
||||
// Opslaan in measurement container van valve object
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(q);
|
||||
this.logger.info('FlowKlep updated to: ' + q);
|
||||
this.logger.info('Calculating new deltaP based on new flow');
|
||||
this.updateDeltaPKlep(q,this.kv,this.downstreamP,this.rho,this.T); //update deltaP based on new flow
|
||||
}
|
||||
|
||||
updatePosition() { //update alle parameters nadat er een verandering is geweest in stand van klep
|
||||
if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") {
|
||||
|
||||
this.logger.debug('Calculating new deltaP');
|
||||
const currentPosition = this.state.getCurrentPosition();
|
||||
const currentFlow = this.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(); // haal de flow op uit de measurement containe
|
||||
//const valveSize = 125; //NOTE: nu nog hardcoded maar moet een attribute van de valve worden
|
||||
this.predictKv.fDimension = 125; //load valve size by defining fdimension in predict class
|
||||
//const vCurve = this.loadSpecs().v_curve[valveSize]; // haal de curve op van de valve
|
||||
//const Spline = require('cubic-spline'); // spline library -> nodig om kv waarde te benaderen op curve
|
||||
const x = currentPosition; // dit is de positie van de klep waarvoor we delta P willen berekenen
|
||||
const y = this.predictKv.y(x); // haal de waarde van kv op uit de spline
|
||||
|
||||
this.kv = y; //update de kv waarde in de valve class
|
||||
if (this.kv < 0.1){
|
||||
this.kv = 0.1; //minimum waarde voor kv
|
||||
}
|
||||
this.logger.debug(`Kv value for position valve ${x} is ${this.kv}`); // log de waarde van kv
|
||||
this.updateDeltaPKlep(currentFlow,this.kv,this.downstreamP,this.rho,this.T); //update deltaP
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
|
||||
// Improved output object generation
|
||||
const output = {};
|
||||
//build the output object
|
||||
this.measurements.getTypes().forEach(type => {
|
||||
this.measurements.getVariants().forEach(variant => {
|
||||
this.measurements.getPositions().forEach(position => {
|
||||
|
||||
const value = this.measurements.type(type).variant(variant).position(position).getCurrentValue(); //get the current value of the measurement
|
||||
|
||||
|
||||
if (value != null) {
|
||||
output[`${position}_${variant}_${type}`] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//fill in the rest of the output object
|
||||
output["state"] = this.state.getCurrentState();
|
||||
output["percentageOpen"] = this.state.getCurrentPosition();
|
||||
output["moveTimeleft"] = this.state.getMoveTimeLeft();
|
||||
output["mode"] = this.currentMode;
|
||||
|
||||
//this.logger.debug(`Output: ${JSON.stringify(output)}`);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Valve;
|
||||
|
||||
|
||||
|
||||
381
dependencies/valveConfig.json
vendored
381
dependencies/valveConfig.json
vendored
@@ -1,381 +0,0 @@
|
||||
{
|
||||
"general": {
|
||||
"name": {
|
||||
"default": "Valve",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A human-readable name or label for this valve configuration."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A unique identifier for this configuration. If not provided, defaults to null."
|
||||
}
|
||||
},
|
||||
"unit": {
|
||||
"default": "m3/h",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The default measurement unit for this configuration (e.g., 'meters', 'seconds', 'unitless')."
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"logLevel": {
|
||||
"default": "info",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "debug",
|
||||
"description": "Log messages are printed for debugging purposes."
|
||||
},
|
||||
{
|
||||
"value": "info",
|
||||
"description": "Informational messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "warn",
|
||||
"description": "Warning messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "error",
|
||||
"description": "Error messages are printed."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether logging is active. If true, log messages will be generated."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"functionality": {
|
||||
"softwareType": {
|
||||
"default": "valve",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Specified software type for this configuration."
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"default": "valveController",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Indicates the role this configuration plays within the system."
|
||||
}
|
||||
}
|
||||
},
|
||||
"asset": {
|
||||
"uuid": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A universally unique identifier for this asset. May be null if not assigned."
|
||||
}
|
||||
},
|
||||
"geoLocation": {
|
||||
"default": {},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"description": "An object representing the asset's physical coordinates or location.",
|
||||
"schema": {
|
||||
"x": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "X coordinate of the asset's location."
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Y coordinate of the asset's location."
|
||||
}
|
||||
},
|
||||
"z": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Z coordinate of the asset's location."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supplier": {
|
||||
"default": "Unknown",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The supplier or manufacturer of the asset."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"default": "valve",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A general classification of the asset tied to the specific software. This is not chosen from the asset dropdown menu."
|
||||
}
|
||||
},
|
||||
"subType": {
|
||||
"default": "ecdv",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A more specific classification within 'type'. For example, 'ecdv valve'."
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"default": "Unknown",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A user-defined or manufacturer-defined model identifier for the asset."
|
||||
}
|
||||
},
|
||||
"accuracy": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "The accuracy of the valve or sensor, typically as a percentage or absolute value."
|
||||
}
|
||||
},
|
||||
"valveCurve": {
|
||||
"default": {
|
||||
"nq": {
|
||||
"1": {
|
||||
"x": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"y": [
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
40,
|
||||
50
|
||||
]
|
||||
}
|
||||
},
|
||||
"np": {
|
||||
"1": {
|
||||
"x": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"y": [
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
40,
|
||||
50
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"type": "valveCurve",
|
||||
"description": "All valves curves must have a 'nq' and 'np' curve. nq stands for the flow curve, np stands for the power curve. Together they form the efficiency curve."
|
||||
}
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"current": {
|
||||
"default": "auto",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "auto",
|
||||
"description": "Accepts setpoints from a parent controller and runs autonomously."
|
||||
},
|
||||
{
|
||||
"value": "virtualControl",
|
||||
"description": "Controlled via GUI setpoints; ignores parent commands."
|
||||
},
|
||||
{
|
||||
"value": "fysicalControl",
|
||||
"description": "Controlled via physical buttons or switches; ignores external automated commands."
|
||||
},
|
||||
{
|
||||
"value": "maintenance",
|
||||
"description": "No active control from auto, virtual, or fysical sources."
|
||||
}
|
||||
],
|
||||
"description": "The operational mode."
|
||||
}
|
||||
},
|
||||
"allowedActions":{
|
||||
"default":{},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"schema":{
|
||||
"auto": {
|
||||
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Actions allowed in auto mode."
|
||||
}
|
||||
},
|
||||
"virtualControl": {
|
||||
"default": ["statusCheck", "execMovement", "execSequence", "emergencyStop"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Actions allowed in virtualControl mode."
|
||||
}
|
||||
},
|
||||
"fysicalControl": {
|
||||
"default": ["statusCheck", "emergencyStop"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Actions allowed in fysicalControl mode."
|
||||
}
|
||||
},
|
||||
"maintenance": {
|
||||
"default": ["statusCheck"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Actions allowed in maintenance mode."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Information about valid command sources recognized."
|
||||
}
|
||||
},
|
||||
"allowedSources":{
|
||||
"default": {},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"schema":{
|
||||
"auto": {
|
||||
"default": ["parent", "GUI", "fysical"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sources allowed in auto mode."
|
||||
}
|
||||
},
|
||||
"virtualControl": {
|
||||
"default": ["GUI", "fysical"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sources allowed in virtualControl mode."
|
||||
}
|
||||
},
|
||||
"fysicalControl": {
|
||||
"default": ["fysical"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sources allowed in fysicalControl mode."
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Information about valid command sources recognized."
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"default": "parent",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "parent",
|
||||
"description": "Commands are received from a parent controller."
|
||||
},
|
||||
{
|
||||
"value": "GUI",
|
||||
"description": "Commands are received from a graphical user interface."
|
||||
},
|
||||
{
|
||||
"value": "fysical",
|
||||
"description": "Commands are received from physical buttons or switches."
|
||||
}
|
||||
],
|
||||
"description": "Information about valid command sources recognized."
|
||||
}
|
||||
},
|
||||
"sequences":{
|
||||
"default":{},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"schema": {
|
||||
"startup": {
|
||||
"default": ["starting","warmingup","operational"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sequence of states for starting up."
|
||||
}
|
||||
},
|
||||
"shutdown": {
|
||||
"default": ["stopping","coolingdown","idle"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sequence of states for shutting down."
|
||||
}
|
||||
},
|
||||
"emergencystop": {
|
||||
"default": ["emergencystop","off"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sequence of states for an emergency stop."
|
||||
}
|
||||
},
|
||||
"boot": {
|
||||
"default": ["idle","starting","warmingup","operational"],
|
||||
"rules": {
|
||||
"type": "set",
|
||||
"itemType": "string",
|
||||
"description": "Sequence of states for booting up."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Predefined sequences of states."
|
||||
|
||||
},
|
||||
"calculationMode": {
|
||||
"default": "medium",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "low",
|
||||
"description": "Calculations run at fixed intervals (time-based)."
|
||||
},
|
||||
{
|
||||
"value": "medium",
|
||||
"description": "Calculations run when new setpoints arrive or measured changes occur (event-driven)."
|
||||
},
|
||||
{
|
||||
"value": "high",
|
||||
"description": "Calculations run on all event-driven info, including every movement."
|
||||
}
|
||||
],
|
||||
"description": "The frequency at which calculations are performed."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"author": "Rene De Ren / Janneke Tack",
|
||||
"license": "SEE LICENSE",
|
||||
"dependencies": {
|
||||
"generalFunctions": "git+https://gitea.centraal.wbd-rd.nl/RnD/generalFunctions.git",
|
||||
"convert": "git+https://gitea.centraal.wbd-rd.nl/RnD/convert.git"
|
||||
"generalFunctions": "git+https://gitea.centraal.wbd-rd.nl/RnD/generalFunctions.git"
|
||||
},
|
||||
"node-red": {
|
||||
"nodes": {
|
||||
|
||||
296
src/nodeClass.js
Normal file
296
src/nodeClass.js
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 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 { outputUtils, configManager } = require('generalFunctions');
|
||||
const Specific = require("./specificClass");
|
||||
|
||||
|
||||
class nodeClass {
|
||||
/**
|
||||
* Create a MeasurementNode.
|
||||
* @param {object} uiConfig - Node-RED node configuration.
|
||||
* @param {object} RED - Node-RED runtime API.
|
||||
* @param {object} nodeInstance - The Node-RED node instance.
|
||||
* @param {string} nameOfNode - The name of the node, used for
|
||||
*/
|
||||
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; // Will hold the specific class instance
|
||||
this.config = null; // Will hold the merged configuration
|
||||
|
||||
// Load default & UI config
|
||||
this._loadConfig(uiConfig,this.node);
|
||||
|
||||
// Instantiate core Measurement class
|
||||
this._setupSpecificClass(uiConfig);
|
||||
|
||||
// Wire up event and lifecycle handlers
|
||||
this._bindEvents();
|
||||
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,node) {
|
||||
|
||||
// Merge UI config over defaults
|
||||
this.config = {
|
||||
general: {
|
||||
name: uiConfig.name,
|
||||
id: 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
|
||||
}
|
||||
},
|
||||
asset: {
|
||||
uuid: uiConfig.assetUuid, //need to add this later to the asset model
|
||||
tagCode: uiConfig.assetTagCode, //need to add this later to the asset model
|
||||
supplier: uiConfig.supplier,
|
||||
category: uiConfig.category, //add later to define as the software type
|
||||
type: uiConfig.assetType,
|
||||
model: uiConfig.model,
|
||||
unit: uiConfig.unit
|
||||
},
|
||||
functionality: {
|
||||
positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified
|
||||
}
|
||||
};
|
||||
|
||||
// Utility for formatting outputs
|
||||
this._output = new outputUtils();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate the core logic and store as source.
|
||||
*/
|
||||
_setupSpecificClass(uiConfig) {
|
||||
const vconfig = this.config;
|
||||
|
||||
// need extra state for this
|
||||
const stateConfig = {
|
||||
general: {
|
||||
logging: {
|
||||
enabled: vconfig.eneableLog,
|
||||
logLevel: vconfig.logLevel
|
||||
}
|
||||
},
|
||||
movement: {
|
||||
speed: Number(uiConfig.speed)
|
||||
},
|
||||
time: {
|
||||
starting: Number(uiConfig.startup),
|
||||
warmingup: Number(uiConfig.warmup),
|
||||
stopping: Number(uiConfig.shutdown),
|
||||
coolingdown: Number(uiConfig.cooldown)
|
||||
}
|
||||
};
|
||||
|
||||
this.source = new Specific(vconfig, stateConfig);
|
||||
|
||||
//store in node
|
||||
this.node.source = this.source; // Store the source in the node instance for easy access
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Measurement events to Node-RED status updates. Using internal emitter. --> REMOVE LATER WE NEED ONLY COMPLETE CHILDS AND THEN CHECK FOR UPDATES
|
||||
*/
|
||||
_bindEvents() {
|
||||
|
||||
}
|
||||
|
||||
_updateNodeStatus() {
|
||||
const v = this.source;
|
||||
|
||||
try {
|
||||
const mode = v.currentMode; // modus is bijv. auto, manual, etc.
|
||||
const state = v.state.getCurrentState(); //is bijv. operational, idle, off, etc.
|
||||
// check if measured flow is available otherwise use predicted flow
|
||||
const flow = Math.round(v.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue());
|
||||
|
||||
let deltaP = v.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue();
|
||||
if (deltaP !== null) {
|
||||
deltaP = parseFloat(deltaP.toFixed(0));
|
||||
} //afronden op 4 decimalen indien geen "null"
|
||||
if(isNaN(deltaP)) {
|
||||
deltaP = "∞";
|
||||
}
|
||||
const roundedPosition = Math.round(v.state.getCurrentPosition() * 100) / 100;
|
||||
let symbolState;
|
||||
switch(state){
|
||||
case "off":
|
||||
symbolState = "⬛";
|
||||
break;
|
||||
case "idle":
|
||||
symbolState = "⏸️";
|
||||
break;
|
||||
case "operational":
|
||||
symbolState = "⏵️";
|
||||
break;
|
||||
case "starting":
|
||||
symbolState = "⏯️";
|
||||
break;
|
||||
case "warmingup":
|
||||
symbolState = "🔄";
|
||||
break;
|
||||
case "accelerating":
|
||||
symbolState = "⏩";
|
||||
break;
|
||||
case "stopping":
|
||||
symbolState = "⏹️";
|
||||
break;
|
||||
case "coolingdown":
|
||||
symbolState = "❄️";
|
||||
break;
|
||||
case "decelerating":
|
||||
symbolState = "⏪";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
let status;
|
||||
switch (state) {
|
||||
case "off":
|
||||
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
|
||||
break;
|
||||
case "idle":
|
||||
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "operational":
|
||||
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
case "starting":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "warmingup":
|
||||
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
case "accelerating":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar` }; //deltaP toegevoegd
|
||||
break;
|
||||
case "stopping":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "coolingdown":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "decelerating":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
default:
|
||||
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
}
|
||||
return status;
|
||||
} catch (error) {
|
||||
node.error("Error in updateNodeStatus: " + error.message);
|
||||
return { fill: "red", shape: "ring", text: "Status Error" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the periodic tick loop.
|
||||
*/
|
||||
_startTickLoop() {
|
||||
setTimeout(() => {
|
||||
this._tickInterval = setInterval(() => this._tick(), 1000);
|
||||
|
||||
// Update node status on nodered screen every second ( this is not the best way to do this, but it works for now)
|
||||
this._statusInterval = setInterval(() => {
|
||||
const status = this._updateNodeStatus();
|
||||
this.node.status(status);
|
||||
}, 1000);
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single tick: update measurement, format and send outputs.
|
||||
*/
|
||||
_tick() {
|
||||
//this.source.tick();
|
||||
|
||||
const raw = this.source.getOutput();
|
||||
const processMsg = this._output.formatMsg(raw, this.config, 'process');
|
||||
const influxMsg = this._output.formatMsg(raw, this.config, 'influxdb');
|
||||
|
||||
// Send only updated outputs on ports 0 & 1
|
||||
this.node.send([processMsg, influxMsg]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the node's input handler, routing control messages to the class.
|
||||
*/
|
||||
_attachInputHandler() {
|
||||
this.node.on('input', (msg, send, done) => {
|
||||
const v = this.source;
|
||||
switch(msg.topic) {
|
||||
case 'registerChild':
|
||||
const childId = msg.payload;
|
||||
const childObj = this.RED.nodes.getNode(childId);
|
||||
v.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
||||
break;
|
||||
case 'setMode':
|
||||
v.setMode(msg.payload);
|
||||
break;
|
||||
case 'execSequence':
|
||||
const { source: seqSource, action: seqAction, parameter } = msg.payload;
|
||||
v.handleInput(seqSource, seqAction, parameter);
|
||||
break;
|
||||
case 'execMovement':
|
||||
const { source: mvSource, action: mvAction, setpoint } = msg.payload;
|
||||
v.handleInput(mvSource, mvAction, Number(setpoint));
|
||||
break;
|
||||
case 'emergencystop':
|
||||
const { source: esSource, action: esAction } = msg.payload;
|
||||
v.handleInput(esSource, esAction);
|
||||
break;
|
||||
case 'showcurve':
|
||||
v.showCurve();
|
||||
send({ topic : "Showing curve" , payload: v.showCurve() });
|
||||
break;
|
||||
case 'updateFlow': //Als nieuwe flow van header node dan moet deltaP weer opnieuw worden berekend en doorgegeven aan header node
|
||||
v.updateFlow(msg.payload.variant, msg.payload.value, msg.payload.position);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up timers and intervals when Node-RED stops the node.
|
||||
*/
|
||||
_attachCloseHandler() {
|
||||
this.node.on('close', (done) => {
|
||||
clearInterval(this._tickInterval);
|
||||
clearInterval(this._statusInterval);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = nodeClass;
|
||||
372
src/specificClass.js
Normal file
372
src/specificClass.js
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* @file valve.js
|
||||
*
|
||||
* Permission is hereby granted to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to use it for personal
|
||||
* or non-commercial purposes, with the following restrictions:
|
||||
*
|
||||
* 1. **No Copying or Redistribution**: The Software or any of its parts may not
|
||||
* be copied, merged, distributed, sublicensed, or sold without explicit
|
||||
* prior written permission from the author.
|
||||
*
|
||||
* 2. **Commercial Use**: Any use of the Software for commercial purposes requires
|
||||
* a valid license, obtainable only with the explicit consent of the author.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Ownership of this code remains solely with the original author. Unauthorized
|
||||
* use of this Software is strictly prohibited.
|
||||
*
|
||||
* Author:
|
||||
* - Rene De Ren
|
||||
* Email:
|
||||
* - r.de.ren@brabantsedelta.nl
|
||||
*
|
||||
* Future Improvements:
|
||||
* - Time-based stability checks
|
||||
* - Warmup handling
|
||||
* - Dynamic outlier detection thresholds
|
||||
* - Dynamic smoothing window and methods
|
||||
* - Alarm and threshold handling
|
||||
* - Maintenance mode
|
||||
* - Historical data and trend analysis
|
||||
*/
|
||||
/**
|
||||
* @file valveClass.js
|
||||
*
|
||||
* Permission is hereby granted to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to use it for personal
|
||||
....
|
||||
*/
|
||||
|
||||
//load local dependencies
|
||||
const EventEmitter = require('events');
|
||||
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');
|
||||
|
||||
class Valve {
|
||||
constructor(valveConfig = {}, stateConfig = {}) {
|
||||
//basic setup
|
||||
this.emitter = new EventEmitter(); // nodig voor ontvangen en uitvoeren van events emit() --> Zien als internet berichten (niet bedraad in node-red)
|
||||
|
||||
this.logger = new logger(valveConfig.general.logging.enabled,valveConfig.general.logging.logLevel, valveConfig.general.name);
|
||||
this.configManager = new configManager();
|
||||
this.defaultConfig = this.configManager.getConfig('valve'); // Load default config for rotating machine ( use software type name ? )
|
||||
this.configUtils = new configUtils(this.defaultConfig);
|
||||
|
||||
// Load a specific curve
|
||||
this.model = valveConfig.asset.model; // Get the model from the valveConfig
|
||||
this.curve = this.model ? loadCurve(this.model) : null;
|
||||
|
||||
//Init config and check if it is valid
|
||||
this.config = this.configUtils.initConfig(valveConfig);
|
||||
|
||||
// Initialize measurements
|
||||
this.measurements = new MeasurementContainer();
|
||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
||||
|
||||
// Init after config is set
|
||||
this.state = new state(stateConfig, this.logger); // Init State manager and pass logger
|
||||
|
||||
this.state.stateManager.currentState = "operational"; // Set default state to operational
|
||||
|
||||
this.kv = 0; //default
|
||||
this.rho = 1,225 //dichtheid van lucht standaard
|
||||
this.T = 293; // temperatuur in K standaard
|
||||
this.downstreamP = 0.54 //hardcodes for now --> assumed to be constant watercolumn and deltaP diffuser
|
||||
this.currentMode = this.config.mode.current;
|
||||
|
||||
// wanneer hij deze ontvangt is de positie van de klep verandererd en gaat hij de updateposition functie aanroepen wat dan alle metingen en standen gaat updaten
|
||||
this.state.emitter.on("positionChange", (data) => {
|
||||
this.logger.debug(`Position change detected: ${data}`);
|
||||
this.updatePosition()}); //To update deltaP
|
||||
|
||||
|
||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
||||
this.vCurve = this.curve[1.204]; // specificy the desired density RECALC THIS AUTOMTICALLY BASED ON DENSITY OF AIR LATER OLIFANT!!
|
||||
this.predictKv = new predict({curve:this.vCurve}); // load valve size (x : ctrl , y : kv relationship)
|
||||
//this.logger.debug(`PredictKv initialized with curve: ${JSON.stringify(this.predictKv)}`);
|
||||
}
|
||||
|
||||
// -------- Config -------- //
|
||||
updateConfig(newConfig) {
|
||||
this.config = this.configUtils.updateConfig(this.config, newConfig);
|
||||
}
|
||||
|
||||
isValidSourceForMode(source, mode) {
|
||||
const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
|
||||
return allowedSourcesSet.has(source);
|
||||
}
|
||||
|
||||
async handleInput(source, action, parameter) {
|
||||
if (!this.isValidSourceForMode(source, this.currentMode)) {
|
||||
let warningTxt = `Source '${source}' is not valid for mode '${this.currentMode}'.`;
|
||||
this.logger.warn(warningTxt);
|
||||
return {status : false , feedback: warningTxt};
|
||||
}
|
||||
|
||||
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
|
||||
try {
|
||||
switch (action) {
|
||||
case "execSequence":
|
||||
await this.executeSequence(parameter);
|
||||
break;
|
||||
case "execMovement": // past het setpoint aan - movement van klep stand
|
||||
await this.setpoint(parameter);
|
||||
break;
|
||||
case "emergencyStop":
|
||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||
await this.executeSequence("emergencyStop");
|
||||
break;
|
||||
case "statusCheck":
|
||||
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source }'.`);
|
||||
break;
|
||||
default:
|
||||
this.logger.warn(`Action '${action}' is not implemented.`);
|
||||
break;
|
||||
}
|
||||
this.logger.debug(`Action '${action}' successfully executed`);
|
||||
return {status : true , feedback: `Action '${action}' successfully executed.`};
|
||||
} catch (error) {
|
||||
this.logger.error(`Error handling input: ${error}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setMode(newMode) {
|
||||
const availableModes = defaultConfig.mode.current.rules.values.map(v => v.value);
|
||||
if (!availableModes.includes(newMode)) {
|
||||
this.logger.warn(`Invalid mode '${newMode}'. Allowed modes are: ${availableModes.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentMode = newMode;
|
||||
this.logger.info(`Mode successfully changed to '${newMode}'.`);
|
||||
}
|
||||
|
||||
// -------- Sequence Handlers -------- //
|
||||
async executeSequence(sequenceName) {
|
||||
|
||||
const sequence = this.config.sequences[sequenceName];
|
||||
|
||||
if (!sequence || sequence.size === 0) {
|
||||
this.logger.warn(`Sequence '${sequenceName}' not defined.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.getCurrentState() == "operational" && sequenceName == "shutdown") {
|
||||
this.logger.info(`Machine will ramp down to position 0 before performing ${sequenceName} sequence`);
|
||||
await this.setpoint(0);
|
||||
}
|
||||
|
||||
this.logger.info(` --------- Executing sequence: ${sequenceName} -------------`);
|
||||
|
||||
for (const state of sequence) {
|
||||
try {
|
||||
await this.state.transitionToState(state);
|
||||
// Update measurements after state change
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`Error during sequence '${sequenceName}': ${error}`);
|
||||
break; // Exit sequence execution on error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setpoint(setpoint) {
|
||||
|
||||
try {
|
||||
// Validate setpoint
|
||||
if (typeof setpoint !== 'number' || setpoint < 0) {
|
||||
throw new Error("Invalid setpoint: Setpoint must be a non-negative number.");
|
||||
}
|
||||
|
||||
// Move to the desired setpoint
|
||||
await this.state.moveTo(setpoint);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error setting setpoint: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
updatePressure(variant,value,position) {
|
||||
if( value === null || value === undefined) {
|
||||
this.logger.warn(`Received null or undefined value for flow update. Variant: ${variant}, Position: ${position}`);
|
||||
return;
|
||||
}
|
||||
this.logger.debug(`Updating pressure: variant=${variant}, value=${value}, position=${position}`);
|
||||
|
||||
switch (variant) {
|
||||
case ("measured"):
|
||||
// put value in measurements container
|
||||
console.log( 'wtf ... ' + value);
|
||||
this.measurements.type("pressure").variant("measured").position(position).value(value);
|
||||
// get latest downstream pressure measurement
|
||||
const measuredDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
|
||||
// update predicted flow measurement
|
||||
this.updateDeltaPKlep(value,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
|
||||
break;
|
||||
|
||||
case ("predicted"):
|
||||
// put value in measurements container
|
||||
this.measurements.type("pressure").variant("predicted").position(position).value(value);
|
||||
const predictedDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
|
||||
this.updateDeltaPKlep(value,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateMeasurement(variant, subType, value, position) {
|
||||
this.logger.debug(`---------------------- updating ${subType} ------------------ `);
|
||||
switch (subType) {
|
||||
case "pressure":
|
||||
// Update pressure measurement
|
||||
this.updatePressure(variant,value,position);
|
||||
break;
|
||||
case "flow":
|
||||
this.updateFlow(variant,value,position);
|
||||
break;
|
||||
case "power":
|
||||
// Update power measurement
|
||||
break;
|
||||
default:
|
||||
this.logger.error(`Type '${subType}' not recognized for measured update.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Omdat met zeer kleine getallen wordt gewerkt en er kwadraten in de formule zitten kan het zijn dat we alles *1000 moeten doen
|
||||
updateDeltaPKlep(q,kv,downstreamP,rho,temp){
|
||||
//q must be in Nm3/h
|
||||
//temp must be in K
|
||||
//q must be in m3/h
|
||||
|
||||
//downstreamP must be in bar so transfer from mbar to bar
|
||||
downstreamP = downstreamP / 1000;
|
||||
//convert downstreamP to absolute bar
|
||||
downstreamP += 1.01325;
|
||||
|
||||
if( kv !== 0 && downstreamP != 0 && q != 0) { //check if kv and downstreamP are not zero to avoid division by zero
|
||||
|
||||
//calculate deltaP
|
||||
let deltaP = ( q**2 * rho * temp ) / ( 514**2 * kv**2 * downstreamP);
|
||||
|
||||
//convert deltaP to mbar
|
||||
deltaP = deltaP * 1000;
|
||||
|
||||
// Synchroniseer deltaP met het Valve-object
|
||||
this.deltaPKlep = deltaP
|
||||
|
||||
// Opslaan in measurement container
|
||||
this.measurements.type("pressure").variant("predicted").position("delta").value(deltaP);
|
||||
this.logger.info('DeltaP updated to: ' + deltaP);
|
||||
|
||||
this.emitter.emit('deltaPChange', deltaP); // Emit event to notify valveGroupController of deltaP change
|
||||
this.logger.info('DeltaPChange emitted to valveGroupController');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Als er een nieuwe flow door de klep komt doordat de machines harder zijn gaan werken, dan update deze functie dit ook in de valve attributes en measurements
|
||||
updateFlow(variant,value,position) {
|
||||
if( value === null || value === undefined) {
|
||||
this.logger.warn(`Received null or undefined value for flow update. Variant: ${variant}, Position: ${position}`);
|
||||
return;
|
||||
}
|
||||
this.logger.debug(`Updating flow: variant=${variant}, value=${value}, position=${position}`);
|
||||
|
||||
switch (variant) {
|
||||
case ("measured"):
|
||||
// put value in measurements container
|
||||
this.measurements.type("flow").variant("measured").position(position).value(value);
|
||||
// get latest downstream pressure measurement
|
||||
const measuredDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
|
||||
// update predicted flow measurement
|
||||
this.updateDeltaPKlep(value,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
|
||||
break;
|
||||
|
||||
case ("predicted"):
|
||||
// put value in measurements container
|
||||
this.measurements.type("flow").variant("predicted").position(position).value(value);
|
||||
const predictedDownStreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); //update downstream pressure measurement
|
||||
this.updateDeltaPKlep(value,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updatePosition() { //update alle parameters nadat er een verandering is geweest in stand van klep
|
||||
if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") {
|
||||
|
||||
this.logger.debug('Calculating new deltaP');
|
||||
const currentPosition = this.state.getCurrentPosition();
|
||||
const measuredFlow = this.measurements.type("flow").variant("measured").position("downstream").getCurrentValue(); // haal de flow op uit de measurement containe
|
||||
const predictedFlow = this.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(); // haal de predicted flow op uit de measurement container
|
||||
const currentFlow = predictedFlow ;
|
||||
|
||||
const downstreamP = this.measurements.type("pressure").variant("measured").position("downstream").getCurrentValue(); // haal de downstream pressure op uit de measurement container
|
||||
//const valveSize = 125; //NOTE: nu nog hardcoded maar moet een attribute van de valve worden
|
||||
this.predictKv.fDimension = 125; //load valve size by defining fdimension in predict class
|
||||
const x = currentPosition; // dit is de positie van de klep waarvoor we delta P willen berekenen
|
||||
const y = this.predictKv.y(x); // haal de waarde van kv op uit de spline
|
||||
|
||||
this.kv = y; //update de kv waarde in de valve class
|
||||
if (this.kv < 0.1){
|
||||
this.kv = 0.1; //minimum waarde voor kv
|
||||
}
|
||||
this.logger.debug(`Kv value for position valve ${x} is ${this.kv}`); // log de waarde van kv
|
||||
|
||||
this.updateDeltaPKlep(currentFlow,this.kv,downstreamP,this.rho,this.T); //update deltaP
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
|
||||
// Improved output object generation
|
||||
const output = {};
|
||||
//build the output object
|
||||
this.measurements.getTypes().forEach(type => {
|
||||
this.measurements.getVariants().forEach(variant => {
|
||||
this.measurements.getPositions().forEach(position => {
|
||||
|
||||
const value = this.measurements.type(type).variant(variant).position(position).getCurrentValue(); //get the current value of the measurement
|
||||
|
||||
|
||||
if (value != null) {
|
||||
output[`${position}_${variant}_${type}`] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//fill in the rest of the output object
|
||||
output["state"] = this.state.getCurrentState();
|
||||
output["percentageOpen"] = this.state.getCurrentPosition();
|
||||
output["moveTimeleft"] = this.state.getMoveTimeLeft();
|
||||
output["mode"] = this.currentMode;
|
||||
|
||||
//this.logger.debug(`Output: ${JSON.stringify(output)}`);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Valve;
|
||||
|
||||
|
||||
|
||||
361
valve.html
361
valve.html
@@ -1,275 +1,130 @@
|
||||
<!--<script src="resources/wbd-valve/unit_class.js"></script>-->
|
||||
<script type="module">
|
||||
import * as menuUtils from "/generalFunctions/helper/menuUtils.js"; //CHANGED to import the menuUtils module
|
||||
<!--
|
||||
| S88-niveau | Primair (blokkleur) | Tekstkleur |
|
||||
| ---------------------- | ------------------- | ---------- |
|
||||
| **Area** | `#0f52a5` | wit |
|
||||
| **Process Cell** | `#0c99d9` | wit |
|
||||
| **Unit** | `#50a8d9` | zwart |
|
||||
| **Equipment (Module)** | `#86bbdd` | zwart |
|
||||
| **Control Module** | `#a9daee` | zwart |
|
||||
|
||||
RED.nodes.registerType('valve',{
|
||||
category: 'digital twin',
|
||||
color: '#e4a363',
|
||||
defaults: {
|
||||
// Define default properties
|
||||
name: { value: "", required: true },
|
||||
enableLog: { value: false },
|
||||
logLevel: { value: "error" },
|
||||
|
||||
// Define specific properties
|
||||
/* NOT USED
|
||||
speed: { value: 1, required: true },
|
||||
startup: { value: 0 },
|
||||
warmup: { value: 0 },
|
||||
shutdown: { value: 0 },
|
||||
cooldown: { value: 0 },
|
||||
*/
|
||||
-->
|
||||
<script src="/valve/menu.js"></script> <!-- Load the menu script for dynamic dropdowns -->
|
||||
<script src="/valve/configData.js"></script> <!-- Load the config script for node information -->
|
||||
|
||||
//define general asset properties
|
||||
supplier: { value: "" }, // laten staan als voorbeeld
|
||||
/*NOT USED
|
||||
subType: { value: "" },
|
||||
model: { value: "" },
|
||||
unit: { value: "" },*/
|
||||
<script>
|
||||
RED.nodes.registerType("valve", {
|
||||
category: "EVOLV",
|
||||
color: "#86bbdd", // color for the node based on the S88 schema
|
||||
defaults: {
|
||||
|
||||
//define specific asset properties
|
||||
/*NOT USED
|
||||
valveCurve : { value: {}},*/
|
||||
},
|
||||
// Define default properties
|
||||
name: { value: "" }, // use asset category as name
|
||||
|
||||
// Define specific properties
|
||||
speed: { value: 1, required: true },
|
||||
|
||||
//inputs en outputs voor node
|
||||
inputs: 1,
|
||||
outputs: 4,
|
||||
inputLabels: ["Valve Input"],
|
||||
outputLabels: ["proces", "dbase", "upstream parent", "downstream parent"],
|
||||
icon: "font-awesome/fa-sliders", //CHANGED to other icon from Font Awesome 4
|
||||
|
||||
label: function () {
|
||||
return this.name || "Valve"; //CHANGED to Valve
|
||||
},
|
||||
|
||||
// Prepare for opening bewerkingsinterface van node in de Node-RED editor
|
||||
oneditprepare: function () {
|
||||
const node = this;
|
||||
//define asset properties
|
||||
uuid: { value: "" },
|
||||
supplier: { value: "" },
|
||||
category: { value: "" },
|
||||
assetType: { value: "" },
|
||||
model: { value: "" },
|
||||
unit: { value: "" },
|
||||
|
||||
console.log("Valve Node: Edit Prepare"); //CHANGED to Valve Node
|
||||
|
||||
const elements = {
|
||||
// Basic fields
|
||||
name: document.getElementById("node-input-name"),
|
||||
// specific fields
|
||||
/*NOT USED
|
||||
speed: document.getElementById("node-input-speed"),
|
||||
startup: document.getElementById("node-input-startup"),
|
||||
warmup: document.getElementById("node-input-warmup"),
|
||||
shutdown: document.getElementById("node-input-shutdown"),
|
||||
cooldown: document.getElementById("node-input-cooldown"),*/
|
||||
// Logging fields
|
||||
logCheckbox: document.getElementById("node-input-enableLog"),
|
||||
logLevelSelect: document.getElementById("node-input-logLevel"),
|
||||
rowLogLevel: document.getElementById("row-logLevel"),
|
||||
// Asset fields
|
||||
supplier: document.getElementById("node-input-supplier"),
|
||||
/*NOT USED
|
||||
subType: document.getElementById("node-input-subType"),
|
||||
model: document.getElementById("node-input-model"),
|
||||
unit: document.getElementById("node-input-unit"), */
|
||||
};
|
||||
//logger properties
|
||||
enableLog: { value: false },
|
||||
logLevel: { value: "error" },
|
||||
|
||||
const projecSettingstURL = "http://localhost:1880/generalFunctions/settings/projectSettings.json"; //volgens mij hoeft deze niet aangepast?
|
||||
//physicalAspect
|
||||
positionVsParent: { value: "" },
|
||||
positionIcon: { value: "" },
|
||||
hasDistance: { value: false },
|
||||
distance: { value: 0 },
|
||||
distanceUnit: { value: "m" },
|
||||
distanceDescription: { value: "" }
|
||||
|
||||
try{
|
||||
|
||||
// Fetch project settings
|
||||
menuUtils.fetchProjectData(projecSettingstURL)
|
||||
.then((projectSettings) => {
|
||||
|
||||
//assign to node vars
|
||||
node.configUrls = projectSettings.configUrls;
|
||||
|
||||
const { cloudConfigURL, localConfigURL } = menuUtils.getSpecificConfigUrl("valve",node.configUrls.cloud.taggcodeAPI); //CHANGED to valve
|
||||
node.configUrls.cloud.config = cloudConfigURL; // first call
|
||||
node.configUrls.local.config = localConfigURL; // backup call
|
||||
|
||||
node.locationId = projectSettings.locationId;
|
||||
node.uuid = projectSettings.uuid;
|
||||
|
||||
// Gets the ID of the active workspace (Flow)
|
||||
const activeFlowId = RED.workspaces.active(); //fetches active flow id
|
||||
node.processId = activeFlowId;
|
||||
|
||||
// UI elements
|
||||
menuUtils.initBasicToggles(elements);
|
||||
menuUtils.fetchAndPopulateDropdowns(node.configUrls, elements, node); // function for all assets
|
||||
|
||||
})
|
||||
|
||||
}catch(e){
|
||||
console.log("Error fetching project settings", e);
|
||||
}
|
||||
|
||||
if(node.d){
|
||||
//this means node is disabled
|
||||
console.log("Current status of node is disabled");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
inputs: 1,
|
||||
outputs: 3,
|
||||
inputLabels: ["Input"],
|
||||
outputLabels: ["process", "dbase", "parent"],
|
||||
icon: "font-awesome/fa-toggle-on",
|
||||
|
||||
label: function () {
|
||||
return this.positionIcon + " " + this.category.slice(0, -1) || "Valve";
|
||||
},
|
||||
|
||||
oneditprepare: function() {
|
||||
const waitForMenuData = () => {
|
||||
if (window.EVOLV?.nodes?.measurement?.initEditor) {
|
||||
window.EVOLV.nodes.measurement.initEditor(this);
|
||||
} else {
|
||||
setTimeout(waitForMenuData, 50);
|
||||
}
|
||||
};
|
||||
// Wait for the menu data to be ready before initializing the editor
|
||||
waitForMenuData();
|
||||
|
||||
// THIS IS NODE SPECIFIC --------------- Initialize the dropdowns and other specific UI elements -------------- this should be derived from the config in the future (make config based menu)
|
||||
document.getElementById("node-input-speed");
|
||||
//------------------- END OF CUSTOM config UI ELEMENTS ------------------- //
|
||||
},
|
||||
oneditsave: function () {
|
||||
const node = this;
|
||||
|
||||
console.log(`------------ Saving changes to node ------------`);
|
||||
console.log(`${node.uuid}`);
|
||||
|
||||
//save basic properties
|
||||
["name", "supplier"].forEach( //CHANGED to only save the properties we have now
|
||||
(field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
if (element) {
|
||||
node[field] = element.value || "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const logLevelElement = document.getElementById("node-input-logLevel");
|
||||
node.logLevel = logLevelElement ? logLevelElement.value || "info" : "info";
|
||||
|
||||
if (!node.unit) {
|
||||
RED.notify("Unit selection is required.", "error");
|
||||
// Validate asset properties using the asset menu
|
||||
if (window.EVOLV?.nodes?.valve?.assetMenu?.saveEditor) {
|
||||
success = window.EVOLV.nodes.valve.assetMenu.saveEditor(this);
|
||||
}
|
||||
|
||||
if (node.subType && !node.unit) {
|
||||
RED.notify("Unit must be set when specifying a subtype.", "error");
|
||||
// Validate logger properties using the logger menu
|
||||
if (window.EVOLV?.nodes?.valve?.loggerMenu?.saveEditor) {
|
||||
success = window.EVOLV.nodes.valve.loggerMenu.saveEditor(node);
|
||||
}
|
||||
|
||||
try{
|
||||
// Fetch project settings
|
||||
menuUtils.apiCall(node,node.configUrls)
|
||||
.then((response) => {
|
||||
|
||||
//save response to node information
|
||||
node.assetId = response.asset_id;
|
||||
node.assetTagNumber = response.asset_tag_number;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error during API call", error);
|
||||
});
|
||||
}catch(e){
|
||||
console.log("Error saving assetID and tagnumber", e);
|
||||
// save position field
|
||||
if (window.EVOLV?.nodes?.valve?.positionMenu?.saveEditor) {
|
||||
window.EVOLV.nodes.valve.positionMenu.saveEditor(this);
|
||||
}
|
||||
},
|
||||
|
||||
["speed"].forEach((field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
const value = parseFloat(element?.value) || 0;
|
||||
console.log(`----------------> Saving ${field}: ${value}`);
|
||||
node[field] = value;
|
||||
});
|
||||
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Main UI -->
|
||||
|
||||
<script type="text/html" data-template-name="valve">
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Main UI Template -->
|
||||
<script type="text/html" data-template-name="valve">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="node-input-name"
|
||||
placeholder="Valve Name"
|
||||
style="width:70%;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- NOT USED
|
||||
<div class="form-row">
|
||||
<label for="node-input-startup"><i class="fa fa-clock-o"></i> Startup Time</label>
|
||||
<input type="number" id="node-input-startup" placeholder="0" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-warmup"><i class="fa fa-clock-o"></i> Warmup Time</label>
|
||||
<input type="number" id="node-input-warmup" placeholder="0" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-shutdown"><i class="fa fa-clock-o"></i> Shutdown Time</label>
|
||||
<input type="number" id="node-input-shutdown" placeholder="0" />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-cooldown"><i class="fa fa-clock-o"></i> Cooldown Time</label>
|
||||
<input type="number" id="node-input-cooldown" placeholder="0" />
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- Optional Extended Fields: supplier, type, subType, model
|
||||
<hr />
|
||||
<div class="form-row">
|
||||
<label for="node-input-supplier"
|
||||
><i class="fa fa-industry"></i> Supplier</label
|
||||
>
|
||||
<select id="node-input-supplier" style="width:60%;">
|
||||
<option value="">(optional)</option>
|
||||
</select>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- NOT USED
|
||||
<div class="form-row">
|
||||
<label for="node-input-subType"
|
||||
><i class="fa fa-puzzle-piece"></i> SubType</label
|
||||
>
|
||||
<select id="node-input-subType" style="width:60%;">
|
||||
<option value="">(optional)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-model"><i class="fa fa-wrench"></i> Model</label>
|
||||
<select id="node-input-model" style="width:60%;">
|
||||
<option value="">(optional)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-unit"><i class="fa fa-balance-scale"></i> Unit</label>
|
||||
<select id="node-input-unit" style="width:60%;"></select>
|
||||
</div>
|
||||
-->
|
||||
<hr />
|
||||
|
||||
<!-- loglevel checkbox -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-enableLog"
|
||||
><i class="fa fa-cog"></i> Enable Log</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="node-input-enableLog"
|
||||
style="width:20px; vertical-align:baseline;"
|
||||
/>
|
||||
<span>Enable logging</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="row-logLevel">
|
||||
<label for="node-input-logLevel"><i class="fa fa-cog"></i> Log Level</label>
|
||||
<select id="node-input-logLevel" style="width:60%;">
|
||||
<option value="info">Info</option>
|
||||
<option value="debug">Debug</option>
|
||||
<option value="warn">Warn</option>
|
||||
<option value="error">Error</option>
|
||||
</select>
|
||||
<!-- Node-specific controls -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-speed"><i class="fa fa-clock-o"></i> Reaction Speed</label>
|
||||
<input type="number" id="node-input-speed" style="width:60%;" />
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Help Text Template -->
|
||||
<script type="text/html" data-help-name="valve">
|
||||
<p>
|
||||
<b>Valve Node</b>: Configure the behavior of a valve
|
||||
used in a digital twin.
|
||||
</p>
|
||||
<ul>
|
||||
<!-- NOT USED
|
||||
<li><b>Supplier:</b> Select a supplier to populate valve options.</li>
|
||||
<li><b>SubType:</b> Select a subtype if applicable to further categorize the asset.</li>
|
||||
<li><b>Model:</b> Define the specific model for more granular asset configuration.</li>
|
||||
<li><b>Unit:</b> Assign a unit to standardize measurements or operations.</li>
|
||||
<li><b>Speed:</b> Reaction speed of the machine in response to inputs.</li>
|
||||
<li><b>Startup:</b> Define the startup time for the machine.</li>
|
||||
<li><b>Warmup:</b> Define the warmup time for the machine.</li>
|
||||
<li><b>Shutdown:</b> Define the shutdown time for the machine.</li>
|
||||
<li><b>Cooldown:</b> Define the cooldown time for the machine.</li>
|
||||
-->
|
||||
<li><b>Enable Log:</b> Enable or disable logging for the valve.</li>
|
||||
<li><b>Log Level:</b> Set the log level (Info, Debug, Warn, Error).</li>
|
||||
</ul>
|
||||
</script>
|
||||
<!-- Optional Extended Fields: supplier, cat, type, model, unit -->
|
||||
<!-- Asset fields will be injected here -->
|
||||
<div id="asset-fields-placeholder"></div>
|
||||
|
||||
<!-- loglevel checkbox -->
|
||||
<div id="logger-fields-placeholder"></div>
|
||||
|
||||
<!-- Position fields will be injected here -->
|
||||
<div id="position-fields-placeholder"></div>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/html" data-help-name="valve">
|
||||
<p><b>Valve Node</b>: Controls the flow of data through a valve-like mechanism.</p>
|
||||
<p>This node is used to manage the flow of data in a process, similar to how a valve controls the flow of liquids or gases.</p>
|
||||
|
||||
</script>
|
||||
|
||||
278
valve.js
278
valve.js
@@ -1,250 +1,40 @@
|
||||
module.exports = function (RED) {
|
||||
function valve(config) {
|
||||
//create node
|
||||
const nameOfNode = 'valve'; // this is the name of the node, it should match the file name and the node type in Node-RED
|
||||
const nodeClass = require('./src/nodeClass.js'); // this is the specific node class
|
||||
const { MenuManager, configManager } = require('generalFunctions');
|
||||
|
||||
// This is the main entry point for the Node-RED node, it will register the node and setup the endpoints
|
||||
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);
|
||||
//call this => node so whenver you want to call a node function type node and the function behind it
|
||||
var node = this;
|
||||
// Then create your custom class and attach it
|
||||
this.nodeClass = new nodeClass(config, RED, this, nameOfNode);
|
||||
});
|
||||
|
||||
// Setup admin UIs
|
||||
const menuMgr = new MenuManager(); //this will handle the menu endpoints so we can load them dynamically
|
||||
const cfgMgr = new configManager(); // this will handle the config endpoints so we can load them dynamically
|
||||
|
||||
// Register the different menu's for the measurement node (in the future we could automate this further by refering to the config)
|
||||
RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => {
|
||||
try {
|
||||
const Valve = require("./dependencies/valveClass"); // Importeer de valve class
|
||||
const OutputUtils = require("../generalFunctions/helper/outputUtils"); // Importeer de OutputUtils class
|
||||
|
||||
const valveConfig = { // Configuratie van de valve
|
||||
general: {
|
||||
name: config.name || "Default Valve",
|
||||
id: node.id,
|
||||
logging: {
|
||||
enabled: config.eneableLog,
|
||||
logLevel: config.logLevel
|
||||
}
|
||||
},
|
||||
asset: {
|
||||
supplier: config.supplier || "Unknown",
|
||||
/* NOT USED
|
||||
type: config.valveType || "generic",
|
||||
subType: config.subType || "generic",
|
||||
model: config.model || "generic",
|
||||
valveCurve: config.valveCurve */
|
||||
}
|
||||
};
|
||||
|
||||
const stateConfig = { // Configuratie van de state
|
||||
general: {
|
||||
logging: {
|
||||
enabled: config.eneableLog,
|
||||
logLevel: config.logLevel
|
||||
}
|
||||
},
|
||||
|
||||
/* NOT USED
|
||||
movement: {
|
||||
speed: Number(config.speed)
|
||||
},
|
||||
time: {
|
||||
starting: Number(config.startup),
|
||||
warmingup: Number(config.warmup),
|
||||
stopping: Number(config.shutdown),
|
||||
coolingdown: Number(config.cooldown)
|
||||
} */
|
||||
};
|
||||
|
||||
// Create valve instance
|
||||
const v = new Valve(valveConfig, stateConfig);
|
||||
|
||||
// put m on node memory as source
|
||||
node.source = v;
|
||||
|
||||
//load output utils
|
||||
const output = new OutputUtils();
|
||||
|
||||
//Hier worden node-red statussen en metingen geupdate
|
||||
function updateNodeStatus() {
|
||||
try {
|
||||
const mode = v.currentMode; // modus is bijv. auto, manual, etc.
|
||||
const state = v.state.getCurrentState(); //is bijv. operational, idle, off, etc.
|
||||
const flow = Math.round(v.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue());
|
||||
let deltaP = v.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue();
|
||||
if (deltaP !== null) {
|
||||
deltaP = parseFloat(deltaP.toFixed(0));
|
||||
} //afronden op 4 decimalen indien geen "null"
|
||||
if(isNaN(deltaP)) {
|
||||
deltaP = "∞";
|
||||
}
|
||||
const roundedPosition = Math.round(v.state.getCurrentPosition() * 100) / 100;
|
||||
let symbolState;
|
||||
switch(state){
|
||||
case "off":
|
||||
symbolState = "⬛";
|
||||
break;
|
||||
case "idle":
|
||||
symbolState = "⏸️";
|
||||
break;
|
||||
case "operational":
|
||||
symbolState = "⏵️";
|
||||
break;
|
||||
case "starting":
|
||||
symbolState = "⏯️";
|
||||
break;
|
||||
case "warmingup":
|
||||
symbolState = "🔄";
|
||||
break;
|
||||
case "accelerating":
|
||||
symbolState = "⏩";
|
||||
break;
|
||||
case "stopping":
|
||||
symbolState = "⏹️";
|
||||
break;
|
||||
case "coolingdown":
|
||||
symbolState = "❄️";
|
||||
break;
|
||||
case "decelerating":
|
||||
symbolState = "⏪";
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
let status;
|
||||
switch (state) {
|
||||
case "off":
|
||||
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
|
||||
break;
|
||||
case "idle":
|
||||
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "operational":
|
||||
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
case "starting":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "warmingup":
|
||||
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
case "accelerating":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar` }; //deltaP toegevoegd
|
||||
break;
|
||||
case "stopping":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "coolingdown":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
break;
|
||||
case "decelerating":
|
||||
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar`}; //deltaP toegevoegd
|
||||
break;
|
||||
default:
|
||||
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
|
||||
}
|
||||
return status;
|
||||
} catch (error) {
|
||||
node.error("Error in updateNodeStatus: " + error.message);
|
||||
return { fill: "red", shape: "ring", text: "Status Error" };
|
||||
}
|
||||
}
|
||||
|
||||
function tick() { // versturen van output messages --> tick van tick op de klop. Is tijd based en niet event based
|
||||
try {
|
||||
const status = updateNodeStatus();
|
||||
node.status(status);
|
||||
|
||||
//v.tick();
|
||||
|
||||
//get output
|
||||
const classOutput = v.getOutput();
|
||||
const dbOutput = output.formatMsg(classOutput, v.config, "influxdb");
|
||||
const pOutput = output.formatMsg(classOutput, v.config, "process");
|
||||
|
||||
//only send output on values that changed
|
||||
let msgs = [];
|
||||
msgs[0] = pOutput;
|
||||
msgs[1] = dbOutput;
|
||||
|
||||
node.send(msgs);
|
||||
|
||||
} catch (error) {
|
||||
node.error("Error in tick function: " + error);
|
||||
node.status({ fill: "red", shape: "ring", text: "Tick Error" });
|
||||
}
|
||||
}
|
||||
|
||||
// register child on first output this timeout is needed because of node - red stuff
|
||||
setTimeout(
|
||||
() => {
|
||||
|
||||
/*---execute code on first start----*/
|
||||
let msgs = [];
|
||||
|
||||
msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upStream" };
|
||||
msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downStream" };
|
||||
|
||||
//send msg
|
||||
node.send(msgs);
|
||||
},
|
||||
100
|
||||
);
|
||||
|
||||
//declare refresh interval internal node
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
//---execute code on first start----
|
||||
this.interval_id = setInterval(function(){ tick() },1000)
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
node.on("input", function(msg, send, done) { // Functie die wordt aangeroepen wanneer er een input wordt ontvangen
|
||||
console.log("CKECK! Input received: ", msg.topic, msg.payload); // CHECKPOINT
|
||||
try {
|
||||
let result;
|
||||
switch(msg.topic) {
|
||||
case 'registerChild':
|
||||
const childId = msg.payload;
|
||||
const childObj = RED.nodes.getNode(childId);
|
||||
v.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
||||
break;
|
||||
case 'setMode':
|
||||
v.setMode(msg.payload);
|
||||
break;
|
||||
case 'execSequence':
|
||||
const { source: seqSource, action: seqAction, parameter } = msg.payload;
|
||||
v.handleInput(seqSource, seqAction, parameter);
|
||||
break;
|
||||
case 'execMovement':
|
||||
const { source: mvSource, action: mvAction, setpoint } = msg.payload;
|
||||
v.handleInput(mvSource, mvAction, Number(setpoint));
|
||||
break;
|
||||
case 'emergencystop':
|
||||
const { source: esSource, action: esAction } = msg.payload;
|
||||
v.handleInput(esSource, esAction);
|
||||
break;
|
||||
case 'showcurve':
|
||||
v.showCurve();
|
||||
send({ topic : "Showing curve" , payload: v.showCurve() });
|
||||
break;
|
||||
case 'newFlow': //Als nieuwe flow van header node dan moet deltaP weer opnieuw worden berekend en doorgegeven aan header node
|
||||
const { source: nfSource, action: nfAction, parameter: nfParameter } = msg.payload; //parameter is new flow, action should be "calcNewDeltaP"
|
||||
v.handleInput(nfSource, nfAction, nfParameter);
|
||||
}
|
||||
|
||||
if (done) done();
|
||||
} catch (error) {
|
||||
node.error("Error processing input: " + error.message);
|
||||
if (done) done(error);
|
||||
}
|
||||
});
|
||||
|
||||
node.on('close', function(done) { // Functie die wordt aangeroepen wanneer de node wordt gesloten
|
||||
if (node.interval_id) clearTimeout(node.interval_id);
|
||||
if (node.tick_interval) clearInterval(node.tick_interval);
|
||||
if (done) done();
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
node.error("Fatal error in node initialization: " + error.stack);
|
||||
node.status({fill: "red", shape: "ring", text: "Fatal Error"});
|
||||
const script = menuMgr.createEndpoint(nameOfNode, ['asset','logger','position']);
|
||||
res.type('application/javascript').send(script);
|
||||
} catch (err) {
|
||||
res.status(500).send(`// Error generating menu: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint to get the configuration data for the specific node
|
||||
RED.httpAdmin.get(`/${nameOfNode}/configData.js`, (req, res) => {
|
||||
try {
|
||||
const script = cfgMgr.createEndpoint(nameOfNode);
|
||||
// Send the configuration data as JSON response
|
||||
res.type('application/javascript').send(script);
|
||||
} catch (err) {
|
||||
res.status(500).send(`// Error generating configData: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
RED.nodes.registerType("valve", valve);
|
||||
};
|
||||
Reference in New Issue
Block a user