Compare commits
30 Commits
a6dfbec5d0
...
dev-Rene
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
108d2e23ca | ||
|
|
446ef81f24 | ||
|
|
966ba06faa | ||
|
|
e8c96c4b1e | ||
|
|
f083e7596a | ||
|
|
6ca6e536a5 | ||
|
|
fb75fb8a11 | ||
|
|
6528c966d8 | ||
|
|
994cf641a3 | ||
|
|
6ae622b6bf | ||
|
|
4b5ec33c1d | ||
|
|
51f966cfb9 | ||
|
|
4ae6beba37 | ||
|
|
d2a0274eb3 | ||
|
|
2073207df1 | ||
|
|
bc916c0165 | ||
|
|
5357290b41 | ||
|
|
000bee7190 | ||
|
|
b0c18e7bae | ||
|
|
38408c7bc3 | ||
|
|
ddfc612894 | ||
|
|
f4696618a6 | ||
|
|
82bb55e10f | ||
|
|
7820bd2ad2 | ||
|
|
d9c2699566 | ||
|
|
fa7c59fcab | ||
|
|
3fc8dbefe8 | ||
|
|
f24a5fb90b | ||
|
|
8d2a3b80e7 | ||
|
|
b2eb8fe900 |
98
LICENSE
98
LICENSE
@@ -1,9 +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 RnD
|
||||
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 deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 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.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
@@ -1,3 +1,13 @@
|
||||
<!--
|
||||
| S88-niveau | Primair (blokkleur) | Tekstkleur |
|
||||
| ---------------------- | ------------------- | ---------- |
|
||||
| **Area** | `#0f52a5` | wit |
|
||||
| **Process Cell** | `#0c99d9` | wit |
|
||||
| **Unit** | `#50a8d9` | zwart |
|
||||
| **Equipment (Module)** | `#86bbdd` | zwart |
|
||||
| **Control Module** | `#a9daee` | zwart |
|
||||
|
||||
-->
|
||||
<!-- Load the dynamic menu & config endpoints -->
|
||||
<script src="/rotatingMachine/menu.js"></script>
|
||||
<script src="/rotatingMachine/configData.js"></script>
|
||||
@@ -5,16 +15,16 @@
|
||||
<script>
|
||||
RED.nodes.registerType("rotatingMachine", {
|
||||
category: "EVOLV",
|
||||
color: "#4f8582",
|
||||
color: "#86bbdd",
|
||||
defaults: {
|
||||
// Define default properties
|
||||
name: { value: ""}, // use asset category as name ?
|
||||
|
||||
// Define specific properties
|
||||
speed: { value: 1, required: true },
|
||||
startup: { value: 0 },
|
||||
warmup: { value: 0 },
|
||||
shutdown: { value: 0 },
|
||||
cooldown: { value: 0 },
|
||||
movementMode : { value: "staticspeed" }, // static or dynamic
|
||||
machineCurve : { value: {}},
|
||||
|
||||
//define asset properties
|
||||
@@ -32,16 +42,20 @@
|
||||
//physicalAspect
|
||||
positionVsParent: { value: "" },
|
||||
positionIcon: { value: "" },
|
||||
hasDistance: { value: false },
|
||||
distance: { value: 0 },
|
||||
distanceUnit: { value: "m" },
|
||||
distanceDescription: { value: "" }
|
||||
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 3,
|
||||
inputLabels: ["Input"],
|
||||
outputLabels: ["process", "dbase", "parent"],
|
||||
icon: "font-awesome/fa-tachometer",
|
||||
icon: "font-awesome/fa-cog",
|
||||
|
||||
label: function () {
|
||||
return this.positionIcon + " " + this.category.slice(0, -1) || "Machine";
|
||||
return this.positionIcon + " " + this.category || "Machine";
|
||||
},
|
||||
|
||||
oneditprepare: function() {
|
||||
@@ -56,15 +70,20 @@
|
||||
waitForMenuData();
|
||||
|
||||
// your existing project‐settings & asset dropdown logic can remain here
|
||||
document.getElementById("node-input-speed"),
|
||||
document.getElementById("node-input-startup"),
|
||||
document.getElementById("node-input-warmup"),
|
||||
document.getElementById("node-input-shutdown"),
|
||||
document.getElementById("node-input-cooldown")
|
||||
document.getElementById("node-input-speed");
|
||||
document.getElementById("node-input-startup");
|
||||
document.getElementById("node-input-warmup");
|
||||
document.getElementById("node-input-shutdown");
|
||||
document.getElementById("node-input-cooldown");
|
||||
const movementMode = document.getElementById("node-input-movementMode");
|
||||
if (movementMode) {
|
||||
movementMode.value = this.movementMode || "staticspeed";
|
||||
}
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
const node = this;
|
||||
|
||||
// save asset fields
|
||||
if (window.EVOLV?.nodes?.rotatingMachine?.assetMenu?.saveEditor) {
|
||||
window.EVOLV.nodes.rotatingMachine.assetMenu.saveEditor(this);
|
||||
@@ -77,10 +96,16 @@
|
||||
if (window.EVOLV?.nodes?.rotatingMachine?.positionMenu?.saveEditor) {
|
||||
window.EVOLV.nodes.rotatingMachine.positionMenu.saveEditor(this);
|
||||
}
|
||||
// …plus any custom saves for speed, startup, etc.
|
||||
["speed", "startup", "warmup", "shutdown", "cooldown"].forEach(
|
||||
(field) => (node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0)
|
||||
);
|
||||
|
||||
["speed", "startup", "warmup", "shutdown", "cooldown"].forEach((field) => {
|
||||
const element = document.getElementById(`node-input-${field}`);
|
||||
const value = parseFloat(element?.value) || 0;
|
||||
console.log(`----------------> Saving ${field}: ${value}`);
|
||||
node[field] = value;
|
||||
});
|
||||
|
||||
node.movementMode = document.getElementById("node-input-movementMode").value;
|
||||
console.log(`----------------> Saving movementMode: ${node.movementMode}`);
|
||||
|
||||
}
|
||||
});
|
||||
@@ -110,6 +135,13 @@
|
||||
<label for="node-input-cooldown"><i class="fa fa-clock-o"></i> Cooldown Time</label>
|
||||
<input type="number" id="node-input-cooldown" style="width:60%;" />
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-movementMode"><i class="fa fa-exchange"></i> Movement Mode</label>
|
||||
<select id="node-input-movementMode" style="width:60%;">
|
||||
<option value="staticspeed">Static</option>
|
||||
<option value="dynspeed">Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Asset fields injected here -->
|
||||
<div id="asset-fields-placeholder"></div>
|
||||
|
||||
@@ -20,12 +20,13 @@ class nodeClass {
|
||||
this.RED = RED; // This is the Node-RED runtime API, we can use this to create endpoints if needed
|
||||
this.name = nameOfNode; // This is the name of the node, it should match the file name and the node type in Node-RED
|
||||
this.source = null; // Will hold the specific class instance
|
||||
this.config = null; // Will hold the merged configuration
|
||||
|
||||
// Load default & UI config
|
||||
this._loadConfig(uiConfig,this.node);
|
||||
|
||||
// Instantiate core Measurement class
|
||||
this._setupSpecificClass();
|
||||
// Instantiate core class
|
||||
this._setupSpecificClass(uiConfig);
|
||||
|
||||
// Wire up event and lifecycle handlers
|
||||
this._bindEvents();
|
||||
@@ -40,13 +41,10 @@ class nodeClass {
|
||||
* @param {object} uiConfig - Raw config from Node-RED UI.
|
||||
*/
|
||||
_loadConfig(uiConfig,node) {
|
||||
const cfgMgr = new configManager();
|
||||
this.defaultConfig = cfgMgr.getConfig(this.name);
|
||||
|
||||
// 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: {
|
||||
@@ -64,7 +62,7 @@ class nodeClass {
|
||||
unit: uiConfig.unit
|
||||
},
|
||||
functionality: {
|
||||
positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified
|
||||
positionVsParent: uiConfig.positionVsParent
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,9 +73,11 @@ class nodeClass {
|
||||
/**
|
||||
* Instantiate the core Measurement logic and store as source.
|
||||
*/
|
||||
_setupSpecificClass() {
|
||||
_setupSpecificClass(uiConfig) {
|
||||
const machineConfig = this.config;
|
||||
|
||||
console.log(`----------------> Loaded movementMode in nodeClass: ${uiConfig.movementMode}`);
|
||||
|
||||
// need extra state for this
|
||||
const stateConfig = {
|
||||
general: {
|
||||
@@ -87,13 +87,14 @@ class nodeClass {
|
||||
}
|
||||
},
|
||||
movement: {
|
||||
speed: Number(machineConfig.speed)
|
||||
speed: Number(uiConfig.speed),
|
||||
mode: uiConfig.movementMode
|
||||
},
|
||||
time: {
|
||||
starting: Number(machineConfig.startup),
|
||||
warmingup: Number(machineConfig.warmup),
|
||||
stopping: Number(machineConfig.shutdown),
|
||||
coolingdown: Number(machineConfig.cooldown)
|
||||
starting: Number(uiConfig.startup),
|
||||
warmingup: Number(uiConfig.warmup),
|
||||
stopping: Number(uiConfig.shutdown),
|
||||
coolingdown: Number(uiConfig.cooldown)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,12 +106,10 @@ class nodeClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Measurement events to Node-RED status updates. Using internal emitter. --> REMOVE LATER WE NEED ONLY COMPLETE CHILDS AND THEN CHECK FOR UPDATES
|
||||
* Bind events to Node-RED status updates. Using internal emitter. --> REMOVE LATER WE NEED ONLY COMPLETE CHILDS AND THEN CHECK FOR UPDATES
|
||||
*/
|
||||
_bindEvents() {
|
||||
this.source.emitter.on('mAbs', (val) => {
|
||||
this.node.status({ fill: 'green', shape: 'dot', text: `${val} ${this.config.general.unit}` });
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_updateNodeStatus() {
|
||||
@@ -118,8 +117,8 @@ class nodeClass {
|
||||
try {
|
||||
const mode = m.currentMode;
|
||||
const state = m.state.getCurrentState();
|
||||
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue());
|
||||
const power = Math.round(m.measurements.type("power").variant("predicted").position('upstream').getCurrentValue());
|
||||
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue('m3/h'));
|
||||
const power = Math.round(m.measurements.type("power").variant("predicted").position('atequipment').getCurrentValue('kW'));
|
||||
let symbolState;
|
||||
switch(state){
|
||||
case "off":
|
||||
@@ -149,6 +148,9 @@ class nodeClass {
|
||||
case "decelerating":
|
||||
symbolState = "⏪";
|
||||
break;
|
||||
case "maintenance":
|
||||
symbolState = "🔧";
|
||||
break;
|
||||
}
|
||||
const position = m.state.getCurrentPosition();
|
||||
const roundedPosition = Math.round(position * 100) / 100;
|
||||
@@ -206,7 +208,7 @@ class nodeClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the periodic tick loop to drive the Measurement class.
|
||||
* Start the periodic tick loop.
|
||||
*/
|
||||
_startTickLoop() {
|
||||
setTimeout(() => {
|
||||
@@ -228,8 +230,8 @@ class nodeClass {
|
||||
//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');
|
||||
const processMsg = this._output.formatMsg(raw, this.source.config, 'process');
|
||||
const influxMsg = this._output.formatMsg(raw, this.source.config, 'influxdb');
|
||||
|
||||
// Send only updated outputs on ports 0 & 1
|
||||
this.node.send([processMsg, influxMsg]);
|
||||
@@ -269,9 +271,9 @@ class nodeClass {
|
||||
const { source: esSource, action: esAction } = msg.payload;
|
||||
m.handleInput(esSource, esAction);
|
||||
break;
|
||||
case 'showCompleteCurve':
|
||||
m.showCompleteCurve();
|
||||
send({ topic : "Showing curve" , payload: m.showCompleteCurve() });
|
||||
case 'showWorkingCurves':
|
||||
m.showWorkingCurves();
|
||||
send({ topic : "Showing curve" , payload: m.showWorkingCurves() });
|
||||
break;
|
||||
case 'CoG':
|
||||
m.showCoG();
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
/**
|
||||
* @file machine.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.
|
||||
*
|
||||
* @summary A class to interact and manipulate machines with a non-euclidian curve
|
||||
* @description A class to interact and manipulate machines with a non-euclidian curve
|
||||
* @module machine
|
||||
* @exports machine
|
||||
* @version 0.1.0
|
||||
* @since 0.1.0
|
||||
*
|
||||
* Author:
|
||||
* - Rene De Ren
|
||||
* Email:
|
||||
* - r.de.ren@brabantsedelta.nl
|
||||
*
|
||||
* Add functionality later
|
||||
// -------- Operational Metrics -------- //
|
||||
maintenanceAlert: this.state.checkMaintenanceStatus()
|
||||
|
||||
|
||||
*/
|
||||
|
||||
//load local dependencies
|
||||
const EventEmitter = require('events');
|
||||
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');
|
||||
const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop} = require('generalFunctions');
|
||||
|
||||
class Machine {
|
||||
|
||||
@@ -53,6 +8,7 @@ class Machine {
|
||||
|
||||
//basic setup
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
|
||||
this.logger = new logger(machineConfig.general.logging.enabled,machineConfig.general.logging.logLevel, machineConfig.general.name);
|
||||
this.configManager = new configManager();
|
||||
this.defaultConfig = this.configManager.getConfig('rotatingMachine'); // Load default config for rotating machine ( use software type name ? )
|
||||
@@ -60,10 +16,16 @@ class Machine {
|
||||
|
||||
// Load a specific curve
|
||||
this.model = machineConfig.asset.model; // Get the model from the machineConfig
|
||||
this.curve = this.model ? loadCurve(this.model) : null;
|
||||
this.curve = this.model ? loadCurve(this.model) : null; // we need to convert the curve and add units to the curve information
|
||||
|
||||
//Init config and check if it is valid
|
||||
this.config = this.configUtils.initConfig(machineConfig);
|
||||
|
||||
//add unique name for this node.
|
||||
this.config = this.configUtils.updateConfig(this.config, {general:{name: this.config.functionality?.softwareType + "_" + machineConfig.general.id}}); // add unique name if not present
|
||||
|
||||
if (!this.model || !this.curve) {
|
||||
this.logger.warning(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`);
|
||||
this.logger.error(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`);
|
||||
// Set prediction objects to null to prevent method calls
|
||||
this.predictFlow = null;
|
||||
this.predictPower = null;
|
||||
@@ -72,8 +34,8 @@ class Machine {
|
||||
}
|
||||
else{
|
||||
this.hasCurve = true;
|
||||
machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
||||
this.config = this.configUtils.initConfig(machineConfig);
|
||||
this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } });
|
||||
//machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
||||
this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq }); // load nq (x : ctrl , y : flow relationship)
|
||||
this.predictPower = new predict({ curve: this.config.asset.machineCurve.np }); // load np (x : ctrl , y : power relationship)
|
||||
this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
|
||||
@@ -83,9 +45,18 @@ class Machine {
|
||||
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
||||
|
||||
// Initialize measurements
|
||||
this.measurements = new MeasurementContainer();
|
||||
this.measurements = new MeasurementContainer({
|
||||
autoConvert: true,
|
||||
windowSize: 50,
|
||||
defaultUnits: {
|
||||
pressure: 'mbar',
|
||||
flow: this.config.general.unit,
|
||||
power: 'kW',
|
||||
temperature: 'C'
|
||||
}
|
||||
});
|
||||
|
||||
this.interpolation = new interpolation();
|
||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
||||
|
||||
this.flowDrift = null;
|
||||
|
||||
@@ -98,17 +69,105 @@ class Machine {
|
||||
this.absDistFromPeak = 0;
|
||||
this.relDistFromPeak = 0;
|
||||
|
||||
// When position state changes, update position
|
||||
this.state.emitter.on("positionChange", (data) => {
|
||||
this.logger.debug(`Position change detected: ${data}`);
|
||||
this.updatePosition();
|
||||
});
|
||||
|
||||
//this.calcCog();
|
||||
//When state changes look if we need to do other updates
|
||||
this.state.emitter.on("stateChange", (newState) => {
|
||||
this.logger.debug(`State change detected: ${newState}`);
|
||||
this._updateState();
|
||||
});
|
||||
|
||||
|
||||
//perform init for certain values
|
||||
this._init();
|
||||
|
||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
||||
|
||||
|
||||
}
|
||||
|
||||
_init(){
|
||||
//assume standard temperature is 20degrees
|
||||
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
|
||||
//assume standard atm pressure is at sea level
|
||||
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
|
||||
//populate min and max
|
||||
const flowunit = this.config.general.unit;
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit)
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
||||
}
|
||||
|
||||
_updateState(){
|
||||
const isOperational = this._isOperationalState();
|
||||
if(!isOperational){
|
||||
//overrule the last prediction this should be 0 now
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------- Register child events -------------------*/
|
||||
registerChild(child, softwareType) {
|
||||
this.logger.debug('Setting up child event for softwaretype ' + softwareType);
|
||||
|
||||
if(softwareType === "measurement"){
|
||||
const position = child.config.functionality.positionVsParent;
|
||||
const distance = child.config.functionality.distanceVsParent || 0;
|
||||
const measurementType = child.config.asset.type;
|
||||
const key = `${measurementType}_${position}`;
|
||||
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
|
||||
const eventName = `${measurementType}.measured.${position}`;
|
||||
|
||||
this.logger.debug(`Setting up listener for ${eventName} from child ${child.config.general.name}`);
|
||||
// Register event listener for measurement updates
|
||||
child.measurements.emitter.on(eventName, (eventData) => {
|
||||
this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
|
||||
|
||||
|
||||
this.logger.debug(` Emitting... ${eventName} with data:`);
|
||||
// Store directly in parent's measurement container
|
||||
this.measurements
|
||||
.type(measurementType)
|
||||
.variant("measured")
|
||||
.position(position)
|
||||
.value(eventData.value, eventData.timestamp, eventData.unit);
|
||||
|
||||
// Call the appropriate handler
|
||||
this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Centralized handler dispatcher
|
||||
_callMeasurementHandler(measurementType, value, position, context) {
|
||||
switch (measurementType) {
|
||||
case 'pressure':
|
||||
this.updateMeasuredPressure(value, position, context);
|
||||
break;
|
||||
|
||||
case 'flow':
|
||||
this.updateMeasuredFlow(value, position, context);
|
||||
break;
|
||||
|
||||
case 'temperature':
|
||||
this.updateMeasuredTemperature(value, position, context);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
||||
// Generic handler - just update position
|
||||
this.updatePosition();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------- END child stuff -------------//
|
||||
|
||||
// Method to assess drift using errorMetrics
|
||||
assessDrift(measurement, processMin, processMax) {
|
||||
this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
|
||||
@@ -144,46 +203,68 @@ class Machine {
|
||||
// -------- Mode and Input Management -------- //
|
||||
isValidSourceForMode(source, mode) {
|
||||
const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
|
||||
return allowedSourcesSet.has(source);
|
||||
const allowed = allowedSourcesSet.has(source);
|
||||
allowed?
|
||||
this.logger.debug(`source is allowed proceeding with ${source} for mode ${mode}`) :
|
||||
this.logger.warn(`${source} is not allowed in mode ${mode}`);
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
isValidActionForMode(action, mode) {
|
||||
const allowedActionsSet = this.config.mode.allowedActions[mode] || [];
|
||||
return allowedActionsSet.has(action);
|
||||
const allowed = allowedActionsSet.has(action);
|
||||
allowed ?
|
||||
this.logger.debug(`Action is allowed proceeding with ${action} for mode ${mode}`) :
|
||||
this.logger.warn(`${action} is not allowed in mode ${mode}`);
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
//sanitize input
|
||||
if( typeof action !== 'string'){this.logger.error(`Action must be string`); return;}
|
||||
//convert to lower case to avoid to many mistakes in commands
|
||||
action = action.toLowerCase();
|
||||
|
||||
// check for validity of the request
|
||||
if(!this.isValidActionForMode(action,this.currentMode)){return ;}
|
||||
if (!this.isValidSourceForMode(source, this.currentMode)) {return ;}
|
||||
|
||||
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case "execSequence":
|
||||
await this.executeSequence(parameter);
|
||||
//recalc flow and power
|
||||
this.updatePosition();
|
||||
break;
|
||||
case "execMovement":
|
||||
await this.setpoint(parameter);
|
||||
break;
|
||||
case "flowMovement":
|
||||
|
||||
case "execsequence":
|
||||
return await this.executeSequence(parameter);
|
||||
|
||||
case "execmovement":
|
||||
return await this.setpoint(parameter);
|
||||
|
||||
case "entermaintenance":
|
||||
|
||||
return await this.executeSequence(parameter);
|
||||
|
||||
|
||||
case "exitmaintenance":
|
||||
return await this.executeSequence(parameter);
|
||||
|
||||
case "flowmovement":
|
||||
// Calculate the control value for a desired flow
|
||||
const pos = this.calcCtrl(parameter);
|
||||
// Move to the desired setpoint
|
||||
await this.setpoint(pos);
|
||||
break;
|
||||
case "emergencyStop":
|
||||
return await this.setpoint(pos);
|
||||
|
||||
case "emergencystop":
|
||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||
await this.executeSequence("emergencyStop");
|
||||
break;
|
||||
case "statusCheck":
|
||||
return await this.executeSequence("emergencyStop");
|
||||
|
||||
case "statuscheck":
|
||||
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(`Action '${action}' is not implemented.`);
|
||||
break;
|
||||
@@ -193,10 +274,17 @@ class Machine {
|
||||
} catch (error) {
|
||||
this.logger.error(`Error handling input: ${error}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abortMovement(reason = "group override") {
|
||||
if (this.state?.abortCurrentMovement) {
|
||||
this.state.abortCurrentMovement(reason);
|
||||
}
|
||||
}
|
||||
|
||||
setMode(newMode) {
|
||||
const availableModes = defaultConfig.mode.current.rules.values.map(v => v.value);
|
||||
const availableModes = this.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;
|
||||
@@ -233,6 +321,9 @@ class Machine {
|
||||
break; // Exit sequence execution on error
|
||||
}
|
||||
}
|
||||
|
||||
//recalc flow and power
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
async setpoint(setpoint) {
|
||||
@@ -243,6 +334,8 @@ class Machine {
|
||||
throw new Error("Invalid setpoint: Setpoint must be a non-negative number.");
|
||||
}
|
||||
|
||||
this.logger.info(`Setting setpoint to ${setpoint}. Current position: ${this.state.getCurrentPosition()}`);
|
||||
|
||||
// Move to the desired setpoint
|
||||
await this.state.moveTo(setpoint);
|
||||
|
||||
@@ -253,55 +346,54 @@ class Machine {
|
||||
|
||||
// Calculate flow based on current pressure and position
|
||||
calcFlow(x) {
|
||||
if(!this.hasCurve) {
|
||||
const state = this.state.getCurrentState();
|
||||
|
||||
if (!["operational", "accelerating", "decelerating"].includes(state)) {
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0);
|
||||
if(this.hasCurve) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
||||
this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//this.predictFlow.currentX = x; Decrepated
|
||||
const cFlow = this.predictFlow.y(x);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.config.general.unit);
|
||||
//this.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
||||
return cFlow;
|
||||
}
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for flow calculation. Returning 0.`);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0);
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.config.general.unit);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.config.general.unit);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
// Calculate power based on current pressure and position
|
||||
calcPower(x) {
|
||||
if(!this.hasCurve) {
|
||||
const state = this.state.getCurrentState();
|
||||
if (!["operational", "accelerating", "decelerating"].includes(state)) {
|
||||
this.measurements.type("power").variant("predicted").position('upstream').value(0);
|
||||
if(this.hasCurve) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
this.logger.debug(`Machine is not operational. Setting predicted power to 0.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//this.predictPower.currentX = x; Decrepated
|
||||
const cPower = this.predictPower.y(x);
|
||||
this.measurements.type("power").variant("predicted").position('upstream').value(cPower);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(cPower);
|
||||
//this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
||||
return cPower;
|
||||
}
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
|
||||
this.measurements.type("power").variant("predicted").position('upstream').value(0);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
// calculate the power consumption using only flow and pressure
|
||||
inputFlowCalcPower(flow) {
|
||||
if(!this.hasCurve) {
|
||||
if(this.hasCurve) {
|
||||
|
||||
this.predictCtrl.currentX = flow;
|
||||
const cCtrl = this.predictCtrl.y(flow);
|
||||
@@ -312,34 +404,40 @@ class Machine {
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
|
||||
this.measurements.type("power").variant("predicted").position('upstream').value(0);
|
||||
this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
// Function to predict control value for a desired flow
|
||||
calcCtrl(x) {
|
||||
if(!this.hasCurve) {
|
||||
if(this.hasCurve) {
|
||||
this.predictCtrl.currentX = x;
|
||||
const cCtrl = this.predictCtrl.y(x);
|
||||
this.measurements.type("ctrl").variant("predicted").position('upstream').value(cCtrl);
|
||||
this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(cCtrl);
|
||||
//this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
||||
return cCtrl;
|
||||
}
|
||||
|
||||
// If no curve data is available, log a warning and return 0
|
||||
this.logger.warn(`No curve data available for control calculation. Returning 0.`);
|
||||
this.measurements.type("ctrl").variant("predicted").position('upstream').value(0);
|
||||
this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(0);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
// this function returns the pressure for calculations
|
||||
// returns the best available pressure measurement to use in the prediction calculation
|
||||
// this will be either the differential pressure, downstream or upstream pressure
|
||||
getMeasuredPressure() {
|
||||
if(this.hasCurve === false){
|
||||
this.logger.error(`No valid curve available to calculate prediction using last known pressure`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const pressureDiff = this.measurements.type('pressure').variant('measured').difference();
|
||||
|
||||
// Both upstream & downstream => differential
|
||||
if (pressureDiff != null) {
|
||||
if (pressureDiff) {
|
||||
this.logger.debug(`Pressure differential: ${pressureDiff.value}`);
|
||||
this.predictFlow.fDimension = pressureDiff.value;
|
||||
this.predictPower.fDimension = pressureDiff.value;
|
||||
@@ -359,7 +457,7 @@ class Machine {
|
||||
|
||||
// Only downstream => use it, warn that it's partial
|
||||
if (downstreamPressure != null) {
|
||||
this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} `);
|
||||
this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} This is less acurate!!`);
|
||||
this.predictFlow.fDimension = downstreamPressure;
|
||||
this.predictPower.fDimension = downstreamPressure;
|
||||
this.predictCtrl.fDimension = downstreamPressure;
|
||||
@@ -384,6 +482,9 @@ class Machine {
|
||||
const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
|
||||
//update the distance from peak
|
||||
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
||||
//place min and max flow capabilities in containerthis.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin
|
||||
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax).unit(this.config.general.unit);
|
||||
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -406,7 +507,7 @@ class Machine {
|
||||
}
|
||||
|
||||
// get
|
||||
const upstreamFlow = this.measurements.type('pressure').variant('measured').position('upstream').getCurrentValue();
|
||||
const upstreamFlow = this.measurements.type('flow').variant('measured').position('upstream').getCurrentValue();
|
||||
|
||||
// Only upstream => might still accept it, but warn
|
||||
if (upstreamFlow != null) {
|
||||
@@ -415,7 +516,7 @@ class Machine {
|
||||
}
|
||||
|
||||
// get
|
||||
const downstreamFlow = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue();
|
||||
const downstreamFlow = this.measurements.type('flow').variant('measured').position('downstream').getCurrentValue();
|
||||
|
||||
// Only downstream => might still accept it, but warn
|
||||
if (downstreamFlow != null) {
|
||||
@@ -429,7 +530,7 @@ class Machine {
|
||||
}
|
||||
|
||||
handleMeasuredPower() {
|
||||
const power = this.measurements.type("power").variant("measured").position("upstream").getCurrentValue();
|
||||
const power = this.measurements.type("power").variant("measured").position("atEquipment").getCurrentValue();
|
||||
// If your system calls it "upstream" or just a single "value", adjust accordingly
|
||||
|
||||
if (power != null) {
|
||||
@@ -441,73 +542,51 @@ class Machine {
|
||||
}
|
||||
}
|
||||
|
||||
updatePressure(variant,value,position) {
|
||||
// context handler for pressure updates
|
||||
updateMeasuredPressure(value, position, context = {}) {
|
||||
|
||||
switch (variant) {
|
||||
case ("measured"):
|
||||
//only update when machine is in a state where it can be used
|
||||
if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") {
|
||||
// put value in measurements
|
||||
this.measurements.type("pressure").variant("measured").position(position).value(value);
|
||||
//when measured pressure gets updated we need some logic to fetch the relevant value which could be downstream or differential pressure
|
||||
this.logger.debug(`Pressure update: ${value} at ${position} from ${context.childName || 'child'} (${context.childId || 'unknown-id'})`);
|
||||
|
||||
// Store in parent's measurement container
|
||||
this.measurements.type("pressure").variant("measured").position(position).value(value, context.timestamp, context.unit);
|
||||
|
||||
// Determine what kind of value to use as pressure (upstream , downstream or difference)
|
||||
const pressure = this.getMeasuredPressure();
|
||||
//update the flow power and cog
|
||||
this.updatePosition();
|
||||
this.logger.debug(`Measured pressure: ${pressure}`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.warn(`Unrecognized variant '${variant}' for pressure update.`);
|
||||
break;
|
||||
}
|
||||
this.logger.debug(`Using pressure: ${pressure} for calculations`);
|
||||
}
|
||||
|
||||
updateFlow(variant,value,position) {
|
||||
|
||||
switch (variant) {
|
||||
case ("measured"):
|
||||
// put value in measurements
|
||||
this.measurements.type("flow").variant("measured").position(position).value(value);
|
||||
//when measured flow gets updated we need to push the last known value in the prediction measurements to keep them synced
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY);
|
||||
break;
|
||||
|
||||
case ("predicted"):
|
||||
this.logger.debug('not doing anythin yet');
|
||||
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);
|
||||
// Update flow measurement
|
||||
this.flowDrift = this.assessDrift("flow", this.predictFlow.currentFxyYMin , this.predictFlow.currentFxyYMax);
|
||||
this.logger.debug(`---------------------------------------- `);
|
||||
break;
|
||||
case "power":
|
||||
// Update power measurement
|
||||
break;
|
||||
default:
|
||||
this.logger.error(`Type '${type}' not recognized for measured update.`);
|
||||
// NEW: Flow handler
|
||||
updateMeasuredFlow(value, position, context = {}) {
|
||||
if (!this._isOperationalState()) {
|
||||
this.logger.warn(`Machine not operational, skipping flow update from ${context.childName || 'unknown'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
|
||||
|
||||
// Store in parent's measurement container
|
||||
this.measurements.type("flow").variant("measured").position(position).value(value, context.timestamp, context.unit);
|
||||
|
||||
// Update predicted flow if you have prediction capability
|
||||
if (this.predictFlow) {
|
||||
this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0);
|
||||
this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method for operational state check
|
||||
_isOperationalState() {
|
||||
const state = this.state.getCurrentState();
|
||||
this.logger.debug(`Checking operational state ${this.state.getCurrentState()} ? ${["operational", "accelerating", "decelerating"].includes(state)}`);
|
||||
return ["operational", "accelerating", "decelerating"].includes(state);
|
||||
}
|
||||
|
||||
//what is the internal functions that need updating when something changes that has influence on this.
|
||||
updatePosition() {
|
||||
if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") {
|
||||
|
||||
if (this._isOperationalState()) {
|
||||
|
||||
const currentPosition = this.state.getCurrentPosition();
|
||||
|
||||
@@ -524,6 +603,7 @@ class Machine {
|
||||
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
calcDistanceFromPeak(currentEfficiency,peakEfficiency){
|
||||
@@ -538,6 +618,22 @@ class Machine {
|
||||
return distance;
|
||||
}
|
||||
|
||||
showWorkingCurves() {
|
||||
// Show the current curves for debugging
|
||||
const { powerCurve, flowCurve } = this.getCurrentCurves();
|
||||
return {
|
||||
powerCurve: powerCurve,
|
||||
flowCurve: flowCurve,
|
||||
cog: this.cog,
|
||||
cogIndex: this.cogIndex,
|
||||
NCog: this.NCog,
|
||||
minEfficiency: this.minEfficiency,
|
||||
currentEfficiencyCurve: this.currentEfficiencyCurve,
|
||||
absDistFromPeak: this.absDistFromPeak,
|
||||
relDistFromPeak: this.relDistFromPeak
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the center of gravity for current pressure
|
||||
calcCog() {
|
||||
|
||||
@@ -547,7 +643,7 @@ class Machine {
|
||||
const {efficiencyCurve, peak, peakIndex, minEfficiency } = this.calcEfficiencyCurve(powerCurve, flowCurve);
|
||||
|
||||
// Calculate the normalized center of gravity
|
||||
const NCog = (flowCurve.y[peakIndex] - this.predictFlow.currentFxyYMin) / (this.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin);
|
||||
const NCog = (flowCurve.y[peakIndex] - this.predictFlow.currentFxyYMin) / (this.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin); //
|
||||
|
||||
//store in object for later retrieval
|
||||
this.currentEfficiencyCurve = efficiencyCurve;
|
||||
@@ -599,14 +695,37 @@ class Machine {
|
||||
|
||||
calcEfficiency(power,flow,variant) {
|
||||
|
||||
const pressureDiff = this.measurements.type('pressure').variant('measured').difference('Pa');
|
||||
const g = gravity.getStandardGravity();
|
||||
const temp = this.measurements.type('temperature').variant('measured').position('atEquipment').getCurrentValue('K');
|
||||
const atmPressure = this.measurements.type('atmPressure').variant('measured').position('atEquipment').getCurrentValue('Pa');
|
||||
|
||||
console.log(`--------------------calc efficiency : Pressure diff:${pressureDiff},${temp}, ${g} `);
|
||||
const rho = coolprop.PropsSI('D', 'T', temp, 'P', atmPressure, 'WasteWater');
|
||||
|
||||
|
||||
this.logger.debug(`temp: ${temp} atmPressure : ${atmPressure} rho : ${rho} pressureDiff: ${pressureDiff?.value || 0}`);
|
||||
const flowM3s = this.measurements.type('flow').variant('predicted').position('atEquipment').getCurrentValue('m3/s');
|
||||
const powerWatt = this.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue('W');
|
||||
this.logger.debug(`Flow : ${flowM3s} power: ${powerWatt}`);
|
||||
|
||||
if (power != 0 && flow != 0) {
|
||||
// Calculate efficiency after measurements update
|
||||
this.measurements.type("efficiency").variant(variant).position('downstream').value((flow / power));
|
||||
} else {
|
||||
this.measurements.type("efficiency").variant(variant).position('downstream').value(null);
|
||||
const specificFlow = flow / power;
|
||||
const specificEnergyConsumption = power / flow;
|
||||
|
||||
this.measurements.type("efficiency").variant(variant).position('atEquipment').value(specificFlow);
|
||||
this.measurements.type("specificEnergyConsumption").variant(variant).position('atEquipment').value(specificEnergyConsumption);
|
||||
|
||||
if(pressureDiff?.value != null && flowM3s != null && powerWatt != null){
|
||||
const meterPerBar = pressureDiff.value / rho * g;
|
||||
const nHydraulicEfficiency = rho * g * flowM3s * (pressureDiff.value * meterPerBar ) / powerWatt;
|
||||
this.measurements.type("nHydraulicEfficiency").variant(variant).position('atEquipment').value(nHydraulicEfficiency);
|
||||
}
|
||||
|
||||
return this.measurements.type("efficiency").variant(variant).position('downstream').getCurrentValue();
|
||||
}
|
||||
|
||||
//change this to nhydrefficiency ?
|
||||
return this.measurements.type("efficiency").variant(variant).position('atEquipment').getCurrentValue();
|
||||
|
||||
}
|
||||
|
||||
@@ -652,26 +771,8 @@ class Machine {
|
||||
getOutput() {
|
||||
|
||||
// Improved output object generation
|
||||
const output = {};
|
||||
//build the output object
|
||||
this.measurements.getTypes().forEach(type => {
|
||||
this.measurements.getVariants(type).forEach(variant => {
|
||||
|
||||
const downstreamVal = this.measurements.type(type).variant(variant).position("downstream").getCurrentValue();
|
||||
const upstreamVal = this.measurements.type(type).variant(variant).position("upstream").getCurrentValue();
|
||||
|
||||
if (downstreamVal != null) {
|
||||
output[`downstream_${variant}_${type}`] = downstreamVal;
|
||||
}
|
||||
if (upstreamVal != null) {
|
||||
output[`upstream_${variant}_${type}`] = upstreamVal;
|
||||
}
|
||||
if (downstreamVal != null && upstreamVal != null) {
|
||||
const diffVal = this.measurements.type(type).variant(variant).difference().value;
|
||||
output[`differential_${variant}_${type}`] = diffVal;
|
||||
}
|
||||
});
|
||||
});
|
||||
const output = this.measurements.getFlattenedOutput();
|
||||
|
||||
//fill in the rest of the output object
|
||||
output["state"] = this.state.getCurrentState();
|
||||
@@ -682,6 +783,7 @@ class Machine {
|
||||
output["cog"] = this.cog; // flow / power efficiency
|
||||
output["NCog"] = this.NCog; // normalized cog
|
||||
output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ;
|
||||
output["maintenanceTime"] = this.state.getMaintenanceTimeHours();
|
||||
|
||||
if(this.flowDrift != null){
|
||||
const flowDrift = this.flowDrift;
|
||||
@@ -710,7 +812,7 @@ module.exports = Machine;
|
||||
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
|
||||
|
||||
//import a child
|
||||
const Child = require('../../../measurement/dependencies/measurement/measurement');
|
||||
const Child = require('../../measurement/src/specificClass');
|
||||
|
||||
console.log(`Creating child...`);
|
||||
const PT1 = new Child(config={
|
||||
@@ -723,11 +825,16 @@ const PT1 = new Child(config={
|
||||
},
|
||||
functionality:{
|
||||
softwareType:"measurement",
|
||||
positionVsParent:"upstream",
|
||||
},
|
||||
asset:{
|
||||
type:"sensor",
|
||||
subType:"pressure",
|
||||
supplier:"Vega",
|
||||
category:"sensor",
|
||||
type:"pressure",
|
||||
model:"Vegabar 82",
|
||||
unit: "mbar"
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const PT2 = new Child(config={
|
||||
@@ -740,10 +847,14 @@ const PT2 = new Child(config={
|
||||
},
|
||||
functionality:{
|
||||
softwareType:"measurement",
|
||||
positionVsParent:"upstream",
|
||||
},
|
||||
asset:{
|
||||
type:"sensor",
|
||||
subType:"pressure",
|
||||
supplier:"Vega",
|
||||
category:"sensor",
|
||||
type:"pressure",
|
||||
model:"Vegabar 82",
|
||||
unit: "mbar"
|
||||
},
|
||||
});
|
||||
|
||||
@@ -761,7 +872,7 @@ const machineConfig = {
|
||||
asset: {
|
||||
supplier: "Hydrostal",
|
||||
type: "pump",
|
||||
subType: "centrifugal",
|
||||
category: "centrifugal",
|
||||
model: "H05K-S03R+HGM1X-X280KO", // Ensure this field is present.
|
||||
machineCurve: curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user